Predefined models? No different than any others.

Earlier today I was trying to figure out how to make a list of potential models to be created. For example: I have some Users and some Groups. I have a many-to-many Membership join model with some has_many :throughs. The fundamental goal is to provide the user an interface to add Membership models that link a User to a Group. I could use a bunch of checkboxes, but I’d rather pull up a list of possible Groups with little “add” links next to them.

Since I’m using RESTful controllers, we have a MembershipsController that implements all the standard methods. We’re also nesting our routes such that MembershipsController nested beneath the UsersController. In order to create the Membership, we need to POST to /users/1/memberships. But how do we get a list of Memberships that we can quickly add?

How about modifying MembershipsController.new? We don’t need the normal definition of new, since we’re never going to be manually creating a new Membership.

# controllers/memberships_controller.rb
def new
  @memberships = Groups.find(:all).collect do |group|
    Membership.new(:user_id => params[:user_id], :group_id => group.id)
  end
end

Now we have a list of potential Membership objects available to our view. Remember, the Memberships haven’t been saved yet. They’re just there for convenience for holding attributes. We are doing object-oriented programming after all.

# views/memberships/new.html.erb
<% @memberships.each do |membership| %>
	<% form_for :membership, membership, :url => memberships_path do |f| %>
		<%= f.hidden_field :user_id %>
		<%= f.hidden_field :group_id %>
		<%= f.submit membership.group.name %>
	<% end %>
<% end %>

Basically what we have here is a big list of predefined join models wrapped up in form tags. When you click on one of them, you’ll end up submitting the form that actually creates the model. Eventually, we could make this into an AJAX widget that uses form_remote_for with very little effort.

Nothing I have described here is particularly revolutionary. Rather than just returning a single new Membership in the new method, we return a list of objects. Rather than rendering one form with editable fields, we render multiple forms with predefined, hidden fields. Whichever you submit creates the corresponding object. These two very simple changes to the standard REST actions allow us to easily and elegantly create our join models.

Brandon Dimcheff
Brandon Dimcheff
Chief Architect

Brandon Dimcheff is a software engineer born and raised in Ann Arbor, Michigan. He uses go for his day job, has fallen in love with Kubernetes, is an aspiring functional programming language nerd, and is an advocate of open source.