Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Override ActiveRecord << operator on has_many :through relationship, to accept data for the join model

I have three classes: Person, Position, and Directory.

  • A Person has_many :directories, :through => :position.
  • A Directory has_many :people, :through => :position.
  • Both Person and Directory has_many :positions.
  • The Position model, in addition to having an id, a person_id, and a directory_id, has one or more additional fields (e.g., title).

What I would like to be able to do is add data to the join model, such as the title field, every time I add a person to a Directory.people collection. The usual << operator won't cut it. Id est:

directory = Directory.last     # Let's assume that retrieves a Directory object
person = Person.last           # Let's assume that retrieves a Person object
directory.people << person

This will add a person to the people collection of a Directory object, but will not allow me the chance to assign data to the join model. So, after doing a lot of research on this site, I found another way to add the Person to the people collection and add data to the Position that links the Person and the Directory, id est:

directory = Directory.last     # Let's assume that retrieves a Directory object
person = Person.last           # Let's assume that retrieves a Person object
position = person.positions.build(:directory_id => directory.id, :title => "Administrative Assistant") 
position.save

This is cumbersome. An equally cumbersome way would be:

directory = Directory.last     # Let's assume that retrieves a Directory object
person = Person.last           # Let's assume that retrieves a Person object
position = Position.new(directory_id: directory.id, person_id: person.id, title: "Administrative Assistant")

Again, seems wrong because I'd like to be able to emphasize the relationship between Person and Directory, which I believe is what makes using has_many :through appropriate.

What I'd like to be able to do is use the << operator, and just pass the additional data, e.g.:

directory = Directory.last # Let's assume that retrieves a Directory object
person = Person.last # Let's assume that retrieves a Person object
directory.people << person, :position => {:title => "Administrative Assistant"}

I have overloaded the << operator in my has_many :through declaration, as follows:

has_many :people, :through => :positions do
  def << *args
    arg = args.first
    if arg.is_a?(Person)
      self.push([arg]) 
    elsif arg.is_a?(Hash)
      # Don't know what to do in here (see below)
    else
      raise "Invalid Value" # There's a better error to raise here, but good enough for now.
    end
  end
end

The advantage of getting this working is that it works well syntactically, and allows me to concisely assign data to the join object (a Position) while adding the Person to the people collection of the Directory object.

But I cannot make it work because I would need to be able to access the Directory object of which the people collection on the left side of the << operator is an attribute, in order to build a Position and save it to the database.

So, my questions are:

  1. Is there a way to access an object from an attribute of an object?
  2. In the alternative, is there another way to overload the << operator so that I can easily assign data to the join model while adding one object to a collection?

Thanks very much for your help and thoughtful responses. I've been hacking away at this for half a day to no avail.

Answer Thanks to PinnyM, who answered this question, I was able to come up with this implementation:

module AddToPeopleAndPositionExtension
  def << *args
    arg = args.first
      if arg.is_a?(Person)
        self.push([arg]) 
        return self
      elsif arg.is_a?(Hash)
        directory = proxy_association.owner
        person = arg[:person]
        position = person.positions.build(:directory_id => directory.id, :title => arg[:position][:title]) 
        position.save
      else
        raise "Invalid Value"
      end
  end
end

class Directory < ActiveRecord::Base  
  # Relationships
  has_many :positions
  has_many :people, :through => :positions, :extend => AddToPeopleAndPositionExtension
end

This allowed me to call the << operator in the standard way if I did not care what happens on the join model, like:

directory = Directory.last     # Let's assume that retrieves a Directory object
person = Person.last           # Let's assume that retrieves a Person object
directory.people << person

And, I could also call it in a way that specified attributes of the join model like:

directory = Directory.last     # Let's assume that retrieves a Directory object
person = Person.last           # Let's assume that retrieves a Person object
directory.people << {:person => person, :position => {:title => "Administrative Assistant"}}
like image 983
Morris Singer Avatar asked Oct 11 '12 19:10

Morris Singer


People also ask

What is ActiveRecord in Ruby on Rails?

What is ActiveRecord? ActiveRecord is an ORM. It's a layer of Ruby code that runs between your database and your logic code. When you need to make changes to the database, you'll write Ruby code, and then run "migrations" which makes the actual changes to the database.

What is has_ many?

A has_many association is similar to has_one , but indicates a one-to-many connection with another model. You'll often find this association on the "other side" of a belongs_to association. This association indicates that each instance of the model has zero or more instances of another model.

What is dependent destroy in Rails?

what is dependent :destroy. Dependent is an option of Rails collection association declaration to cascade the delete action. The :destroy is to cause the associated object to also be destroyed when its owner is destroyed.


1 Answers

You can use the proxy_association helper inside the block to get the association and proxy_association.owner to get the Directory object itself. See here for some more info on this.

like image 189
PinnyM Avatar answered Oct 21 '22 17:10

PinnyM