I am essentially running into the same issue as this post, though I have a slightly different situation: has_many nested form with a has_one nested form within it
But as someone else mentioned in that post, the provided answer does not solve the issue.
The relationship is set up so that Invoice has_many items and each Item has_one Modifier. I am attempting to make a single form_for Invoice that allows a use to create many items, each with a Modifier.
MODELS
class Invoice < ActiveRecord::Base
has_many :items
has_many :modifiers, through: :items
accepts_nested_attributes_for :items
end
class Item < ActiveRecord::Base
belongs_to :invoice
belongs_to :modifier
accepts_nested_attributes_for :modifier
end
class Modifier < ActiveRecord::Base
has_one :item
end
CONTROLLER
class Invoice
def new
#invoice = Invoice.new
end
def edit
end
...
end
VIEWS(haml)
invoice.html.haml:
= form_for #invoice do |f|
= f.text_field :status
= f.fields_for :items do |builder|
= render partial: "items/fields", locals: { :f => builder }
= link_to_add_association 'New Item', f, :items, partial: "items/fields", id: "add-item-button"
items/_fields.html.haml:
.nested-fields
- #item = #invoice.items.build
= f.fields_for :modifier, #item.build_modifier do |modifier|
= modifier.text_field :name
Let's review what's happening. In order to build the nested has_one relationship, I build an Item in the nested-fields partial so that I can build the has_one Modifier. This is because rails requires that you explicitly call 'build_something' in a has_one relationship (usually this is called in the controller's new, but I only want to build once someone has clicked the New Item button). For creating new Invoices, this code works perfectly. Checking console, I see that the relationship is created and I can verify that the Modifier was created successfully.
However, when I go back to edit the invoice, cocoon knows that I already have a modifier, so it calls the partial once to create the necessary fields_for my single Modifier. These fields are empty. This makes sense though, because as cocoon is rendering that partial, it is building a new Code with a new modifier and setting the fields blank. I can confirm that is what is occuring because once I have my modifier saved properly, I can go into my partial, remove the two build calls, and view the edit page which properly displays the saved Modifier info just fine.
Of course, now that I've removed my build calls, the form no longer saves any Modifiers that I create. So essentially, I need the build calls in there to build new Modifiers, but I can't have them in there if I want to view them.
Does anyone have a solution to this situation? I have found multiple stack overflow questions and none of them resolves this issue.
You say has_one but in your model I see has_many ?
You nested partial items/_fields is wrong: you build an extra item and there is not need for that. Coccon, in the link_to_add_association builds a new item to insert.
There are two ways to do what you want to do.
1) In the partial
To handle it correctly in your partial you can do the following (items/_fields.html.haml) :
.nested-fields
- f.object.build_modifier if f.object.new_record?
= f.fields_for :modifier do |modifier|
= modifier.text_field :name
In rails, to refer to the object of the form you can use f.object. Note this will work, but we have to check if it is a newly created object. Alternatively we could just check if the modifier is present.
2) Using the :wrap_object option ( documentation )
Cocoon allows to execute some extra code with the newly created object. So in your case that would become:
= link_to_add_association('New item', f, :items,
:wrap_object => Proc.new { |item| item.build_modifier; item })
Related
I've got a pretty simple setting for (polymorphic) comments model which also have a has_many relationship wih itself (called :replies):
# comment.rb
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
default_scope -> { order(created_at: :asc) }
belongs_to :user
belongs_to :comment_parent, class_name: "Comment", foreign_key: "comment_id"
has_many :replies, class_name: "Comment", foreign_key: "comment_id"
validates :content, presence: true
validates :commentable, presence: true
end
In the post controller I fetch the comments:
#comments = #commentable.comments.all()
I'm initiating the partial on my blog view page like so:
= render #comments
And finally the comment/_comment partial containing:
(simpiflied code, this only shows gravatar & username)
# comment/_comment.html.haml
%ol.media-list
%li.media{:class => ("media-replied" if comment.comment_id)}
= link_to gravatar_for(comment.user, size: 80, class_name: 'media-object img-circle'), comment.user, class: 'pull-left'
= render partial: 'comments/comment', collection: comment.replies
Somehow this creates some strange recurrence, in which replies get shown on their correct location, but also on an incorrect location. Problem is that there doesn't seem to be any logic in this. (at least for me it doesn't)
I've checked console to verify that the database contains the correct relations between the records (no faulty records), so it must be in the presentation / partial call.
Any ideas?
Ok, that was my own stupid mistake of course... the call #commentable.comments.all() result is all the comments, so even the nested comments are shown as if they are top level.
Short term fixed it by adding where(comment_id: nil)
I have a Vehicles model where each vehicle has a make and model. When a user creates a vehicle, they can either select from currently available makes and models, or they can create a new make and model. Both make and model contain no extra data, so they are stored in the CommonLookup model I use for dynamic enumerations.
I want to limit model choices through ajax based on the currently selected make. To do this, I've created a blongs_to relationship on the CommonLookup model to itself; in other words, any record of that type can optionally reference a parent record of the same type in a many-to-one relationship.
The problem I'm running into is actually saving the relationship. My model code, which works for creating non-related make and model records, is as follows:
class Vehicle < ActiveRecord::Base
belongs_to :make, :class_name => "CommonLookup", :foreign_key => "make_id"
belongs_to :model, :class_name => "CommonLookup", :foreign_key => "model_id"
attr_accessor :new_make_name
attr_accessor :new_model_name
before_save :create_make_from_name, :create_model_from_name
def create_make_from_name
create_make(
:value => new_make_name
)
end
def create_model_from_name
create_model(
:value => new_model_name
)
end
end
This code successfully creates a vehicle with the associated new make and model, but the new make and model are not associated with each other as I need them to be. I need a many-models to one-make relationship that I can use to easily limit choices. To be clear, this question has nothing to do with the ajax part that is necessary for limiting choices; I'm focusing on the creation of the model instances themselves so that they are related, all from a single form.
I've attempted to set up code in the create_model_from_name callback but to no avail; there is no accessible reference to the object created in the first callback that could be used to set up the relationship. What I tried:
def create_model_from_name
create_model(
:value => new_model_name,
:parent => :make
)
end
But this didn't work. Any help would be greatly appreciated.
I solved this by combining the before_save callbacks and including a little more logic:
class Vehicle < ActiveRecord::Base
belongs_to :make, :class_name => "CommonLookup", :foreign_key => "make_id"
belongs_to :model, :class_name => "CommonLookup", :foreign_key => "model_id"
attr_accessor :new_make_name
attr_accessor :new_model_name
before_save :create_make_and_model_from_names
def create_make_and_model_from_names
if not new_model_name.blank?
if not new_make_name.blank?
create_model(
:value => new_model_name,
:parent => create_parent(
:value => new_make_name
)
)
else
create_model(
:value => new_model_name,
:parent => model
)
end
end
end
end
This met my requirements by setting up a way of creating new makes and creating and associating new models with both new makes and existing makes. The end result is that my ajax request is much easier to set up because I can easily locate the models associated with each make and place these in the options list.
I'm new to rails and have spent way too many hours on this. Thanks a lot, in advance, for any help!
I can't seem to get fields_for and/or accepts_nested_attributes_for to work for my nested attributes.
I have a smash_client that has_many contracts and a form that tries to create a smash_client with a parameter and at the same time it tries to also set a parameter on the contract object. The contract belongs_to the smash_client.
I've tried a lot of different solutions and have read the docs but I'm still missing something. I get this in my params hash, in the smash_clients_controller.rb
..., "smash_client"=>{"name"=>"fasdf", "user"=>"adam"}, "smash_client_id"=>{"instance_type"=>"spot"},...
from
= form_for #smash_client do |f|
.field
= f.label :name
= f.text_field :name
.field
= fields_for :smash_client_id do |c|
%p
= c.radio_button :instance_type, 'spot'
= c.label :instance_type, 'spot'
= c.radio_button :instance_type, 'on_demand'
= c.label :instance_type, 'on demand'
.actions
= f.submit 'Save'
and
class SmashClient < ActiveRecord::Base
has_many :contracts, dependent: :destroy
accepts_nested_attributes_for :contracts, allow_destroy: true,
reject_if: proc { |attributes| attributes[:instance_type].blank? }
...
def new
#smash_client = SmashClient.new
3.times { #smash_client.contracts.build }
end
...
def smash_client_params
#smash_client_params = params.require(:smash_client).
permit( :user, :name, contracts_attributes: [:instance_type] )
end
end
and
class Contract < ActiveRecord::Base
belongs_to :smash_client
after_create :determine_instance_type_and_start
before_destroy :stop_instances
...
end
I think the nested params would work if I hard coded it because if I try something like this, in the console, I don't get errors and I get a new SmashClient and Contract.
smash_client_params = {name: 'something', user: 'blah', contracts_attributes: [{instance_type: 'spot'}]}
SmashClient.create( smash_client_params )
I tried using :contracts, #smash_client.contracts and a few other things in the fields_for section. Also tried using select and collection_select but I can't seem to nail down the form.
sorry for the long post. Hopefully I got all the useful info with nothing extra in the question.
I'd really appreciate some direction or answers.
Thanks in advance.
I finally found it. The :instance_type had to be whitelisted in the Contract model. Thanks again, kalyani. I appreciate the help. Here's the changes to the code above:
.field
= fields_for :contracts do |c|
= c.label :instance_type, 'spot instance'
= c.radio_button :instance_type, 'spot', checked: true
= c.label :instance_type, 'on demand instance'
= c.radio_button :instance_type, 'on_demand'
and
def contract_params
params.require(:contract).
permit(:id, :name, :instance_id, :smash_client_id, :instance_type)
end
Instead of : fields_for :smash_client_id do |c|
write it as: fields_for :contracts do |c|
Refer:
1. http://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for
http://railscasts.com/episodes/196-nested-model-form-part-1
Rails 4 Nested Attributes Unpermitted Parameters ---- refer this for writing the code in controller and view the correct way
I am trying to use a select tag to save multiple nested children in a parent.
This is the error I'm looking at
Couldn't find all UserLocations with IDs (1, 2) (found 0 results, but was looking for 2)
I have the following setup under Rails 4 and Devise:
User
class User < ActiveRecord::Base
has_many :user_locations
accepts_nested_attributes_for :user_locations, :allow_destroy => true
end
UserLocation (locations that the user has)
class UserLocation < ActiveRecord::Base
belongs_to :user
belongs_to :location
end
Location (predefined list of locations the user can choose upon)
class Location < ActiveRecord::Base
has_many :user_locations
has_many :users, through: :user_locations
end
However, when trying to save the selected UserLocations, they won't be saved.
Rails Select Tag (You can choose multiple items)
<%= f.select :user_location_ids, options_for_select(Location.all.collect { |l| [ l.name, l.id ] }, #user.user_locations.collect{ |l| l.id }), {}, { multiple: true } %>
I have put the user_location_ids in my application_controller as user_location_ids: []
Cheers
Solved
The solution is to overwrite the default setter method for multiple nested models model_ids=(value). Do not use the plural of the model, e.g. models_ids=(value), because that is wrong!
def user_location_ids=(value)
for slot in value do
unless slot == ""
location = Location.find_by(id: slot.to_i)
unless location.nil?
self.user_locations << UserLocation.create(user_id: self.id, location_id: location.id)
end
end
end
end
I'm trying to add a field to products on spree which is just a checkbox which is just ment to mark products if they are for sale or they're internal products.
I've added the migration and finally figured out how to add the checkbox on the form, but when I click Update I get Can't mass-assign protected attributes: for_sale
This is the migration
class AddProductForSaleField < ActiveRecord::Migration
def up
add_column :spree_products, :for_sale, :boolean
end
def down
remove_column :spree_products, :for_sale
end
end
Here's the field being added
Deface::Override.new(:virtual_path => "spree/admin/products/_form",
:name => "for_sale",
:insert_before => "code[erb-silent]:contains('track_inventory_levels')",
:partial => "spree/admin/products/for_sale")
And this is the partial
<%= f.field_container :for_sale do %>
<%= f.label :for_sale, t(:for_sale) %>
<%= f.check_box :for_sale, { :checked => true } %>
<% end %>
got it, was missing the model part
Spree::Product.class_eval do
attr_accessible :for_sale
end
Mass Assignment is the name Rails gives to the act of constructing your object with a parameters hash. It is "mass assignment" in that you are assigning multiple values to attributes via a single assignment operator.
The following snippets perform mass assignment of the name and topic attribute of the Post model:
Post.new(:name => "John", :topic => "Something")
Post.create(:name => "John", :topic => "Something")
Post.update_attributes(:name => "John", :topic => "Something")
In order for this to work, your model must allow mass assignments for each attribute in the hash you're passing in.
There are two situations in which this will fail:
You have an attr_accessible declaration which does not include :name
You have an attr_protected which does include :name
It recently became the default that attributes had to be manually white-listed via a attr_accessible in order for mass assignment to succeed. Prior to this, the default was for attributes to be assignable unless they were explicitly black-listed attr_protected or any other attribute was white-listed with attr_acessible.
If it's a permission issue then you can add :
Spree::Product.class_eval do
attr_accessible :variable_1, :variable_2 :as => [:default, :product]
end
Marking it as default for a specific model, will remove the mass-assignment warning message !