I have an object Foo and want to assign multiple attributes to it at once, similar to assign_attributes in Rails:
class Foo
attr_accessor :a, :b, :c
end
f = Foo.new
my_hash = {a: "foo", b: "bar", c: "baz"}
f.assign_attributes(my_hash)
The above does not work except if the class is an ActiveRecord Model in Rails. Is there any way to do it in Ruby?
You can implement the mass-assignment method yourself.
One option is to set the corresponding instance variables via instance_variable_set:
class Foo
attr_accessor :a, :b, :c
def assign_attributes(attrs)
attrs.each_pair do |attr, value|
instance_variable_set("##{attr}", value)
end
end
end
Note that this will by-pass any custom setters. As said in the docs:
This may circumvent the encapsulation intended by the author of the class, so it should be used with care.
Another way is to dynamically invoke the setters via public_send:
def assign_attributes(attrs)
attrs.each_pair do |attr, value|
public_send("#{attr}=", value)
end
end
This is equivalent to setting each single attribute sequentially. If a setter has been (re)defined to include constraints and controls on the value being set, the latter approach respects that.
It also raises an exception if you try to set undefined attributes: (because the corresponding setter doesn't exist)
f = Foo.new
f.assign_attributes(d: 'qux')
#=> NoMehodError: undefined method `d=' for #<Foo:0x00007fbb76038430>
In addition, you might want to ensure that the passed argument is indeed a hash and maybe raise a custom exception if the provided attributes are invalid / unknown.
The assign_attributes is an instance method of ActiveRecord
You have to define your assign_attributes method if not using ActiveRecord
def assign_attributes(attrs_hash)
attrs_hash.each do |k, v|
self.instance_variable_set("##{k}", v)
end
end
Related
I have a class with (as example) 3 attributes ,I want to convert the class's attribute to an array ,so I can store them into my csv file.
class Human
attr_accessor :name,:Lname,:id
....
end
when I create :
human1=Human.new("nice","human",1)
I need a function that return ["nice","human",1].
Is there a predefined one that I didn't find or I have to redefine to_a so it does the job.
note: the class has more than 3 attribute
is there a function to go through the object attribute or not.
I need a function that return ["nice","human",1]
Creating such method is trivial. If it is specifically for CSV, I would name it accordingly, e.g.:
class Human
attr_accessor :name, :lname, :id
# ...
def to_csv
[name, lname, id]
end
end
To generate a CSV:
require 'csv'
human1 = Human.new("nice", "human", 1)
csv_string = CSV.generate do |csv|
csv << ['name', 'lname', 'id']
csv << human1.to_csv
end
puts csv_string
# name,lname,id
# nice,human,1
Note that I've renamed Lname to lname in the above example. Uppercase is reserved for constants.
is there a function to go through the object attribute or not?
No, there is no built in way to actually do what you want and you might be falling for a common beginner trap.
attr_accessor does not "define attributes" since Ruby doesn't actually have properties/attributes/members like other langauges do. It defines a setter and getter method for an instance variable. Ruby doesn't keep track of which properties an object is presumed to have - only the actual instance variables which have been set.
But Ruby does provide the basic building blocks to make any kind of attributes system you want. This is very simplefied (and quite rubbish) example:
class Human
# this is a class instance variable
#attributes = []
# a class method that we use for "defining attributes"
def self.attribute(name)
attr_accessor name
#attributes << name
end
attribute(:name)
attribute(:l_name)
attribute(:id)
def initialize(**kwargs)
kwargs.each {|k,v| send("#{k}=", v) }
end
# the attributes that are defined for this class
def self.attributes
#attributes
end
# cast a human to an array
def to_a
self.class.attributes.map{ |attr| send(attr) }
end
# cast a human to an hash
def to_h
self.class.attributes.each_with_object({}) do |attr, hash|
hash[attr] = send(attr)
end
end
end
jd = Human.new(
name: 'John',
l_name: 'Doe',
id: 1
)
jd.to_a # ['John', Doe, 1]
jd.to_h # {:name=>"John", :l_name=>"Doe", :id=>1}
Here we are creating a class method attribute that adds the names of the "attributes" to a class instance variable as they are declared. Thus the class "knows" what attributes it has. It then uses attr_accessor to create the setter and getter as usual.
When we are "extracting" the attributes (to_a and to_h) we use the list we have defined in the class to call each corresponding setter.
Usually this kind functionality would go into a module or a base class and not the actual classes that represent your buisness logic. For example Rails provides this kind of functionality through ActiveModel::Attributes and ActiveRecord::Attributes.
I have a Rails application with a recurring need for setting default attributes. Sometimes the user will supply values for the attributes which will be respected, but in other circumstances either the model or the user might desire that these attributes are overridden with default values disregarding original values.
I guessed that this problem called for a banged (!) and non-banged method for setting default values allowing the user and program to switch to the appropriate state. The non-banged setter will only set default values when they are not nil whilst the banged version will always overwrite the attributes with defaults. The difference is minor:
class BangDiBang
attr_accessor :value
def set_default
self.value ||= do_some_suff_to_determine_default_value
end
def set_default!
self.value = do_some_suff_to_determine_default_value
end
...
end
The issue with this code is that if I had a bunch of variables to set, I would end up repeating the same code twice for each variable.
My question is how to partial out this code? Saving the logic in one method and having two methods set_value and set_value! calling the central logic with the different assignment operators.
I have conjured one solution: write the central logic as text, replace the assignment operation from the setter methods and evaluate (but this does not feel right). How do I not repeat myself?
The way you're going about this isn't going to work for multiple params since you're calling set_default but not specifying which variable. You'll want to define behavior for each variable, ideally. There are libraries that handle this sort of thing, but you can roll your own pretty easily:
class Example
def self.default_param(name, default_value)
define_method("default_#{name}") { default_value }
define_method(name) do
instance_variable_get("##{name}") || default_value
end
attr_writer name
end
default_param :foo, 'foo default'
end
ex = Example.new
ex.foo #=> "foo default"
ex.foo = 'bar'
ex.foo #=> "bar"
ex.default_foo #=> "foo default"
I've renamed set_default and set_default! to be more clear: for each variable with a default value, three methods are created (example using foo as the variable name):
foo — returns the value of #foo if it is truthy, and default_foo otherwise
foo= — sets the value of #foo
default_foo — returns the specified default
You could compartmentalize and dry up some of the code above further, creating a default_params (plural) method to take a hash, extracting the class macro to a concern:
module DefaultParams
def default_param(name, default_value)
define_method("default_#{name}") { default_value }
define_method(name) do
instance_variable_get("##{name}") || default_value
end
attr_writer name
end
def default_params(params)
params.each { |default| default_param(*default) }
end
end
class Example
extend DefaultParams
default_params foo: 'default foo', bar: 'my favorite bar'
end
I have implemented a solution with great inspiration from coreyward. I did not realize at first that rather than having two methods for setting an array of default values, I needed the default values separated out into single methods. This gives a great flexibility to the design of the application. So thanks a lot for the answer.
For the rails setup I’ve added to application_record.rb
def set_attr_from_defaults
default_attrs.each do |atr|
eval("self.#{atr[0..atr.length-9]} ||= self.#{atr}")
end
end
def set_attr_from_defaults!
default_attrs.each do |atr|
eval("self.#{atr[0..atr.length-9]} = self.#{atr}")
end
end
def set_default_attr params
params.each { |key, value| self.define_singleton_method(key){value} }
end
def default_attrs
self.attributes.keys.map{|i| (i+'_default').to_sym} & self.methods
end
default_attrs yields a list of symbols off default attributes. set_default_attr defines singleton methods, with intended use of parsing in a hash like attrname_default: 'default_value' .... The set_attr_from_defaults will set attributes from default values, when an attribute has a pair like var: var_default. The number 9 is the length of _default + 1.
I have an ActiveRecord model instance where I want to modify attributes where some attributes exist on an association rather than directly on the model itself. All of the attributes come in as a hash.
This code is part of my test suite so I'm not concerned about issues of mass attribute assignment in this case.
If I try to update using instance.update_attributes(hash) this fails.
As an alternative I tried looping over the hash and setting the attributes like this.
hash.each do |key, val|
instance[key] = val
end
This method will set attributes that exist directly on the instance, but throws an exception on attributes that are tied to associated records ActiveModel::MissingAttributeError Exception: can't write unknown attribute 'foo'
Give the delegate approach a go. Something along the lines of:
class Foo
attr_accessor :bar, :foo_attribute
delegate :bar_attribute=, to: :bar
def initialize
#bar = Bar.new
end
end
class Bar
attr_accessor :bar_attribute
end
Then, in the console:
hsh = {foo_attribute: 'foo', bar_attribute: 'bar'}
f = Foo.new
hsh.each do |k,v|
f.send("#{k}=",v)
end
f.inspect
=> "#<Foo:0x00000004f1f0f0 #bar=#<Bar:0x00000004f1f0c8 #bar_attribute=\"bar\">, #foo_attribute=\"foo\">"
f.bar.inspect
=> "#<Bar:0x00000004f1f0c8 #bar_attribute=\"bar\">"
I have a several classes, each of which define various statistics.
class MonthlyStat
attr_accessor :cost, :size_in_meters
end
class DailyStat
attr_accessor :cost, :weight
end
I want to create a decorator/presenter for a collection of these objects, that lets me easily access aggregate information about each collection, for example:
class YearDecorator
attr_accessor :objs
def self.[]= *objs
new objs
end
def initialize objs
self.objs = objs
define_helpers
end
def define_helpers
if o=objs.first # assume all objects are the same
o.instance_methods.each do |method_name|
# sums :cost, :size_in_meters, :weight etc
define_method "yearly_#{method_name}_sum" do
objs.inject(0){|o,sum| sum += o.send(method_name)}
end
end
end
end
end
YearDecorator[mstat1, mstat2].yearly_cost_sum
Unfortunately define method isn't available from within an instance method.
Replacing this with:
class << self
define_method "yearly_#{method_name}_sum" do
objs.inject(0){|o,sum| sum += o.send(method_name)}
end
end
...also fails because the variables method_name and objs which are defined in the instance are no longer available. Is there an idomatic was to accomplish this in ruby?
(EDITED: I get what you're trying to do now.)
Well, I tried the same approaches that you probably did, but ended up having to use eval
class Foo
METHOD_NAMES = [:foo]
def def_foo
METHOD_NAMES.each { |method_name|
eval <<-EOF
def self.#{method_name}
\"#{method_name}\".capitalize
end
EOF
}
end
end
foo=Foo.new
foo.def_foo
p foo.foo # => "Foo"
f2 = Foo.new
p f2.foo # => "undefined method 'foo'..."
I myself will admit it's not the most elegant solution (may not even be the most idiomatic) but I've run into similar situations in the past where the most blunt approach that worked was eval.
I'm curious what you're getting for o.instance_methods. This is a class-level method and isn't generally available on instances of objects, which from what I can tell, is what you're dealing with here.
Anyway, you probably are looking for method_missing, which will define the method dynamically the first time you call it, and will let you send :define_method to the object's class. You don't need to redefine the same instance methods every time you instantiate a new object, so method_missing will allow you to alter the class at runtime only if the called method hasn't already been defined.
Since you're expecting the name of a method from your other classes surrounded by some pattern (i.e., yearly_base_sum would correspond to a base method), I'd recommend writing a method that returns a matching pattern if it finds one. Note: this would NOT involve making a list of methods on the other class - you should still rely on the built-in NoMethodError for cases when one of your objects doesn't know how to respond to message you send it. This keeps your API a bit more flexible, and would be useful in cases where your stats classes might also be modified at runtime.
def method_missing(name, *args, &block)
method_name = matching_method_name(name)
if method_name
self.class.send :define_method, name do |*args|
objs.inject(0) {|obj, sum| sum + obj.send(method_name)}
end
send name, *args, &block
else
super(name, *args, &block)
end
end
def matching_method_name(name)
# ... this part's up to you
end
The simple way to loop over all attributes of an object when using Active Record is
order_item_object.attributes.each do |key,value|
....
end
But this doesn't work, when we are not using Active Record. How can I iterate over all the attributes of an object then?
For eg -:
I have a model in Rails which does not use active record.
The object from model order_item can be used in the controller like order_item_object.product_id, order_item_object.quantity, order_item_object.quoted_price. But when I try to order_item_object.attributes.each do |k,v| ...., I get undefined method "attributes" for #<Order:0x00000005aa81b0>
How should I go about this?
try this:
class Parent
def self.attr_accessor(*vars)
#attributes ||= []
#attributes.concat vars
super(*vars)
end
def self.attributes
#attributes
end
def attributes
self.class.attributes
end
end
class ChildClass < Parent
attr_accessor :id, :title, :body
end
p ChildClass.new.attributes.inspect #=> [:id, :title, :body]
attr_accessor is simply a macro that creates some methods to set an instance variable. SO perhaps what you want is the instance_variables method, which returns an array of instance variables that you can iterate through
class Foo
attr_accessor :bar
attr_accessor :baz
end
foo = Foo.new
foo.bar = 123
foo.baz
foo.instance_variables.each do |ivar_name|
ivar_value = foo.instance_variable_get ivar_name
# do something with ivar_name and ivar_value
end
But I wouldn't really recommend this. ActiveRecord keeps model data separate for a reason. Your instance may have lots of uses for instance variables. For instance, perhaps you want to track if the record is saved yet or not. This may be kept in #saved variable, which reflects the state of the instance but not the data of the model.
Perhaps you want to keep a hash of attributes instead?
This is the best way I found
item=self.instance_values.symbolize_keys
item.each do |k,v|
...
..
end
There is a code to show it's usage here (look at the update in the question itself) - Grouping via an element in hash
If you want the string representations of your instance variables, you can do:
self.instance_values.keys.each do |k|
..
end
That should loop over all the keys (instance variable names as strings) that are defined for your class.
The attributes you are referencing is an instance variable on ActiveRecord models which returns a hash of the models db attributes.
A non-ActiveRecord model won't have the attributes method you are calling unless you defined it explicitly. All objects have a method called instance_variables which you can use to get a list of attribute names and then reflectively find the attribute values.
A not very elegant way of doing it:
class Test
attr_accessor :firstname,:lastname
def initialize(fn,ln); #firstname = fn; #lastname = ln; end
end
o = Test.new("Fernando", "Mendez")
o.instance_variables.each {|e| p o.send e.to_s.sub("#","").to_sym}
output:
"Fernando"
"Mendez"
If you want to keep a track of the key-value pair:
r = o.instance_variables.map {|e| Hash[e,(o.send e.to_s.sub("#","").to_sym)]}
#[{:#firstname=>"Fernando"}, {:#lastname=>"Mendez"}]