Rails 4 nested form issue - ruby-on-rails

Hi I'm using the nested form plugin and trying to make it work for rails 4 instead of rails 3. Basically my model looks like this:
has_many :item, :dependent => :destroy
accepts_nested_attributes_for :item, :reject_if => lambda { |a| a[:item].blank? }, :allow_destroy => true
and my view looks like this:
<%= nested_form_for(#store) do |f| %>
<%= f.fields_for :item do |item_form| %>
<%= item_form.text_field :name %>
<%= item_form.link_to_remove "Remove this item" %>
<% end %>
<% end %>
this works (in terms of presentation - you can add and delete fields like you should be able to) but doesn't save the item names.
I tried this in my controller (these are the protected attributes/params):
def store_params
params.require(:store).permit(:name, :owner, :description,:url, :user, item_attributes)
end
but it still comes up with:
Unpermitted parameters: item_attributes
Thanks for all help!

You're going to have to permit the fields of item (like name) to be allowed as well.
So try this:
def store_params
params.require(:store).permit(:name, :owner, :description,:url, :user, item_attributes: [:name])
end

sometimes you have to specify the :id like this:
def store_params
params.require(:store).permit(:name, :owner, :description,:url, :user, item_attributes: [:id, :name])
end
in a similar case I had last week, not specifying the :id made Rails 4 create an new entity instead of updating the existing one.

Related

Auto set value of nested attributes in Rails

I have a model location which has many messages. I am trying to let users who are browsing locations to send messages to location owners.
I have location.rb
has_many :messages
accepts_nested_attributes_for :messages
and message.rb
belongs_to :location
In locations_controller.rb
def location_params
params.require(:location).permit(:name,
:user_id,
:image,
:latitude,
:longitude,
location_images_attributes: [:id, :location_id, :location_image, :_destroy],
messages_attributes: [:id, :location_id, :from_email, :to_email, :content])
end
Currently i have in my view the following code:
<%= simple_form_for #location do |l| %>
<%= l.simple_fields_for :messages, #location.messages.build do |m| %>
<%= m.input :content %>
<%= m.input :to_email, :input_html => {:value => #location.user.email}, as: :hidden %>
<% end %>
<%= l.button :submit %>
<% end %>
I don't want to set the value of the email field via hidden field, but i want to pass value from controller. Or a model. Please advise.
Maybe try this approach in your Location model:
class Location < ActiveRecord::Base
has_many :messages, after_add: :set_email
accepts_nested_attributes_for :messages
def set_email(message)
message.to_email = user.email
end
end
Basically you have to register a method for Location, when new message is added to messages set.
In this example I named it set_email, which takes the message object as it's argument, and you can freely modify it. I'm just setting the to_email based on location's email.
Hope this solves your problem!
You could do something like this in the Message model:
before_create :set_to_email
def set_to_email
self.to_email = self.location.user.email
end
Downside is that on every create action a few additional database lookups are being performed. So on the grand scale this is not an ideal solution in terms of performance optimization.

How to insert nested form values into DB

I am using Rails 4.
In my project, include nested form for has_many relationship. From UI point of view, I got it. But nested form values are not inserting into database.
class Newspaper < ActiveRecord::Base
has_to :newspaper_categories, :dependent_destroy => true
accepts_nested_attributes_for :newspaper_categories, :allow_destroy => true, :reject_if => :all_blank
end
class NewspaperCategory < ActiveRecord::Base
belongs_to :newspaper
end
Newspaper form contents like,
<%= nested_form_for(#newspaper) do |f| %>
# Newspaper form fields
# Include `Newspaper category` form from the file.
<%= f.fields_for :newspaper_categories do |nc|%>
<%= render "newspaper_category" %>
<% end %>
# For add new form using JS
<%= f.link_to_add "Add New", :newspaper_categories %>
<%= f.submit %>
<% end %>
In my Newspaper Controller,
# add build in new method,
def new
#newspaper = Newspaper.new
#newspaper.newspaper_categoried.build
end
# In params set task_attributes,
def newspaper_params
params.require(:newspaper).permit(:name, :logo, task_attributes[:cat_link, :_destroy])
end
Where I goes wrong, still i'm confusing to insert
Update this
params.require(:newspaper).permit(:name, :logo, {newspaper_categories_attributes: [ :_destroy, :category_id, :rss_link, :image_url]})

How do I save this nested form in Rails 4 with a has many through association?

The problem that I have here is that I have a nested form that won't save to the database and I'm suspecting it's because the proper attributes aren't being passed into the form prior to being saved. Right now I'm trying to pass these values through hidden fields but I'm thinking there's probably a more "Railsy" way to do this. This is the form that I have created to do this:
<%= form_for #topic do |f| %>
<%= render "shared/error_messages", object: f.object %>
<%= f.fields_for :subject do |s| %>
<%= s.label :name, "Subject" %>
<%= collection_select :subject, :id, Subject.all, :id, :name, {prompt:"Select a subject"} %>
<% end %>
<%= f.label :name, "Topic" %>
<%= f.text_field :name %>
<div class="text-center"><%= f.submit class: "button radius" %></div>
<% end %>
This form generates a params hash that looks like this:
{"utf8"=>"✓", "authenticity_token"=>"PdxVyZa3X7Sc6mjjQy1at/Ri7NpR4IPUzW09Fs8I710=", "subject"=>{"id"=>"5"}, "topic"=>{"name"=>"Ruby"}, "commit"=>"Create Topic", "action"=>"create", "controller"=>"topics"}
This my model for user.rb:
class User < ActiveRecord::Base
has_many :topics
has_many :subjects, through: :topics
end
In my subject.rb file:
class Subject < ActiveRecord::Base
has_many :topics
has_many :users, through: :topics, dependent: :destroy
validates :name, presence: true
end
In my topic.rb file:
class Topic < ActiveRecord::Base
belongs_to :subject
belongs_to :user
accepts_nested_attributes_for :subject
validates :name, presence: true
end
class TopicsController < ApplicationController
before_filter :require_login
def new
#topic = Topic.new
#topic.build_subject
end
def create
#topic = Topic.new(topic_params)
#topic.user_id = current_user.id
#topic.subject_id = params[:subject][:id]
if #topic.save
flash[:success] = "Success!"
render :new
else
flash[:error] = "Error!"
render :new
end
end
private
def topic_params
params.require(:topic).permit(:name,:subject_id,:user_id, subjects_attributes: [:name])
end
end
So I'm getting closer to having a successful form submission! I placed the method accepts_nested_attributes_for in the join model, which in this case is in topic.rb. I don't really know why this works but I'm thinking it allows Rails to properly set the ":user_id" and ":subject_id" compared to placing accepts_nested_attributes_for on a model containing the "has_many through" relationship. I saw it on this post btw: http://makandracards.com/makandra/1346-popular-mistakes-when-using-nested-forms
NOW, I still have a problem where the ":subject_id" isn't being properly saved into the database. Would I have to pass in a hidden field to do this or would I have to do something else like nest my routes?
Wow that took forever to figure this one out. Since I have a has_many through relationship and I'm trying to created a nested form involving one of these models the problem I was having was I was placing the "accepts_nested_attributes_for" in the wrong model I was placing it in the has_many through model in the subject.rb file when it should have been placed in the model responsible for the join between two tables.
Also I made a super idiotic mistake on this line when I was trying to save the ":subject_id". I was writing this: #topic.subject_id = params[:subject_id][:id] instead of something like this:
#topic.subject_id = params[:subject][:id]
It was a really dumb mistake (probably because I was copying a pasting code from another controller haha)
Anyways I hope others can learn from my mistake if they ever want to do a nested form on models with a "has_many through" relationship, in certain cases the "accepts_nested_attributes_for" method will go on the JOIN table and NOT on the model with the "has_many through" relationship

Nested forms with multiple table inheritance

How do I build a nested form for objects using multiple table inheritance in rails? I am trying to make a nested form to create an object using a model with a has_many relationship to another set of models that feature multi-table inheritance. I am using formtastic and cocoon for the nested form and the act_as_relation gem to implement the multiple table inheritance.
I have the following models:
class Product < ActiveRecord::Base
acts_as_superclass
belongs_to :store
end
class Book < ActiveRecord::Base
acts_as :product, :as => :producible
end
class Pen < ActiveRecord::Base
acts_as :product, :as => :producible acts_as :product, :as => :producible
end
class Store < ActiveRecord::Base
has_many :products
accepts_nested_attributes_for :products, :allow_destroy => true, :reject_if => :all_blank
end'
For this example, the only unique attribute that book has compared to other products is an author field. In reality, I have a number of unique attributes for book which is why I chose multi-table inheritance over the more commonplace single table inheritance.
I am trying to create a nested form that allows you to create a new store with products. Here's my form:
<%= semantic_form_for #store do |f| %>
<%= f.inputs do %>
<%= f.input :name %>
<h3>Books/h3>
<div id='books'>
<%= f.semantic_fields_for :books do |book| %>
<%= render 'book_fields', :f => book %>
<% end %>
<div class='links'>
<%= link_to_add_association 'add book', f, :books %>
</div>
<% end %>
<%= f.actions :submit %>
<% end %>
And the book_fields partial:
<div class='nested-fields'>
<%= f.inputs do %>
<%= f.input :author %>
<%= link_to_remove_association "remove book", f %>
<% end %>
</div>
I get this error:
undefined method `new_record?' for nil:NilClass
Based on reading the issues on the github page for act_as_relation, I thought about making the relationship between store and books more explicit:
class Product < ActiveRecord::Base
acts_as_superclass
belongs_to :store
has_one :book
accepts_nested_attributes_for :book, :allow_destroy => true, :reject_if => :all_blank
end
class Book < ActiveRecord::Base
belongs_to :store
acts_as :product, :as => :producible
end
class Store < ActiveRecord::Base
has_many :products
has_many :books, :through => :products
accepts_nested_attributes_for :products, :allow_destroy => true, :reject_if => :all_blank
accepts_nested_attributes_for :books, :allow_destroy => true, :reject_if => :all_blank
end
Now, I get a silent error. I can create new stores using the form, and cocoon allows me to add new book fields, but when I submit the store gets created but not the child book. When, I go through the `/books/new' route, I can create a new book record that spans (the products and books table) with no problem.
Is there a workaround to this problem? The rest of the code can be found here.
Maybe you could:
Build the books relation manually on your stores_controller#new action
#store.books.build
Store manually the relation on you stores_controller#create action
#store.books ... (not really confident on how to achieve it)
Keep us posted.
You might want to consider creating your own form object. This is a RailsCast pro video, but here are some of the examples in the ASCIIcast:
def new
#signup_form = SignupForm.new(current_user)
end
This signup form can include relations to your other objects, just as you would in your original controller code:
class SignupForm
# Rails 4: include ActiveModel::Model
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
validates_presence_of :username
validates_uniqueness_of :username
validates_format_of :email, with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/
validates_length_of :password, minimum: 6
def persisted?
false
end
def subscribed
subscribed_at
end
def subscribed=(checkbox)
subscribed_at = Time.zone.now if checkbox == "1"
end
def generate_token
begin
self.token = SecureRandom.hex
end while User.exists?(token: token)
end
end
Here is the link to the RailsCast. Getting a pro membership might be worth your time. I have been getting lucky with a membership through www.codeschool.com where you can get 'prizes' when you finish courses:
RailsCast:
http://railscasts.com/episodes/416-form-objects

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
Try the following
<% form_for #site do |f| %>
<%f.fields_for :installed_templates do |af|%>
<%= af.text_field :name %>
<%end%>
<%end%>
I think you are trying to do two different things here.
Select a list of Templates for which you want to create Installed Template joins to the Site.
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 %>
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.

Resources