CRUD, REST and associations

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_tag reader[magazine_ids][]', ‘’ %>

<% for magazine in reader.magazines %> <% end %>
<%= check_box_tag 'reader[magazine_ids][]', magazine.id, @reader.subscribed_to?(magazine) %> <%=h (magazine.title) %>

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 S.
mailto:[email protected]
http://www.schuerig.de/michael/