user1032752
user1032752

Reputation: 871

Rails - Manipulate has_many with join model through single form

I have the following three models:

class Site < AR::Base
  has_many :installed_templates, :as => :installed_templateable, :dependent => :destroy

  accepts_nested_attributes_for :installed_templates, :allow_destroy => true, :update_only => true, :reject_if => lambda { |t| t[:template_id].nil? }
 end

class Template < AR::Base
  has_many :installed_templates, :inverse_of => :template
end

class InstalledTemplate < AR::Base
  belongs_to :template, :inverse_of => :installed_template
  belongs_to :installed_templateable, :polymorphic => true
end

The business logic is that several Templates exist when I create a Site and I can associate as many as I'd like by creating an InstalledTemplate for each. A Site can only have unique Templates - I can't associate the same Template twice to a single Site.

I have the following on the form for Site:

<% Template.all.each_with_index do |template| %>
  <%= hidden_field_tag "site[installed_templates_attributes][][id]", Hash[@site.installed_templates.map { |i| [i.template_id, i.id] }][template.id] %>
  <%= check_box_tag "site[installed_templates_attributes][]]template_id]", template.id, (@site.installed_templates.map(&:template_id).include?(template.id) %>
  <%= label_tag "template_#{template.id}", template.name %>
<% end %>

The above is the only thing that seems to work after lots of experimentation. I wasn't able to achieve this at all using the form_for and fields_for helpers.

It seems like a pretty straight forward relationship and I'm afraid I'm missing something. Anyone have advice on how to accomplish the above in a cleaner fashion?

Thanks

Upvotes: 1

Views: 170

Answers (3)

Krishna Rani Sahoo
Krishna Rani Sahoo

Reputation: 1539

Try the following

<% form_for @site do |f| %>

  <%f.fields_for :installed_templates do |af|%>

    <%= af.text_field :name %>
  <%end%>
<%end%>

Upvotes: 3

Mike Hefferan
Mike Hefferan

Reputation: 76

I think you are trying to do two different things here.

  1. Select a list of Templates for which you want to create Installed Template joins to the Site.
  2. From each selected Template create an instance of an Installed Template which links to the Site.

I think I would handle this through an accessor on the model.

On the Site Model (assuming standard rails naming conventions)

attr_accessor :template_ids

def template_ids
  installed_templates.collect{|i_t| i_t.template.id}
end

def template_ids=(ids, *args)
  ids.each{|i| i.installed_template.build(:site_id => self.id, :template_id => i) }
end

Then the form becomes pretty simple

<% form_for @site do |f| %>
  <% f.collection_select :template_ids, Template.all, :id, :name, {}, :multiple => true %>
<% end %>

Upvotes: 1

davidfurber
davidfurber

Reputation: 5518

Is a join model necessary here?

class Site < ActiveRecord::Base
  has_and_belongs_to_many :templates
end

In the form, easiest if using simple_form:

<%= form.input :template_ids, :as => :radio_buttons, :collection => Template.order(:name), :label => 'Templates installed:' %>

If the join model is necessary, then I would have a dropdown or list of templates I could add, each with a button that submits the form and adds that template. Then I'd use the update_only nested attributes form to display the currently installed templates with their settings.

class Site < ActiveRecord::Base
  ...
  attr_accessor :install_template_id
  before_validation :check_for_newly_installed_template
  def check_for_newly_installed_template
    if install_template_id.present?
      template = Template.find install_template_id
      if template.present? and not templates.include?(template)
        self.templates << template
      end
    end
  end
end

That works kind of like Drupal, where you have to enable the theme before you can edit its settings or select it as the current theme.

Upvotes: 1

Related Questions