Let''s say I have the model class Reader and Magazine, connected by join
model Subscription. It looks something like this
class Reader < ActiveRecord::Base
has_many :subscriptions, :dependent => :delete_all
has_many :magazines, :through => :subscriptions
validates_presence_of :name
end
class Magazine < ActiveRecord::Base
has_many :subscriptions, :dependent => :delete_all
has_many :readers, :through => :subscriptions
end
class Subscription < ActiveRecord::Base
belongs_to :reader
belongs_to :magazine
validates_presence_of :reader, :magazine
end
Now, the nascent RESTful orthodoxy appears to be that for each of these
model classes there ought to be a controller that manages its CRUD
operations. I admit that this has a nice ring to it, alas, it doesn''t
seem to mesh with what I''d like to do user interface-wise. Simply put,
I want to edit and update a reader''s name *and* their subscriptions in
one form. The point is that the user saves (or discards) these changes
in a single user-level transaction. What I don''t want is that changes
to subscriptions are saved immediately, whereas changes to attributes,
such as name, are change only on submitting the form. Also, I don''t
want to spread editing of attributes and editing of subscriptions over
separate views.
So, within the editing form (_form.rhtml when spit out by the scaffold
generator), I display a table of checkboxes and magazines where
subscriptions are shown and can''t be changed.
<!-- hidden field, to get a request param, even if no checkbox is
selected -->
<%= hidden_field_tag reader[magazine_ids][]'', ''''
%>
<table>
<tr>
<td></td>
</tr>
<% for magazine in reader.magazines %>
<tr>
<td><%= check_box_tag ''reader[magazine_ids][]'',
magazine.id,
@reader.subscribed_to?(magazine) %></td>
<td><%=h (magazine.title) %></td>
</tr>
<% end %>
</table>
As the Rails API docs state, has_many :through associations are
read-only. Instead, changes are handled through the join model. So,
let''s ameliorate this shortcoming
class Reader
def magazine_ids=(ids)
Reader.transaction do
subscriptions.clear
ids.each do |magazine_id|
subscription.create(:reader_id => id, :magazine_id => _id)
unless magazine_id.blank?
magazines(true) # force a reload
end
end
end
def subscribed_to?(magazine)
subscriptions.any? { |s| s.magazine_id == magazine.id }
end
end
Okay, this way it works. Still, surprisingly, it is more effort than
could be expected as Rails doesn''t support this behavior out of the box
with a suitable helper and <association_singular_name>_ids= methods.
This *may* be an indication that something is wrong. Is it? Is there a
better way, more in keeping with CRUD and REST to achieve the same
ends?
Michael
--
Michael Schuerig
mailto:michael@schuerig.de
http://www.schuerig.de/michael/