Given a Model, how to loop through all properties? - ruby-on-rails

I want to loop through all the properties of my 'user' model, how can I do this?

If you have an instance of your model then user.attributes is a Hash of the model's attributes and their values so, for example, you can do something like:
user.attributes.each_pair do |name, value|
puts "#{name} = #{value}"
end
If you don't have a specific instance then the class has methods that return information about the fields in the database e.g. User.columns and User.content_columns. e.g.
User.columns.each do |column|
puts column.name
end

Article.columns.each do |column|
puts column.name
end
This iterates over all the column objects for the Article model.

#model.methods returns the names of all methods of the object.
#model.methods.grep(/=$/) will return you the names of all write methods, so you can guess that if you have a setter, then you also have a reader, so this may be a "property".
You may also inspect the attributes hash (#model.attributes) which is a Hash with all the columns defined in the database, and this may be the most reliable way, since the "methods" method may not include the attribute readers (and writers) generated dynamically. (It may depend on the version of RubyOnRails you are using).

Related

when to use attributes hash

In this SO answer undefined method `stringify_keys!' ruby on rails, the OP tried to create a new car object with a 'Honda' string to the
#car = Car.new(params[:car])
and got a stringify keys error. The person who answered said that he had to specify the column from the table (in this case the 'name' column) when creating the object ,
create expects to get an attributes hash and to stringify it's keys for the column names.
If you have a column named name in your cars table then try this:
#car = Car.new(:name => params[:car])
However, I'm watching a RailsCast where Rbates creates an entry table with 'name' string and a column 'winner' as a boolean. In his controller (which he's set to respond with json), he does not use an attributes hash. His create action
def create
respond_with Entry.create(params[:entry])
end
Why didn't Rbates have to use an attributes hash, and, if he could have, what would that attributes hash look like? Something like this? Do you have to name every column, in ryans case :name and :winner?
def create
respond_with Entry.create(:name => params[:entry][:name], :winner => params[:entry] [:winner]
end
It all depends on what params[:car] contains. In the Railscast example, params[:car] is a Hash containing two entries (name and winner). In the other SO question it looks like params[:car] was a String containing the name of a car.
Car.new will always expect a Hash. If you want to pass a single value you need to turn it into a Hash with a key that tells Car.new what value you're passing.
In the previous question, params[:car] was a string. Rails's new and create methods both expect hashes, which is why the code was altered to pass in :name => params[:car]
If you use Rails's form_for to construct your forms, params[:model_name] will have a hash which keys matching model attributes. In that case, no custom work is necessary, and simply initializing the model with params[:model_name] works fine.

Non persistent ActiveRecord model attributes

I want to add to an existing model some attributes that need not be persisted, or even mapped to a database column.
Is there a solution to specify such thing ?
Of course use good old ruby's attr_accessor. In your model:
attr_accessor :foo, :bar
You'll be able to do:
object.foo = 'baz'
object.foo #=> 'baz'
I was having the same problem but I needed to bootstrap the model, so the attribute had to persist after to_json was called. You need to do one extra thing for this.
As stated by apneadiving, the easiest way to start is to go to your model and add:
attr_accessor :foo
Then you can assign the attributes you want. But to make the attribute stick you need to change the attributes method. In your model file add this method:
def attributes
super.merge('foo' => self.foo)
end
In case anyone is wondering how to render this to the view, use the method arguments for the render method, like so:
render json: {results: results}, methods: [:my_attribute]
Please know that this only works if you set the attr_accessor on your model and set the attribute in the controller action, as the selected answer explained.
From Rails 5.0 onwards you could use attribute:
class StoreListing < ActiveRecord::Base
attribute :non_persisted
attribute :non_persisted_complex, :integer, default: -1
end
With attribute the attribute will be created just like the ones being persisted, i.e. you can define the type and other options, use it with the create method, etc.
If your DB table contains a matching column it will be persisted because attribute is also used to affect conversion to/from SQL for existing columns.
see: https://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html#method-i-attribute
In my case I wanted to use a left join to populate custom attribute. It works if I don't add anything but I also want to be able to set the attribute on a new object and of course it doesn't exist. If I add attr_accessor then it always returns nil after a select. Here's the approach I've ended up with that works for setting on new object and retrieving from left join.
after_initialize do
self.foo = nil unless #attributes.key?("foo")
end
def foo
#attributes["foo"]
end
def foo=(value)
#attributes["foo"] = value
end

How to enumerate an ActiveRecord object without knowing its attributes

I want to enumerate Active Record data objects like what Hash#each does in Ruby, is this possible?
Each individual object has accessible attributes:
model.attributes.each do |attribute, value|
# ...
end

usage of attr_accessor in Rails

When do you use attr_reader/attr_writer/attr_accessor in Rails models?
Never, unless you have specific need for it. Automatic database-backed accessors are created for you, so you don't need to worry.
Any attr_accessors you do create will change the relevant #attr on the rails object, but this will be lost when the object is destroyed, unless you stick it back in the database. Sometimes you do want this behavior, but it's unusual in a rails app.
Now in ruby, it's a different story, and you end up using these very frequently. But I'd be surprised if you need them in rails---especially initially.
attr_accessor can be used for values you don't want to store in the database directly and that will only exist for the life of the object (e.g. passwords).
attr_reader can be used as one of several alternatives to doing something like this:
def instance_value
"my value"
end
Rails models are just ruby classes that inherit from ActiveRecord::Base. ActiveRecord employs attr_accessors to define getters and setters for the column names that refer to the ruby class's table. It's important to note that this is just for persistence; the models are still just ruby classes.
attr_accessor :foo is simply a shortcut for the following:
def foo=(var)
#foo = var
end
def foo
#foo
end
attr_reader :foo is simply a shortcut for the following:
def foo
#foo
end
attr_writer :foo is a shortcut for the following:
def foo=(var)
#foo = var
end
attr_accessor is a shortcut for the getter and setter while attr_reader is the shortcut for the getter and attr_writer is a shortcut for just the setter.
In rails, ActiveRecord uses these getters and setters in a convenient way to read and write values to the database. BUT, the database is just the persistence layer. You should be free to use attr_accessor and attr_reader as you would any other ruby class to properly compose your business logic. As you need to get and set attributes of your objects outside of what you need to persist to the database, use the attr_s accordingly.
More info:
http://apidock.com/ruby/Module/attr_accessor
http://www.rubyist.net/~slagell/ruby/accessors.html
What is attr_accessor in Ruby?
If you are using it to validate the acceptance of the terms_of_service, you should really consider using validates :terms_of_service, :acceptance => true. It will create a virtual attribute and is much more concise.
http://guides.rubyonrails.org/active_record_validations.html#acceptance.
One example is to have a number of options stored in one serialized column. Form builder would complain if you try to have a text field for one of these options. You can use attr_accessor to fake it, and then in the update action save it in the serialized column.

Custom Named Attributes in Rails

Is it possible in ActiveRecord to customize/override the name of an attribute so that it does not match the column name in the database?
My specific case involves a legacy column, "revision", that I can't remove at this time. The column name conflicts with acts_as_audited. Which of course errors out the legacy code that I need until my migrations are complete.
My desired solution would be to override the attribute name for this column, and update the few areas that call it. Thus allowing the legacy column to live alongside acts_as_audited.
I haven't used acts_as_audited, but I'm assuming its implementation is overriding the accessor for that column. In that case, you should be able to just do something like this:
class ActiveRecord::Base
def self.name_column(column_name, new_name)
define_method(new_name) {read_attribute column_name}
define_method("#{new_name}=") {|value| write_attribute column_name, value}
define_method("#{new_name}?") {attribute_present? column_name}
end
end
These will directly access the column named in column_name without going through the overridden accessor.
Oh, bonus duplication-destroying metaprogramming answer:
class ActiveRecord::Base
def self.name_column(column_name, new_name)
{ '' => :read_attribute,
'=' => :write_attribute,
'?' => :attribute_present? }.each do |suffix,method|
define_method("#{new_name}#{suffix}") {|*args| send method, column_name, *args}
end
end
end
Just because I like to show how it can be done.
Create a migration to rename the column from revision to whatever-you-want.
Then you can declare an attr_accessor :revision and use it without the need to map the attribute to a database field.

Resources