Note that there are some explanatory texts on larger screens.

plurals
  1. POOverride ActiveRecord << operator on has_many :through relationship, to accept data for the join model
    primarykey
    data
    text
    <p>I have three classes: Person, Position, and Directory.</p> <ul> <li>A Person has_many :directories, :through => :position.</li> <li>A Directory has_many :people, :through => :position.</li> <li>Both Person and Directory has_many :positions.</li> <li>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).</li> </ul> <p>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 &lt;&lt; operator won't cut it. Id est:</p> <pre><code>directory = Directory.last # Let's assume that retrieves a Directory object person = Person.last # Let's assume that retrieves a Person object directory.people &lt;&lt; person </code></pre> <p>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:</p> <pre><code>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 =&gt; directory.id, :title =&gt; "Administrative Assistant") position.save </code></pre> <p>This is cumbersome. An equally cumbersome way would be:</p> <pre><code>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") </code></pre> <p>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.</p> <p>What I'd like to be able to do is use the &lt;&lt; operator, and just pass the additional data, e.g.:</p> <pre><code>directory = Directory.last # Let's assume that retrieves a Directory object person = Person.last # Let's assume that retrieves a Person object directory.people &lt;&lt; person, :position =&gt; {:title =&gt; "Administrative Assistant"} </code></pre> <p>I have overloaded the &lt;&lt; operator in my has_many :through declaration, as follows:</p> <pre><code>has_many :people, :through =&gt; :positions do def &lt;&lt; *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 </code></pre> <p>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.</p> <p>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 &lt;&lt; operator is an attribute, in order to build a Position and save it to the database.</p> <p><strong>So, my questions are:</strong></p> <ol> <li>Is there a way to access an object from an attribute of an object?</li> <li>In the alternative, is there another way to overload the &lt;&lt; operator so that I can easily assign data to the join model while adding one object to a collection?</li> </ol> <p>Thanks very much for your help and thoughtful responses. I've been hacking away at this for half a day to no avail.</p> <p><strong>Answer</strong> Thanks to PinnyM, who answered this question, I was able to come up with this implementation:</p> <pre><code>module AddToPeopleAndPositionExtension def &lt;&lt; *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 =&gt; directory.id, :title =&gt; arg[:position][:title]) position.save else raise "Invalid Value" end end end class Directory &lt; ActiveRecord::Base # Relationships has_many :positions has_many :people, :through =&gt; :positions, :extend =&gt; AddToPeopleAndPositionExtension end </code></pre> <p>This allowed me to call the &lt;&lt; operator in the standard way if I did not care what happens on the join model, like:</p> <pre><code>directory = Directory.last # Let's assume that retrieves a Directory object person = Person.last # Let's assume that retrieves a Person object directory.people &lt;&lt; person </code></pre> <p>And, I could also call it in a way that specified attributes of the join model like:</p> <pre><code>directory = Directory.last # Let's assume that retrieves a Directory object person = Person.last # Let's assume that retrieves a Person object directory.people &lt;&lt; {:person =&gt; person, :position =&gt; {:title =&gt; "Administrative Assistant"}} </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload