A parameter defaulted to an empty hash attrs={} returns an error:
can't convert Array into Hash
(TypeError)
I've tried this on Ruby versions 1.8.6, 1.8.7 and 1.9.1. A hash will be passed to attrs.
class Category < Object
attr_accessor :attributes
attr_accessor :rel_attrs
attr_accessor :cls_str
def initialize (term='',title='', attrs={}, scheme = '', rel=[], cls_str='')
#attributes ={}
#attributes['scheme'] = scheme
#attributes['term'] = term
#attributes['title'] = title
#attributes['related'] = rel
#cls_str = cls_str
if not attrs.empty?
#attributes.update attrs
end
end
end
What am I doing wrong?
Some notes:
You don't have to inherit from Object.
if not can more idiomatically be expressed as unless.
Making the attrs hash the last argument has the advantage that you can leave off the {} around the hash elements when calling Category.new. You can not do this if the hash is the middle, so it would make sense to show us your call to Category.new.
I changed your code accordingly:
class Category
attr_accessor :attributes
attr_accessor :rel_attrs
attr_accessor :cls_str
def initialize (term='',title='', scheme = '', rel=[], cls_str='', attrs={})
#attributes ={}
#attributes['scheme'] = scheme
#attributes['term'] = term
#attributes['title'] = title
#attributes['related'] = rel
#cls_str = cls_str
#attributes.update(attrs) unless attrs.empty?
end
end
Here's how you call it:
>> c = Category.new("term", "title", "scheme", [1,2,3], 'cls_string', :foo => 'bar', :baz => 'qux')
#=> #<Category:0x00000100b7bff0 #attributes={"scheme"=>"scheme", "term"=>"term", "title"=>"title", "related"=>[1, 2, 3], :foo=>"bar", :baz=>"qux"}, cls_str"cls_string"
>> c.attributes
#=> {"scheme"=>"scheme", "term"=>"term", "title"=>"title", "related"=>[1, 2, 3], :foo=>"bar", :baz=>"qux"}
This obviously has the problem that all other optional arguments have to be specified in order to be able to specify attrs. If you don't want this, move attrs back to the middle of the argument list and make sure to include the {} in the call. Or even better, make the whole argument list a hash and merge it with the defaults args. Something like
class Category(opts = {})
# stuff skipped
#options = { :bla => 'foo', :bar => 'baz'}.merge(opts)
# more stuff skipped
end
Related
I was working on a homework assignment when I ran into a frustrating issue. The assignment is an exercise in Ruby metaprogramming and the goal is to define an 'attr_accessor_with_history' that does all the same things as 'attr_accessor', but also provides a history of all values that an attribute has ever been. Here is the provided code from the assignment along with some code I added in an attempt to complete the assignment:
class Class
def attr_accessor_with_history(attr_name)
attr_name = attr_name.to_s
attr_hist_name = attr_name+'_history'
history_hash = {attr_name => []}
#getter
self.class_eval("def #{attr_name} ; ##{attr_name} ; end")
#setter
self.class_eval %Q{
def #{attr_name}=(val)
# add to history
##{attr_hist_name} = [nil] if ##{attr_hist_name}.nil?
##{attr_hist_name} << val
history_hash[##{attr_name}] = ##{attr_hist_name}
# set the value itself
##{attr_name} = val
end
def history(attr) ; #history_hash[attr.to_s] ; end
}
end
end
class Foo
attr_accessor_with_history :bar
attr_accessor_with_history :crud
end
f = Foo.new # => #<Foo:0x127e678>
f.bar = 3 # => 3
f.bar = :wowzo # => :wowzo
f.bar = 'boo!' # => 'boo!'
puts f.history(:bar) # => [3, :wowzo, 'boo!']
f.crud = 42
f.crud = "Hello World!"
puts f.history(:crud)
I wanted to use a hash to store different histories for different attributes but I cannot access that hash in the class_eval statement for the setter. No matter how I try to set it up I always either seem to get a NoMethodError for the []= method because 'history_hash' somehow becomes type NilClass, or a NameError occurs because it sees 'history_hash' as an undefined local variable or method. How do I use the hash in the class_eval statements?
or a NameError occurs because it sees 'history_hash' as an undefined local variable or method
I'd say you can't, because it is a local variable, one that is inaccessible in the context you want it. However, why do you even need it? I'm reasonably sure it's in the "some code I added in an attempt to complete the assignment", and not the original assignment code (which, I assume, expects you to store the history of #bar in #bar_history - or else what is attr_hist_name all about?)
I'm also uncomfortable about string evals; it's generally not necessary, and Ruby can do better, with its powerful metaprogramming facilities. Here's how I'd do it:
class Class
def attr_accessor_with_history(attr_name)
attr_setter_name = :"#{attr_name}="
attr_getter_name = :"#{attr_name}"
attr_hist_name = :"##{attr_name}_history"
attr_name = :"##{attr_name}"
self.class_eval do
define_method(attr_getter_name) do
instance_variable_get(attr_name)
end
define_method(attr_setter_name) do |val|
instance_variable_set(attr_name, val)
history = instance_variable_get(attr_hist_name)
instance_variable_set(attr_hist_name, history = []) unless history
history << val
end
end
end
end
class Object
def history(attr_name)
attr_hist_name = :"##{attr_name}_history"
instance_variable_get(attr_hist_name)
end
end
Finally, as it's monkey-patching base classes, I'd rather use refinements to add it where needed, but that's probably an overkill for an assignment.
According to this post, to make attributes stick during to_json call, we need to do something like this:
def attributes
super.merge('foo' => self.foo)
end
With my beginner's knowledge in Ruby, I fail to understand the following:
Is there an attribute method present for every Ruby class?
What is super.merge doing here? Which hashmap does it append its argument to?
No, there is not an #attributes method for every Ruby class. The class you're using likely inherits from another class or mixes in a module which does (e.g. ActiveRecord::Base#attributes).
That attributes method that you're defining will override any existing #attributes method. In the case of an override, Ruby provides a #super method, which calls the original method that you're overriding. In this case, you'll be calling the original #attributes method which returns a Hash of attributes keys and their values (e.g. { attr1: 'a', attr2: 'b' }).
#merge is a Hash function that you're calling on the Hash that the original #attributes call returns (e.g { attr1: 'a', attr2: 'b' }). #merge creates a new hash consisting of the original attributes hash combined with the key-value pairs provided in the second hash.
From the Ruby 2.2 docs on Hash#merge:
merge(other_hash) → new_hash click to toggle source
merge(other_hash){|key, oldval, newval| block} → new_hash
Returns a new hash containing the contents of other_hash and the
contents of hsh. If no block is specified, the value for entries with
duplicate keys will be that of other_hash. Otherwise the value for
each duplicate key is determined by calling the block with the key,
its value in hsh and its value in other_hash.
h1 = { "a" => 100, "b" => 200 }
h2 = { "b" => 254, "c" => 300 }
h1.merge(h2) #=> {"a"=>100, "b"=>254, "c"=>300}
h1.merge(h2){|key, oldval, newval| newval - oldval}
#=> {"a"=>100, "b"=>54, "c"=>300}
h1 #=> {"a"=>100, "b"=>200}
http://ruby-doc.org/core-2.2.0/Hash.html#method-i-merge
Some notes about your example: 'foo' => self.foo
You don't need to specify self in self.foo. foo alone should suffice. That's really only needed for assignments self.foo = 'whatever' and in cases where you've defined another foo in the same scope.
Make sure that they type of the key you're providing matches the type of the keys that #attributes is returning.
Case 1: #attributes returns a Hash of Strings -> Values, so merge in a hash with String keys (ActiveRecord does this.)
{ 'attr1' => 1, 'attr2' => 'b' }.merge( { 'attr3' => '3' }
Case 2: #attributes returns a Hash of Symbols -> Values, so merge in a hash with Symbol keys:
{ :attr1 => 1, :attr2 => 'b' }.merge( { :attr3 => '3' }
Hi attributes is a method provided by ActiveRecord. If you click on source you will notice how it really just exposes the instance variable #attributes which is a hash (as it can be nil it is enforced to a hash through .to_hash).
class ActiveRecord::Base
def attributes
#attributes.to_hash
end
end
We'll call this method parent as we will extend its behaviour in our class. This is possible through inheritance:
class Person < ActiveRecord::Base
def attributes
super.merge('foo' => self.foo)
end
end
attributes is now calling the parent's method [Activerecord::Base].attributes and is adding a new key to the hash.
This code is roughly equivalent and should be easier to read.
class Person < ActiveRecord::Base
def attributes
attrs = super # eg. { name: "x", created_at: [...], [...] }
attrs[:foo] = self.foo # eg. { name: "x", foo: [...], ... }
attrs
end
end
Attributes are a synonym properties, variables etc on your class. Not all classes contain attributes but data models generally exist to encapsulate the attributes and methods they contain, as well as some behavior to modify and/or operate on them. To respond with a json representation of a model you would generally do something like this in your controller.
respond_to :json
def tags
render :json => User.find(params[:id]).tags
end
According to the documentation unset attributes of Struct are set to nil:
unset parameters default to nil.
Is it possible to specify the default value for particular attributes?
For example, for the following Struct
Struct.new("Person", :name, :happy)
I would like the attribute happy to default to true rather than nil. How can I do this? If I do as follows
Struct.new("Person", :name, :happy = true)
I get
-:1: syntax error, unexpected '=', expecting ')'
Struct.new("Person", :name, :happy = true)
^
-:1: warning: possibly useless use of true in void context
This can also be accomplished by creating your Struct as a subclass, and overriding
initialize with default values as in the following example:
class Person < Struct.new(:name, :happy)
def initialize(name, happy=true); super end
end
On one hand, this method does lead to a little bit of boilerplate; on the other, it does what you're looking for nice and succinctly.
One side-effect (which may be either a benefit or an annoyance depending on your preferences/use case) is that you lose the default Struct behavior of all attributes defaulting to nil -- unless you explicitly set them to be so. In effect, the above example would make name a required parameter unless you declare it as name=nil
Following #rintaun's example you can also do this with keyword arguments in Ruby 2+
A = Struct.new(:a, :b, :c) do
def initialize(a:, b: 2, c: 3); super end
end
A.new
# ArgumentError: missing keyword: a
A.new a: 1
# => #<struct A a=1, b=2, c=3>
A.new a: 1, c: 6
# => #<struct A a=1, b=2, c=6>
UPDATE
The code now needs to be written as follows to work.
A = Struct.new(:a, :b, :c) do
def initialize(a:, b: 2, c: 3)
super(a, b, c)
end
end
I also found this:
Person = Struct.new "Person", :name, :happy do
def initialize(*)
super
self.location ||= true
end
end
#Linuxios gave an answer that overrides member lookup. This has a couple problems: you can't explicitly set a member to nil and there's extra overhead on every member reference. It seems to me you really just want to supply the defaults when initializing a new struct object with partial member values supplied to ::new or ::[].
Here's a module to extend Struct with an additional factory method that lets you describe your desired structure with a hash, where the keys are the member names and the values the defaults to fill in when not supplied at initialization:
# Extend stdlib Struct with a factory method Struct::with_defaults
# to allow StructClasses to be defined so omitted members of new structs
# are initialized to a default instead of nil
module StructWithDefaults
# makes a new StructClass specified by spec hash.
# keys are member names, values are defaults when not supplied to new
#
# examples:
# MyStruct = Struct.with_defaults( a: 1, b: 2, c: 'xyz' )
# MyStruct.new #=> #<struct MyStruct a=1, b=2, c="xyz"
# MyStruct.new(99) #=> #<struct MyStruct a=99, b=2, c="xyz">
# MyStruct[-10, 3.5] #=> #<struct MyStruct a=-10, b=3.5, c="xyz">
def with_defaults(*spec)
new_args = []
new_args << spec.shift if spec.size > 1
spec = spec.first
raise ArgumentError, "expected Hash, got #{spec.class}" unless spec.is_a? Hash
new_args.concat spec.keys
new(*new_args) do
class << self
attr_reader :defaults
end
def initialize(*args)
super
self.class.defaults.drop(args.size).each {|k,v| self[k] = v }
end
end.tap {|s| s.instance_variable_set(:#defaults, spec.dup.freeze) }
end
end
Struct.extend StructWithDefaults
Just add another variation:
class Result < Struct.new(:success, :errors)
def initialize(*)
super
self.errors ||= []
end
end
I think that the override of the #initialize method is the best way, with call to #super(*required_args).
This has an additional advantage of being able to use hash-style arguments. Please see the following complete and compiling example:
Hash-Style Arguments, Default Values, and Ruby Struct
# This example demonstrates how to create Ruby Structs that use
# newer hash-style parameters, as well as the default values for
# some of the parameters, without loosing the benefits of struct's
# implementation of #eql? #hash, #to_s, #inspect, and other
# useful instance methods.
#
# Run this file as follows
#
# > gem install rspec
# > rspec struct_optional_arguments.rb --format documentation
#
class StructWithOptionals < Struct.new(
:encrypted_data,
:cipher_name,
:iv,
:salt,
:version
)
VERSION = '1.0.1'
def initialize(
encrypted_data:,
cipher_name:,
iv: nil,
salt: 'salty',
version: VERSION
)
super(encrypted_data, cipher_name, iv, salt, version)
end
end
require 'rspec'
RSpec.describe StructWithOptionals do
let(:struct) { StructWithOptionals.new(encrypted_data: 'data', cipher_name: 'AES-256-CBC', iv: 'intravenous') }
it 'should be initialized with default values' do
expect(struct.version).to be(StructWithOptionals::VERSION)
end
context 'all fields must be not null' do
%i(encrypted_data cipher_name salt iv version).each do |field|
subject { struct.send(field) }
it field do
expect(subject).to_not be_nil
end
end
end
end
This is my hash:
tempData = {"a" => 100, "here" => 200, "c" => "hello"}
I need to access the hash keys as a method like:
tempData.a #100
tempData.here # 200
You could just wrap up your hash in an OpenStruct:
require 'ostruct'
tempData = {"a" => 100, "here" => 200, "c" => "hello"}
os = OpenStruct.new tempData
os.a #=> 100
os.here #=> 200
If you really really wanted to, you could also monkey-patch the Hash class, but I'd advise against that:
class Hash
def method_missing(m, *args, &blk)
fetch(m) { fetch(m.to_s) { super } }
end
end
tempData = {"a" => 100, "here" => 200, "c" => "hello"}
tempData.a #=> 100
Update: In my personal extensions library I added a Hash#to_ostruct method. This will recursively convert a hash into an OpenStruct including all nested hashes.
There is another way to do this.
JSON.parse(tempData.to_json, object_class: OpenStruct)
that will give object
#<OpenStruct a=100, here=200, c="hello">
In this way nested hash also will be converted to OpenStruct Object
tempData = {a: { b: { c: 3}}, foo: 200, msg: 'test msg'}
obj = JSON.parse(tempData.to_json, object_class: OpenStruct)
Now we are able to call
obj.a.b.c # 3
obj.foo # 200
obj.msg # 'test msg'
Hope this will help someone.
Alternatively, if it’s just a small script it might be more convenient to just extend Hash itself
class Hash
def method_missing sym,*
fetch(sym){fetch(sym.to_s){super}}
end
end
method_missing is a magic method that is called whenever your code tries to call a method that does not exist. Ruby will intercept the failing call at run time and let you handle it so your program can recover gracefully. The implementation above tries to access the hash using the method name as a symbol, the using the method name as a string, and eventually fails with Ruby's built-in method missing error.
NB for a more complex script, where adding this behavior might break other third-party gems, you might alternatively use a module
and extend each instance
module H
def method_missing sym,*
fetch(sym){fetch(sym.to_s){super}}
end
end
the = { answer: 42 }
the.extend(H)
the.answer # => 42
and for greater convenience you can even propagate the module down to
nested hashes
module H
def method_missing sym,*
r = fetch(sym){fetch(sym.to_s){super}}
Hash === r ? r.extend(H) : r
end
end
the = { answer: { is: 42 } }
the.extend(H)
the.answer.is # => 42
If the hash is inside a module, you can define methods on that module (or class) dynamically using define_method. For example:
module Version
module_function
HASH = {
major: 1,
minor: 2,
patch: 3,
}
HASH.each do |name, value|
define_method(name) do
return value
end
end
end
This will define a Version module with major, minor, and patch methods that return 1, 2, and 3, respectively.
you can extend the Hash class in the following way.
class Hash
# return nil whenever the key doesn't exist
def method_missing(m, *opts)
if self.has_key?(m.to_s)
return self[m.to_s]
elsif self.has_key?(m.to_sym)
return self[m.to_sym]
end
return nil
# comment out above line and replace with line below if you want to return an error
# super
end
end
How can I add an instance variable to a defined class at runtime, and later get and set its value from outside of the class?
I'm looking for a metaprogramming solution that allows me to modify the class instance at runtime instead of modifying the source code that originally defined the class. A few of the solutions explain how to declare instance variables in the class definitions, but that is not what I am asking about.
Ruby provides methods for this, instance_variable_get and instance_variable_set. (docs)
You can create and assign a new instance variables like this:
>> foo = Object.new
=> #<Object:0x2aaaaaacc400>
>> foo.instance_variable_set(:#bar, "baz")
=> "baz"
>> foo.inspect
=> #<Object:0x2aaaaaacc400 #bar=\"baz\">
You can use attribute accessors:
class Array
attr_accessor :var
end
Now you can access it via:
array = []
array.var = 123
puts array.var
Note that you can also use attr_reader or attr_writer to define just getters or setters or you can define them manually as such:
class Array
attr_reader :getter_only_method
attr_writer :setter_only_method
# Manual definitions equivalent to using attr_reader/writer/accessor
def var
#var
end
def var=(value)
#var = value
end
end
You can also use singleton methods if you just want it defined on a single instance:
array = []
def array.var
#var
end
def array.var=(value)
#var = value
end
array.var = 123
puts array.var
FYI, in response to the comment on this answer, the singleton method works fine, and the following is proof:
irb(main):001:0> class A
irb(main):002:1> attr_accessor :b
irb(main):003:1> end
=> nil
irb(main):004:0> a = A.new
=> #<A:0x7fbb4b0efe58>
irb(main):005:0> a.b = 1
=> 1
irb(main):006:0> a.b
=> 1
irb(main):007:0> def a.setit=(value)
irb(main):008:1> #b = value
irb(main):009:1> end
=> nil
irb(main):010:0> a.setit = 2
=> 2
irb(main):011:0> a.b
=> 2
irb(main):012:0>
As you can see, the singleton method setit will set the same field, #b, as the one defined using the attr_accessor... so a singleton method is a perfectly valid approach to this question.
#Readonly
If your usage of "class MyObject" is a usage of an open class, then please note you are redefining the initialize method.
In Ruby, there is no such thing as overloading... only overriding, or redefinition... in other words there can only be 1 instance of any given method, so if you redefine it, it is redefined... and the initialize method is no different (even though it is what the new method of Class objects use).
Thus, never redefine an existing method without aliasing it first... at least if you want access to the original definition. And redefining the initialize method of an unknown class may be quite risky.
At any rate, I think I have a much simpler solution for you, which uses the actual metaclass to define singleton methods:
m = MyObject.new
metaclass = class << m; self; end
metaclass.send :attr_accessor, :first, :second
m.first = "first"
m.second = "second"
puts m.first, m.second
You can use both the metaclass and open classes to get even trickier and do something like:
class MyObject
def metaclass
class << self
self
end
end
def define_attributes(hash)
hash.each_pair { |key, value|
metaclass.send :attr_accessor, key
send "#{key}=".to_sym, value
}
end
end
m = MyObject.new
m.define_attributes({ :first => "first", :second => "second" })
The above is basically exposing the metaclass via the "metaclass" method, then using it in define_attributes to dynamically define a bunch of attributes with attr_accessor, and then invoking the attribute setter afterwards with the associated value in the hash.
With Ruby you can get creative and do the same thing many different ways ;-)
FYI, in case you didn't know, using the metaclass as I have done means you are only acting on the given instance of the object. Thus, invoking define_attributes will only define those attributes for that particular instance.
Example:
m1 = MyObject.new
m2 = MyObject.new
m1.define_attributes({:a => 123, :b => 321})
m2.define_attributes({:c => "abc", :d => "zxy"})
puts m1.a, m1.b, m2.c, m2.d # this will work
m1.c = 5 # this will fail because c= is not defined on m1!
m2.a = 5 # this will fail because a= is not defined on m2!
Mike Stone's answer is already quite comprehensive, but I'd like to add a little detail.
You can modify your class at any moment, even after some instance have been created, and get the results you desire. You can try it out in your console:
s1 = 'string 1'
s2 = 'string 2'
class String
attr_accessor :my_var
end
s1.my_var = 'comment #1'
s2.my_var = 'comment 2'
puts s1.my_var, s2.my_var
The other solutions will work perfectly too, but here is an example using define_method, if you are hell bent on not using open classes... it will define the "var" variable for the array class... but note that it is EQUIVALENT to using an open class... the benefit is you can do it for an unknown class (so any object's class, rather than opening a specific class)... also define_method will work inside a method, whereas you cannot open a class within a method.
array = []
array.class.send(:define_method, :var) { #var }
array.class.send(:define_method, :var=) { |value| #var = value }
And here is an example of it's use... note that array2, a DIFFERENT array also has the methods, so if this is not what you want, you probably want singleton methods which I explained in another post.
irb(main):001:0> array = []
=> []
irb(main):002:0> array.class.send(:define_method, :var) { #var }
=> #<Proc:0x00007f289ccb62b0#(irb):2>
irb(main):003:0> array.class.send(:define_method, :var=) { |value| #var = value }
=> #<Proc:0x00007f289cc9fa88#(irb):3>
irb(main):004:0> array.var = 123
=> 123
irb(main):005:0> array.var
=> 123
irb(main):006:0> array2 = []
=> []
irb(main):007:0> array2.var = 321
=> 321
irb(main):008:0> array2.var
=> 321
irb(main):009:0> array.var
=> 123
Readonly, in response to your edit:
Edit: It looks like I need to clarify
that I'm looking for a metaprogramming
solution that allows me to modify the
class instance at runtime instead of
modifying the source code that
originally defined the class. A few of
the solutions explain how to declare
instance variables in the class
definitions, but that is not what I am
asking about. Sorry for the confusion.
I think you don't quite understand the concept of "open classes", which means you can open up a class at any time. For example:
class A
def hello
print "hello "
end
end
class A
def world
puts "world!"
end
end
a = A.new
a.hello
a.world
The above is perfectly valid Ruby code, and the 2 class definitions can be spread across multiple Ruby files. You could use the "define_method" method in the Module object to define a new method on a class instance, but it is equivalent to using open classes.
"Open classes" in Ruby means you can redefine ANY class at ANY point in time... which means add new methods, redefine existing methods, or whatever you want really. It sounds like the "open class" solution really is what you are looking for...
I wrote a gem for this some time ago. It's called "Flexible" and not available via rubygems, but was available via github until yesterday. I deleted it because it was useless for me.
You can do
class Foo
include Flexible
end
f = Foo.new
f.bar = 1
with it without getting any error. So you can set and get instance variables from an object on the fly.
If you are interessted... I could upload the source code to github again. It needs some modification to enable
f.bar?
#=> true
as method for asking the object if a instance variable "bar" is defined or not, but anything else is running.
Kind regards, musicmatze
It looks like all of the previous answers assume that you know what the name of the class that you want to tweak is when you are writing your code. Well, that isn't always true (at least, not for me). I might be iterating over a pile of classes that I want to bestow some variable on (say, to hold some metadata or something). In that case something like this will do the job,
# example classes that we want to tweak
class Foo;end
class Bar;end
klasses = [Foo, Bar]
# iterating over a collection of klasses
klasses.each do |klass|
# #class_eval gets it done
klass.class_eval do
attr_accessor :baz
end
end
# it works
f = Foo.new
f.baz # => nil
f.baz = 'it works' # => "it works"
b = Bar.new
b.baz # => nil
b.baz = 'it still works' # => "it still works"