For instance,
class Point
attr_accessor :x, :y, :pointer_to_something_huge
end
I only want to serialize x and y and leave everything else as nil.
In Ruby 1.9, to_yaml_properties is deprecated; if you're using Ruby 1.9, a more future proof method would be to use encode_with:
class Point
def encode_with coder
coder['x'] = #x
coder['y'] = #y
end
end
In this case that’s all you need, as the default is to set the corresponding instance variable of the new object to the appropriate value when loading from Yaml, but in more comple cases you could use init_with:
def init_with coder
#x = coder['x']
#y = coder['y']
end
After an inordinate amount of searching I stumbled on this:
class Point
def to_yaml_properties
["#x", "#y"]
end
end
This method is used to select the properties that YAML serializes. There is a more powerful approach that involves custom emitters (in Psych) but I don't know what it is.
This solution only works in Ruby 1.8; in Ruby 1.9, to_yaml has switched to using Psych, for which Matt's answer using encode_with is the appropriate solution.
If you want all fields but a few, you could do this
def encode_with(coder)
vars = instance_variables.map{|x| x.to_s}
vars = vars - ['#unwanted_field1', '#unwanted_field2']
vars.each do |var|
var_val = eval(var)
coder[var.gsub('#', '')] = var_val
end
end
This stops you from manually having to manage the list. Tested on Ruby 1.9
If you have a plenty of instance variables, you could use a short version like this one
def encode_with( coder )
%w[ x y a b c d e f g ].each { |v| coder[ v ] = instance_variable_get "##{v}" }
end
You should use #encode_with because #to_yaml_properties is deprecated:
def encode_with(coder)
# remove #unwanted and #other_unwanted variable from the dump
(instance_variables - [:#unwanted, :#other_unwanted]).each do |var|
var = var.to_s # convert symbol to string
coder[var.gsub('#', '')] = eval(var) # set key and value in coder hash
end
end
or you might prefer this if eval is too dangerous and you only need to filter out one instance var. All other vars need to have an accessor:
attr_accessor :keep_this, :unwanted
def encode_with(coder)
# reject #unwanted var, all others need to have an accessor
instance_variables.reject{|x|x==:#unwanted}.map(&:to_s).each do |var|
coder[var[1..-1]] = send(var[1..-1])
end
end
I'd recommend adding a custom to_yaml method in your class that constructs the specific yaml format you want.
I know that to_json accepts parameters to tell it what attributes to serialize, but I can't find the same for to_yaml.
Here's the actual source for to_yaml:
# File activerecord/lib/active_record/base.rb, line 653
def to_yaml(opts = {}) #:nodoc:
if YAML.const_defined?(:ENGINE) && !YAML::ENGINE.syck?
super
else
coder = {}
encode_with(coder)
YAML.quick_emit(self, opts) do |out|
out.map(taguri, to_yaml_style) do |map|
coder.each { |k, v| map.add(k, v) }
end
end
end
end
So it looks like there may be an opportunity to set opts so that it includes specific key/value pairs in the yaml.
Related
In Python I can easily decorate methods so that they remember their result:
def store(self):
a = line1()
b = line2(a)
return line3(b)
=>
from lazy import lazy
#lazy
def store(self):
a = line1()
b = line2(a)
return line3(b)
Is there some similar idiom in Ruby for calculating method result only once?
In Ruby this is generally called memoization and it takes the naive form of:
def store
#store ||= begin
a = line1
b = line2(a)
line3(b)
end
end
There are important concerns if this code is used in a multi-threaded environment, though, which is why there are gems that manage this and ensure your lazy initializers are run only once if that's a concern.
Another option that works with false and nil too:
def store
unless defined?(#store)
a = line1
b = line2(a)
#store = line3(b)
end
#store
end
Not really. There is an operator (||=) that assigns a value only if the left operand is falsy (nil or false). Usually using that along with an instance variable does the trick.
Memoist and Memoizable are gems often used for that purpose.
But my guess is that your question is more general - how do you implement method decorators in ruby?
class Module
def decorate(method_name, &decoration)
undecorated_method = instance_method(method_name)
define_method(method_name) do
decoration.call(&undecorated_method.bind(self))
end
end
def memoize(method_name)
#values ||= {}
decorate(method_name) do |&evaluator|
unless #values.key?(method_name)
#values[method_name] = evaluator.call
end
#values[method_name]
end
end
end
class SuperComputer
memoize def answer
puts "Pending... 7½ million years remaining."
42
end
end
deep_mind = SuperComputer.new
deep_mind.answer # => 42 (and prints)
deep_mind.answer # => 42 (doesn't print)
Now there is a lot to be desired from this implementation (args, kwargs, blocks, per instance memoization, etc), but it should give you a general idea of how decorators can be implemented without the need for changes to the language itself.
I'm using a Struct as opposed to a simple Hash in a project to provide a semantic name to a collection of key value pairs. Once I've built the structure, however, I need to output a hash value. I'm in Ruby 1.9.3. Example:
MyMeaninfulName = Struct.new(:alpha, :beta, :gamma) do
def to_hash
self.members.inject({}) {|h,m| h[m] = self[m]; h}
end
end
my_var = MyMeaningfulName.new
my_var.to_hash # -> { :alpha=>nil, :beta=>nil, :gamma=>nil }
Is there a reason why Struct does not include a to_hash method? It seems like a natural fit, but perhaps there's an underlying reason why it's not included.
Second, is there a more elegant way to build a generic to_hash method into Struct (either generally, via monkeypatching, or through a module or inheritance).
I know the question is about ruby 1.9.3, but starting from ruby 2.0.0, Struct has a to_h method which does the job.
MyMeaningfulName = Struct.new(:alpha, :beta, :gamma)
my_var = MyMeaningfulName.new
my_var.to_h # -> { :alpha=>nil, :beta=>nil, :gamma=>nil }
or this:
class Struct
def to_hash
self.class.members.inject({}) {|h,m| h[m] = self[m]}
end
end
(note the extra class to get to the members)
I don't know why, it does seem obvious. Fortunately, you can use it as a hash in many places since it implements bracket operators.
Anyway, this is fairly elegant:
MyMeaningfulName = Struct.new :alpha, :beta, :gamma do
def to_hash
Hash[members.zip values]
end
end
my_var = MyMeaningfulName.new 1, 2, 3
my_var.to_hash # => {:alpha=>1, :beta=>2, :gamma=>3}
Try this:
class Struct
old_new = self.method(:new)
def self.new(*args)
obj = old_new.call(*args)
obj.class_exec do
def to_hash
self.members.inject({}) {|h,m| h[m] = self[m]; h}
end
end
return obj
end
end
Some code that I had that used attr_accessor_with_default in a rails model is now giving me a deprecation warning, telling me to "Use Ruby instead!"
So, thinking that maybe there was a new bit in ruby 1.9.2 that made attr_accessor handle defaults, I googled it, but I don't see that. I did see a bunch of methods to override attr_accessor to handle defaults though.
Is that what they mean when they tell me to "Use Ruby?" Or am I supposed to write full getters/setters now? Or is there some new way I can't find?
This apidock page suggests to just do it in the initialize method.
class Something
attr_accessor :pancakes
def initialize
#pancakes = true
super
end
end
Don't forget to call super especially when using ActiveRecord or similar.
attr_accessor :pancakes
def after_initialize
return unless new_record?
self.pancakes = 11
end
This ensures that the value is initialized to some default for new record only.
Since you probably know your data quite well, it can be quite acceptable to assume nil is not a valid value.
This means you can do away with an after_initialize, as this will be executed for every object you create. As several people have pointed out, this is (potentially) disastrous for performance. Also, inlining the method as in the example is deprecated in Rails 3.1 anyway.
To 'use Ruby instead' I would take this approach:
attr_writer :pancakes
def pancakes
return 12 if #pancakes.nil?
#pancakes
end
So trim down the Ruby magic just a little bit and write your own getter. After all this does exactly what you are trying to accomplish, and it's nice and simple enough for anyone to wrap his/her head around.
This is an ooooold question, but the general problem still crops up - and I found myself here.
The other answers are varied and interesting, but I found problems with all of them when initializing arrays (especially as I wanted to be able to use them at a class level before initialize was called on the instance). I had success with:
attr_writer :pancakes
def pancakes
#pancakes ||= []
end
If you use = instead of ||= you will find that the << operator fails for adding the first element to the array. (An anonymous array is created, a value is assigned to it, but it's never assigned back to #pancakes.)
For example:
obj.pancakes
#=> []
obj.pancakes << 'foo'
#=> ['foo']
obj.pancakes
#=> []
#???#!%$##%FRAK!!!
As this is quite a subtle problem and could cause a few head scratches, I thought it was worth mentioning here.
This pattern will need to be altered for a bool, for example if you want to default to false:
attr_writer :pancakes
def pancakes
#pancakes.nil? ? #pancakes = false : #pancakes
end
Although you could argue that the assignment isn't strictly necessary when dealing with a bool.
There's nothing magical in 1.9.2 for initializing instance variables that you set up with attr_accessor. But there is the after_initialize callback:
The after_initialize callback will be called whenever an Active Record object is instantiated, either by directly using new or when a record is loaded from the database. It can be useful to avoid the need to directly override your Active Record initialize method.
So:
attr_accessor :pancakes
after_initialize :init
protected
def init
#pancakes = 11
end
This is safer than something like this:
def pancakes
#pancakes ||= 11
end
because nil or false might be perfectly valid values after initialization and assuming that they're not can cause some interesting bugs.
I'm wondering if just using Rails implementation would work for you:
http://apidock.com/rails/Module/attr_accessor_with_default
def attr_accessor_with_default(sym, default = nil, &block)
raise 'Default value or block required' unless !default.nil? || block
define_method(sym, block_given? ? block : Proc.new { default })
module_eval( def #{sym}=(value) # def age=(value) class << self; attr_reader :#{sym} end # class << self; attr_reader :age end ##{sym} = value # #age = value end # end, __FILE__, __LINE__ + 1)
end
You can specify default values for instances of any class (not only ActiveRecords) after applying patch to Module:
class Zaloop
attr_accessor var1: :default_value, var2: 2
def initialize
self.initialize_default_values
end
end
puts Zaloop.new.var1 # :default_value
Patch for module:
Module.module_eval do
alias _original_attr_accessor attr_accessor
def attr_accessor(*args)
attr_names = extract_default_values args
_original_attr_accessor *attr_names
end
alias _original_attr_reader attr_reader
def attr_reader(*args)
attr_names = extract_default_values args
_original_attr_reader *attr_names
end
def extract_default_values(args)
#default_values ||= {}
attr_names = []
args.map do |arg|
if arg.is_a? Hash
arg.each do |key, value|
define_default_initializer if #default_values.empty?
#default_values[key] = value
attr_names << key
end
else
attr_names << arg
end
end
attr_names
end
def define_default_initializer
default_values = #default_values
self.send :define_method, :initialize_default_values do
default_values.each do |key, value|
instance_variable_set("##{key}".to_sym, value)
end
end
end
def initialize_default_values
# Helper for autocomplete and syntax highlighters
end
end
I have the following code I am using to turn a hash collection into methods on my classes (somewhat like active record). The problem I am having is that my setter is not working. I am still quite new to Ruby and believe I've gotten myself turned around a bit.
class TheClass
def initialize
#properties = {"my hash"}
self.extend #properties.to_methods
end
end
class Hash
def to_methods
hash = self
Module.new do
hash.each_pair do |key, value|
define_method key do
value
end
define_method("#{key}=") do |val|
instance_variable_set("##{key}", val)
end
end
end
end
end
The methods are created and I can read them on my class but setting them does not work.
myClass = TheClass.new
item = myClass.property # will work.
myClass.property = item # this is what is currently not working.
If your goal is to set dynamic properties then you could use OpenStruct.
require 'ostruct'
person = OpenStruct.new
person.name = "Jennifer Tilly"
person.age = 52
puts person.name
# => "Jennifer Tilly"
puts person.phone_number
# => nil
It even has built-in support to create them from a hash
hash = { :name => "Earth", :population => 6_902_312_042 }
planet = OpenStruct.new(hash)
Your getter method always returns the value in the original hash. Setting the instance variable won't change that; you need to make the getter refer to the instance variable. Something like:
hash.each_pair do |key, value|
define_method key do
instance_variable_get("##{key}")
end
# ... define the setter as before
end
And you also need to set the instance variables at the start, say by putting
#properties.each_pair do |key,val|
instance_variable_set("##{key}",val)
end
in the initialize method.
Note: I do not guarantee that this is the best way to do it; I am not a Ruby expert. But it does work.
It works just fine for me (after fixing the obvious syntax errors in your code, of course):
myClass.instance_variable_get(:#property) # => nil
myClass.property = 42
myClass.instance_variable_get(:#property) # => 42
Note that in Ruby instance variables are always private and you never define a getter for them, so you cannot actually look at them from the outside (other than via reflection), but that doesn't mean that your code doesn't work, it only means that you cannot see that it works.
This is essentially what I was suggesting with method_missing. I'm not familiar enough with either route to say why or why not to use it which is why I asked above. Essentially this will auto-generate properties for you:
def method_missing sym, *args
name = sym.to_s
aname = name.sub("=","")
self.class.module_eval do
attr_accessor aname
end
send name, args.first unless aname == name
end
Is there a simple way to list the accessors/readers that have been set in a Ruby Class?
class Test
attr_reader :one, :two
def initialize
# Do something
end
def three
end
end
Test.new
=> [one,two]
What I'm really trying to do is to allow initialize to accept a Hash with any number of attributes in, but only commit the ones that have readers already defined. Something like:
def initialize(opts)
opts.delete_if{|opt,val| not the_list_of_readers.include?(opt)}.each do |opt,val|
eval("##{opt} = \"#{val}\"")
end
end
Any other suggestions?
This is what I use (I call this idiom hash-init).
def initialize(object_attribute_hash = {})
object_attribute_hash.map { |(k, v)| send("#{k}=", v) }
end
If you are on Ruby 1.9 you can do it even cleaner (send allows private methods):
def initialize(object_attribute_hash = {})
object_attribute_hash.map { |(k, v)| public_send("#{k}=", v) }
end
This will raise a NoMethodError if you try to assign to foo and method "foo=" does not exist. If you want to do it clean (assign attrs for which writers exist) you should do a check
def initialize(object_attribute_hash = {})
object_attribute_hash.map do |(k, v)|
writer_m = "#{k}="
send(writer_m, v) if respond_to?(writer_m) }
end
end
however this might lead to situations where you feed your object wrong keys (say from a form) and instead of failing loudly it will just swallow them - painful debugging ahead. So in my book a NoMethodError is a better option (it signifies a contract violation).
If you just want a list of all writers (there is no way to do that for readers) you do
some_object.methods.grep(/\w=$/)
which is "get an array of method names and grep it for entries which end with a single equals sign after a word character".
If you do
eval("##{opt} = \"#{val}\"")
and val comes from a web form - congratulations, you just equipped your app with a wide-open exploit.
You could override attr_reader, attr_writer and attr_accessor to provide some kind of tracking mechanism for your class so you can have better reflection capability such as this.
For example:
class Class
alias_method :attr_reader_without_tracking, :attr_reader
def attr_reader(*names)
attr_readers.concat(names)
attr_reader_without_tracking(*names)
end
def attr_readers
#attr_readers ||= [ ]
end
alias_method :attr_writer_without_tracking, :attr_writer
def attr_writer(*names)
attr_writers.concat(names)
attr_writer_without_tracking(*names)
end
def attr_writers
#attr_writers ||= [ ]
end
alias_method :attr_accessor_without_tracking, :attr_accessor
def attr_accessor(*names)
attr_readers.concat(names)
attr_writers.concat(names)
attr_accessor_without_tracking(*names)
end
end
These can be demonstrated fairly simply:
class Foo
attr_reader :foo, :bar
attr_writer :baz
attr_accessor :foobar
end
puts "Readers: " + Foo.attr_readers.join(', ')
# => Readers: foo, bar, foobar
puts "Writers: " + Foo.attr_writers.join(', ')
# => Writers: baz, foobar
Try something like this:
class Test
attr_accessor :foo, :bar
def initialize(opts = {})
opts.each do |opt, val|
send("#{opt}=", val) if respond_to? "#{opt}="
end
end
end
test = Test.new(:foo => "a", :bar => "b", :baz => "c")
p test.foo # => nil
p test.bar # => nil
p test.baz # => undefined method `baz' for #<Test:0x1001729f0 #bar="b", #foo="a"> (NoMethodError)
This is basically what Rails does when you pass in a params hash to new. It will ignore all parameters it doesn't know about, and it will allow you to set things that aren't necessarily defined by attr_accessor, but still have an appropriate setter.
The only downside is that this really requires that you have a setter defined (versus just the accessor) which may not be what you're looking for.
Accessors are just ordinary methods that happen to access some piece of data. Here's code that will do roughly what you want. It checks if there's a method named for the hash key and sets an accompanying instance variable if so:
def initialize(opts)
opts.each do |opt,val|
instance_variable_set("##{opt}", val.to_s) if respond_to? opt
end
end
Note that this will get tripped up if a key has the same name as a method but that method isn't a simple instance variable access (e.g., {:object_id => 42}). But not all accessors will necessarily be defined by attr_accessor either, so there's not really a better way to tell. I also changed it to use instance_variable_set, which is so much more efficient and secure it's ridiculous.
There's no built-in way to get such a list. The attr_* functions essentially just add methods, create an instance variable, and nothing else. You could write wrappers for them to do what you want, but that might be overkill. Depending on your particular circumstances, you might be able to make use of Object#instance_variable_defined? and Module#public_method_defined?.
Also, avoid using eval when possible:
def initialize(opts)
opts.delete_if{|opt,val| not the_list_of_readers.include?(opt)}.each do |opt,val|
instance_variable_set "##{opt}", val
end
end
You can look to see what methods are defined (with Object#methods), and from those identify the setters (the last character of those is =), but there's no 100% sure way to know that those methods weren't implemented in a non-obvious way that involves different instance variables.
Nevertheless Foo.new.methods.grep(/=$/) will give you a printable list of property setters. Or, since you have a hash already, you can try:
def initialize(opts)
opts.each do |opt,val|
instance_variable_set("##{opt}", val.to_s) if respond_to? "#{opt}="
end
end