2

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?
Community
  • 1
  • 1
sherlock
  • 2,397
  • 3
  • 27
  • 44

3 Answers3

3

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' }
fny
  • 31,255
  • 16
  • 96
  • 127
  • Can you please locate `attributes` method in `ActiveRecord`'s [documentation](http://www.rubydoc.info/gems/activerecord/4.2.1)? – sherlock May 19 '15 at 19:33
  • 1
    http://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods.html#method-i-attributes – fny May 19 '15 at 19:38
2

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
Adit Saxena
  • 1,617
  • 15
  • 25
0

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
errata
  • 23,596
  • 2
  • 22
  • 32