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.
Related
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.
Given:
class Foo
has_one :bar
def bar_name
bar.name
end
end
class Bar
belongs_to :foo
end
In the console or in a view, I can #foo.bar_name to get 'baz'.
I'm aware that I can #foo.as_json(methods: :bar_name) to get {"id"=>"abc123", "bar_name"=>"baz"}.
I could also denormalize the attribute and make it non-virtual, but I would rather not do that in this case.
Is it possible to automatically return the model with the virtual attribute included?
#<Foo id: "abc123", bar_name: "baz">
I want to do this because I am constructing a large object with nested collections of models, and the as_json call is abstracted away from me.
Not 100% sure I understand if your concern is related to as_json but if so this will work
class Foo
has_one :bar
def bar_name
bar.name
end
def as_json(options={})
super(options.merge!(methods: :bar_name))
end
end
Now a call to #foo.as_json will by default include the bar_name like your explicit example does.
Ugly would not recommend but you could change the inspection of foo e.g. #<Foo id: "abc123", bar_name: "baz"> as follows
class Foo
def inspect
base_string = "#<#{self.class.name}:#{self.object_id} "
fields = self.attributes.map {|k,v| "#{k}: #{v.inspect}"}
fields << "bar_name: #{self.bar_name.inspect}"
base_string << fields.join(", ") << ">"
end
end
Then the "inspection notation" would show that information although I am still unclear if this is your intention and if so why you would want this.
You could use attr_accessor - per the Rails docs:
Defines a named attribute for this module, where the name is symbol.id2name, creating an instance variable (#name) and a corresponding access method to read it. Also creates a method called name= to set the attribute.
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"}]
At the moment I store each option in its own class attribute but this leads to hard to read code when I need to access the passed options from instance methods.
For example if I pass a column name as an option I have to use self.send(self.class.path_finder_column) to get the column value from an instance method.
Notice I have prefixed the class attribute with the name of my plugin to prevent name clashes.
Here is a simple code example of a plugin which is passed an option, column, which is then accessed from the instance method set_path. Can the getters/setters be simplified to be more readable?
# usage: path_find :column => 'path'
module PathFinder
def path_finder(options = {})
send :include, InstanceMethods
# Create class attributes for options
self.cattr_accessor :path_finder_column
self.path_finder_column = options[:column]
module InstanceMethods
def set_path
# setter
self.send(self.class.path_finder_column + '=', 'some value')
# getter
self.send(self.class.path_finder_column)
end
end
end
end
ActiveRecord::Base.send :extend, PathFinder
You can generate all those methods at runtime.
module PathFinder
def path_finder(options = {})
# Create class attributes for options
self.cattr_accessor :path_finder_options
self.path_finder_options = options
class_eval <<-RUBY
def path_finder(value)
self.#{options[:column]} = value
end
def path_finder
self.#{options[:column]}
end
RUBY
end
end
ActiveRecord::Base.send :extend, PathFinder
Unless you need to store the options, you can also delete the lines
self.cattr_accessor :path_finder_options
self.path_finder_options = options
Note that my solution doesn't need a setter and a getter as long as you always use path_finder and path_finder=.
So, the shortest solution is (assuming only the :column option and no other requirements)
module PathFinder
def path_finder(options = {})
# here more logic
# ...
class_eval <<-RUBY
def path_finder(value)
self.#{options[:column]} = value
end
def path_finder
self.#{options[:column]}
end
RUBY
end
end
ActiveRecord::Base.send :extend, PathFinder
This approach is similar to the one adopted by acts_as_list and acts_as_tree.
To start with cattr_accessor creates a class variable for each symbol it's given. In ruby, class variables have their names prefixed with ##.
So you can use ##path_finder_column in place of self.class.path_finder_column.
However that's a moot point considering what I'm going to suggest next.
In the specific case presented by the code in the question. The combination getter and setter you've defined doesn't fit ruby conventions. Seeing as how you're essentially rebranding the accessors generated for the path_finder_column with a generic name, you can reduce it all to just a pair of aliases.
Assuming there's an error in the combo accessor (how is the code supposed to know whether to get or set), The finalized module will look like this:
module PathFinder
def path_finder(options = {})
send :include, InstanceMethods
# Create class attributes for options
self.cattr_accessor :path_finder_column
self.path_finder_column = options[:column]
alias :set_path, path_finder_column
alias :set_path=, "#{path_finder_column}="
end
module InstanceMethods
# other instance methods here.
end
end
You can use cattr_accessor to store the configuration value at a class level and use in all your instance methods. You can see an example at http://github.com/smsohan/acts_as_permalinkable/blob/master/lib/active_record/acts/permalinkable.rb
The code to look at is this:
def acts_as_permalinkable(options = {})
send :cattr_accessor, :permalink_options
self.permalink_options = { :permalink_method => :name, :permalink_field_name => :permalink, :length => 200 }
self.permalink_options.update(options) if options.is_a?(Hash)
send :include, InstanceMethods
send :after_create, :generate_permalink
end
Hope it helps!