I am trying to create multiple checkbox tags for second level embedded documents. However, now it just creates new objects.
I have models
class Paprasa
include Mongoid::Document
attr_accessible :id, :bkompetencijas, :bkompetencijas_attributes
accepts_nested_attributes_for :etapa
accepts_nested_attributes_for :bkompetencijas, :allow_destroy => true
embeds_many :bkompetencijas , class_name: 'Kompetencija', inverse_of: :paprasabk, :cascade_callbacks => true
end
#-----------------------------------------------
class Kompetencija
include Mongoid::Document
attr_accessible :id, :paprasabk, :paprasabk_attributes, :siekinys, :siekinys_attributes
accepts_nested_attributes_for :paprasa, :allow_destroy => true
accepts_nested_attributes_for :siekinys, :allow_destroy => true
embedded_in :paprasabk, class_name: 'Paprasa', inverse_of: :bkompetencijas
embeds_many :siekinys, class_name: 'Siekiny' , inverse_of: :kompetencija, :cascade_callbacks => true
end
#-----------------------------------------------
class Siekiny
include Mongoid::Document
field :matricos_ids, :type => Array, :default => []
attr_accessible :id, :kompetencija, :kompetencija_attributes, :matricos_ids
accepts_nested_attributes_for :kompetencija
embedded_in :kompetencija , class_name: 'Kompetencija', inverse_of: :siekinys
end
a form
<%= form_for [#paprasa],:html => { :multipart => true} do |f| %>
<%= f.fields_for :dkompetencijas do |form_inner| %>
<%= form_inner.fields_for :siekinys do |form_inner_inner| %>
<% #some_other_object.each_with_index do |d,indexx| %>
<%= check_box_tag "#{field_name_for_js(form_inner_inner, "matricos_ids")}[]", d.id, form_inner_inner.object.matricos_ids.include?(d.id), :id => "#{field_id_for_js(form_inner_inner, "matricos_id_") << d.id.to_s}" ).to_s %>
<% end %>
<% end %>
<% end %>
<% end %>
where
def field_id_for_js(builder, attribute)
"#{builder.object_name}[#{attribute.to_s.sub(/\?$/,"")}]".gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
end
def field_name_for_js(builder, attribute)
"#{builder.object_name}[#{attribute.to_s.sub(/\?$/,"")}]"
end
it gives me ouput like this
<input id="paprasa_dkompetencijas_attributes_0_siekinys_attributes_0_matricos_id_506567916226f718e50000cb" name="paprasa[dkompetencijas_attributes][0][siekinys_attributes][0][matricos_ids][]" type="checkbox" value="506567916226f718e50000cb" />
<input id="paprasa_dkompetencijas_attributes_0_siekinys_attributes_1_matricos_id_506567916226f718e50000cb" name="paprasa[dkompetencijas_attributes][0][siekinys_attributes][1][matricos_ids][]" type="checkbox" value="506567916226f718e50000cb" />
and parameters
{"paprasa"=>{"dkompetencijas_attributes"=>{"0"=>{"siekinys_attributes"=>{"0"=>{"id"=>"505adfd26226f7555a000191", "matricos_ids"=>["506567916226f718e50000cb"]}, "1"=>{"id"=>"507558176226f75fa5000033"}, "2"=>{"id"=>"512f19626226f765c7000071"}, "3"=>{"id"=>"512f36456226f765c70000c8"}}, "id"=>"505adfd26226f7555a000190"}}}, "user_id"=>"5058514b6226f73ae4000064", "etapa_id"=>"505851516226f73ae4000065"}
this just creates new "siekiny" objects instead of updating existing ones. What I am doing wrong?
I actually ran into this problem recently. Accepts_nested_attributes_for cannot(yet) decipher with a new parent object form whether to create new child objects or find and update existing ones. If you don't want your child objects being duplicated when you create a new parent object then you will have to create a custom setter method to handle the find_or_create for you.
Checkout THIS question which solves the problem by overriding autosave associations.
Or, checkout THIS question which solves the problem by using reject_if and a custom method.
Another option to solve this problem is to break down the hash in the controller and do all object creating there instead of in the model.
However, this did not work for me. For some actions it worked several times and then broke again. I even tried from #some_other_object side (this object is referenced from paprasa and it just kept creating new objects. The only way I got this was putting some "hackish" things in controller, which I really don't like.
I took parent object, iterated through child using index on params to look for specified array field and assigned to child attribute, then the .save method. Now everything works, but i do not Like it...
Related
I'm working on on a web app that takes in information about companies. Information can be taken for a PreferredOffering (of stock) or an Incorporation. So in other words, when I create a new entry to either one of those models, a new company is formed.
It works out that my database is cleaner if PreferredOffering and Incorporation are children of Company, even though I'm trying to go through the preferred_offerings_controller or theincorporations_controller to create a new Company. Here lies my question; I'm trying to figure out how to configure my view and controllers to create a parent model from a child controller. I've done some research and have seen two other S/O posts on how to accomplish this with Rails 3, however it would seem that the addition of strong params adds another layer of complexity to the endeavor.
So I have my models set up like this
class Company < ActiveRecord::Base
belongs_to :user
has_one :incorporation, dependent: :destroy
has_many :preferred_offerings, dependent: :destroy
accepts_nested_attributes_for :preferred_offerings, allow_destroy: true
accepts_nested_attributes_for :incorporation, allow_destroy: true
end
.
class Incorporation < ActiveRecord::Base
belongs_to :company
end
.
class PreferredOffering < ActiveRecord::Base
belongs_to :company
end
The controller and view are what I'm iffy on.
Let's just take a look at the incorporation view/controller. If I were to configure it so that Incorporation has_one :company, I would set it up as follows:
class IncorporationsController < ApplicationController
def index
end
def new
#user=current_user
#incorporation = #user.incorporations.build
#company = #incorporation.build_company
end
def create
#incorporation = current_user.incorporations.build(incorporation_params)
end
private
def incorporation_params
params.require(:incorporation).permit(:title, :trademark_search, :user_id, :employee_stock_options, :submit, :_destroy,
company_attributes: [:id, :name, :employee_stock_options, :options_pool, :state_corp, :street, :city, :state, :zip, :issued_common_stock, :outstanding_common_stock, :fiscal_year_end_month, :fiscal_year_end_day, :user_id, :_destroy]
)
end
end
And the view would be:
<%= simple_form_for #incorporation, html: {id:"incorporationform"}, remote: false, update: { success: "response", failure: "error"} do |f| %>
(incorporation-specific fields)
<%= f.simple_fields_for :company do |company| %>
(Company-specific fields)
<% end %>
<% end %>
So my question is:
How do I need to modify my controller and view to create a Company from the incorporations_controller IF Company has_one :incorporation
Any suggestions would be much appreciated.
While it isn't the "Rails Way", there is nothing really wrong with having #company being the parent in your form, even though it is in the incorporations#new action. Your view would change to this:
<%= simple_form_for #company, html: {id:"companyform"}, remote: false, update: { success: "response", failure: "error"} do |f| %>
(company-specific fields)
<%= f.simple_fields_for :incorporation do |incorporation| %>
(incorporation-specific fields)
<% end %>
<% end %>
And your strong params would change so that Company is the parent and Incorporation is the child.
Another option would be to simply go through the Company controller. You could create two new actions: new_preferred_offering and new_incorporation. You would then create the objects in those actions. Or you could pass in some kind of :type param so that the normal new action renders one of two forms based on which one you want.
I have a many-to-many relationship between Recipes and Ingredients. I am trying to build a form that allows me to add an ingredient to a recipe.
(Variants of this question have been asked repeatedly, I have spent hours on this, but am fundamentally confused by what accepts_nested_attributes_for does.)
Before you get scared by all the code below I hope you'll see it's really a basic question. Here are the non-scary details...
Errors
When I display a form to create a recipe, I am getting the error "uninitialized constant Recipe::IngredientsRecipe", pointing to a line in my form partial
18: <%= f.fields_for :ingredients do |i| %>
If I change this line to make "ingredients" singular
<%= f.fields_for :ingredient do |i| %>
then the form displays, but when I save I get a mass assignment error Can't mass-assign protected attributes: ingredient.
Models (in 3 files, named accordingly)
class Recipe < ActiveRecord::Base
attr_accessible :name, :ingredient_id
has_many :ingredients, :through => :ingredients_recipes
has_many :ingredients_recipes
accepts_nested_attributes_for :ingredients
accepts_nested_attributes_for :ingredients_recipes
end
class Ingredient < ActiveRecord::Base
attr_accessible :name, :recipe_id
has_many :ingredients_recipes
has_many :recipes, :through => :ingredients_recipes
accepts_nested_attributes_for :recipes
accepts_nested_attributes_for :ingredients_recipes
end
class IngredientsRecipes < ActiveRecord::Base
belongs_to :ingredient
belongs_to :recipe
attr_accessible :ingredient_id, :recipe_id
accepts_nested_attributes_for :recipes
accepts_nested_attributes_for :ingredients
end
Controllers
As RESTful resources generated by rails generate scaffold
And, because the plural of "recipe" is irregular, inflections.rb
ActiveSupport::Inflector.inflections do |inflect|
inflect.irregular 'recipe', 'recipes'
end
View (recipes/_form.html.erb)
<%= form_for(#recipe) do |f| %>
<div class="field">
<%= f.label :name, "Recipe" %><br />
<%= f.text_field :name %>
</div>
<%= f.fields_for :ingredients do |i| %>
<div class="field">
<%= i.label :name, "Ingredient" %><br />
<%= i.collection_select :ingredient_id, Ingredient.all, :id, :name %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Environment
Rails 3.2.9
ruby 1.9.3
Some things tried
If I change the view f.fields_for :ingredient then the form loads (it finds Recipe::IngredientRecipe correctly, but then when I save, I get a mass-assignment error as noted above. Here's the log
Started POST "/recipes" for 127.0.0.1 at 2012-11-20 16:50:37 -0500
Processing by RecipesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"/fMS6ua0atk7qcXwGy7NHQtuOnJqDzoW5P3uN9oHWT4=", "recipe"=>{"name"=>"Stewed Tomatoes", "ingredient"=>{"ingredient_id"=>"1"}}, "commit"=>"Create Recipe"}
Completed 500 Internal Server Error in 2ms
ActiveModel::MassAssignmentSecurity::Error (Can't mass-assign protected attributes: ingredient):
app/controllers/recipes_controller.rb:43:in `new'
app/controllers/recipes_controller.rb:43:in `create'
and the failing lines in the controller is simply
#recipe = Recipe.new(params[:recipe])
So the params being passed, including the nested attributes, are incorrect in some way. But I have tried lots of variants that fix-one-break-another. What am I failing to understand?
Thanks to clues from all, I have found what was wrong with my approach. Here's how I solved it.
I had originally tried with a simple HABTM many-to-many relationship, where the join table was named following standard Rails convention: ingredients_recipes. Then I realized that in a way, accepts_nested_attributes_for is designed for a 1-to-many relationship. So I converted to using has_many_through, creating a model IngredientsRecipes.
That name was the core problem, because Rails needs to be able to convert from plural to singular when using build to create form elements. This caused it to look for the non-existant class Recipe::IngredientsRecipe. When I changed my form so it used fields_for :ingredient the form displayed, but still failed to save with a mass assignment error. It even failed when I added :ingredients_attributes to attr_accessible. It still failed when I added #recipe.ingredients.build to RecipesController#new.
Changing the model to a singular form was the final key to resolve the problem. IngredientsRecipe would have worked, but I chose RecipeIngredients, as it makes more sense.
So to summarize:
can't use accepts_nested_attributes_for with has_and_belongs_to_many; need has_many with through option. (Thanks #kien_thanh)
adding accepts_nested_attributes_for creates a accessor that must be added to attr_accessible in the form <plural-foreign-model>_attributes, e.g. in Recipe I added attr_accessible :name, :ingredients_attributes (Thanks #beerlington)
before displaying the form in the new method of the controller, must call build on the foreign model after creating a new instance, as in 3.times { #recipe.ingredients.build }. This results in HTML having names like recipe[ingredients_attributes][0][name] (Thanks #bravenewweb)
join model must be singular, as with all models. (All me :-).
If you inspect the form that is generated, you'll notice that the nested fields have a name like "ingredients_attributes". The reason you're getting the mass-assignment error is because you need to add these fields to the attr_accessible declaration.
Something like this should fix it (you'll need to doublecheck the field names):
class Recipe < ActiveRecord::Base
attr_accessible :name, :ingredients_attributes
#...
end
Update: There's a similar answer here
Leave the call as
<%= f.fields_for :ingredients do |i| %>
But before that do
<% #recipe.ingredients.build %>
Im guessing that will allow your form to be created the right way, but there are likely other errors with your models, I can look # it more in detail when I have more time if its still not working, but:
As far as what accepts_nested_attributes_for does, when you pass in a correctly formatted params hash to the Model.new or Model.create or Model.update, it allows those attributes on the related model to be saved if they are in the params hash. In addition though, you do need to make the attributes accessible if they are unaccessible in the parent model as stated by beerlington.
I think you just need set up a one-to-many association, one recipe has many ingredients and one ingredient belongs to one recipe, so your model look like:
class Recipe < ActiveRecord::Base
attr_accessible :name, :ingredients_attributes
has_many :ingredients
accepts_nested_attributes_for :ingredients
end
class Ingredient < ActiveRecord::Base
attr_accessible :name, :recipe_id
belongs_to :recipe
end
You are built right form, so I don't write it again here. Now in your new and create controller will be like this:
def new
#recipe = Recipe.new
# This is create just one select field on form
#recipe.ingredients.build
# Create two select field on form
2.times { #recipe.ingredients.build }
# If you keep code above for new method, now you create 3 select field
end
def create
#recipe = Recipe.new(params[:recipe])
if #recipe.save
...
else
...
end
end
How does params[:recipe] look like? If you just have one select field, maybe like this:
params = { recipe: { name: "Stewed Tomatoes", ingredients_attributes: [ { id: 1 } ] } }
If you have 2 ingredient select field:
params = { recipe: { name: "Stewed Tomatoes", ingredients_attributes: [ { id: 1 }, { id: 2 } ] } }
We are using active_admin for our administration backend.
We have a model "App" that :belongs_to model "Publisher":
class App < ActiveRecord::Base
belongs_to :publisher
end
class Publisher < ActiveRecord::Base
has_many :apps
end
When creating a new entry for the "App" model I want to have the option to either select an existing publisher or (if the publisher is not yet created) to create a new publisher in the same (nested) form (or at least without leaving the page).
Is there a way to do this in active_admin?
Here's what we have so far (in admin/app.rb):
form :html => { :enctype => "multipart/form-data" } do |f|
f.inputs do
f.input :title
...
end
f.inputs do
f.semantic_fields_for :publisher do |p| # this is for has_many assocs, right?
p.input :name
end
end
f.buttons
end
After hours of searching, I'd appreciate any hint... Thanks!
First, make sure that in your Publisher model you have the right permissions for the associated object:
class App < ActiveRecord::Base
attr_accessible :publisher_attributes
belongs_to :publisher
accepts_nested_attributes_for :publisher, reject_if: :all_blank
end
Then in your ActiveAdmin file:
form do |f|
f.inputs do
f.input :title
# ...
end
f.inputs do
# Output the collection to select from the existing publishers
f.input :publisher # It's that simple :)
# Then the form to create a new one
f.object.publisher.build # Needed to create the new instance
f.semantic_fields_for :publisher do |p|
p.input :name
end
end
f.buttons
end
I'm using a slightly different setup in my app (a has_and_belongs_to_many relationship instead), but I managed to get it working for me. Let me know if this code outputs any errors.
The form_builder class supports a method called has_many.
f.inputs do
f.has_many :publisher do |p|
p.input :name
end
end
That should do the job.
Update: I re-read your question and this only allows to add a new publisher, I am not sure how to have a select or create though.
According to ActiveAdmin: http://activeadmin.info/docs/5-forms.html
You just need to do as below:
f.input :publisher
I've found you need to do 3 things.
Add semantic fields for the form
f.semantic_fields_for :publisher do |j|
j.input :name
end
Add a nested_belongs_to statement to the controller
controller do
nested_belongs_to :publisher, optional: true
end
Update your permitted parameters on the controller to accept the parameters, using the keyword attributes
permit_params publisher_attributes:[:id, :name]
I have a complex form for scheduling events. Here are the abbreviated associations:
class Event < ActiveRecord::Base
belongs_to :client
accepts_nested_attributes_for :client, :reject_if => lambda { |a| a[:name].blank? }
end
class Client < ActiveRecord::Base
has_many :events
has_many :questions, :dependent => :destroy
accepts_nested_attributes_for :questions, :reject_if => lambda { |a| a[:content].blank? }
end
The form is creating a new event and I have the following structure:
- form_for #event do |event_form|
%select=collection_select(client_options_for_select, :options, :group_name, :id, :name, #event.client_id)
- event_form.fields_for :client do |client|
= client.text_field :name
- client.fields_for :questions do |question|
= question.text_field :content
The client already exists and is chosen from a select menu. An observer renders the nested attributes form by setting the client variable in a controller action and then rendering the partial.
Here is the error I'm getting:
ActionView::TemplateError (wrong number of arguments (0 for 1)) on line #1 of app/views/proceedings/_questions.html.haml:
1: - event_form.fields_for :client do |client|
app/views/proceedings/_questions.html.haml:1:in `form'
app/views/proceedings/_questions.html.haml:1:in `_run_haml_app47views47events47_client_questions46html46haml_locals_client_questions_object'
haml (3.0.21) rails/./lib/haml/helpers/action_view_mods.rb:13:in `render'
app/controllers/proceedings_controller.rb:261:in `__instance_exec0'
app/controllers/proceedings_controller.rb:260:in `corp_client_questions'
app/controllers/proceedings_controller.rb:258:in `corp_client_questions'
I'm having problems (I think) with the belongs_to association between Event and Client. I don't know if Event can accept nested attributes of Client when the Event belongs_to the Client. I've always done it the other way around (Client accepts nested attributes of Event).
Any ideas? I can elaborate if you need more code or background. Thanks!
Update: Added controller code as requested.
def client_questions
if params[:client_id].blank?
render_no_client_questions
elsif #client = Client.find(params[:client_id]) and #client.is_unspecified?
render_no_client_questions
else
respond_to do |format|
format.js {
render :update do |page|
page[:client_questions].replace_html :partial => 'client_questions', :layout => false
end
}
end
end
end
try adding an instance of the fields_for object in the options... generally a symbol isn't enough when creating a new top-level form object... Try the following, but yes it is possible to accept nested attributes on a belongs_to.
<%= event_form.fields_for :client, #client do |client| %>
<%= client.text_field :name %>
<%= client.fields_for :questions, Question.new do |question| %>
<%= question.text_field :content %>
<% end %>
<% end %>
I had the same issue using accepts_nested_attributes_for :address on a belongs_to :address association for my Order object.
The field_for wasn't echoing nothing, but all it took was adding
#order.build_address()
and that made it work.
I think is because we are using the association sort of reversed from the usual, so you have to manually create the association.
In your controller method(s), you need to add:
#event.build_client
The form cannot display fields_for when it does not have a valid object. Most of the time, we use something like #event.client.build, but this will not work with a belongs_to association. That method is only valid with has_many and has_and_belongs_to_many.
Reference here.
While working on Rails 3 app, I came to the problem of nested forms.
One collection is a set of predefined objects (created with db:seed).
The other collection should show a form to allow to choose a few elements.
An example is better than a long description, so here it is.
Suppose you have 2 models: User and Group.
Suppose there are a few groups: Member, Admins, Guests, ...
You want your users to have multiple groups, and so you need a intermediate table: memberships.
The model code is obvious:
class User < ActiveRecord::Base
has_many :memberships
has_many :groups, :through => :memberships
accepts_nested_attributes_for :memberships, :allow_destroy => true
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
class Group < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships
end
The controller should not need to be changed.
The view, however is more complicated.
I want to show a list of checkboxes to choose a few groups of the predefined ones.
I am using here the special _destroy field, with reversed value, to destroy when it is actually unchecked (and so add the user to the group when it is checked)
%p
= f.label :name
%br
= f.text_field :name
%ul
= f.fields_for :memberships, #groups do |g|
%li
- group = g.object
= g.hidden_field :group_id, :value => group.id
= g.check_box :_destroy, {:checked => #user.groups.include?(group)}, 0, 1
= g.label :_destroy, group.name
However, this do not work as expected, because the form g will always create an input with an arbitrary id after each group (and even break the layout by including it after the </li>):
<input id="user_memberships_attributes_0_id" name="user[memberships_attributes][0][id]" type="hidden" value="1" />
<input id="user_memberships_attributes_1_id" name="user[memberships_attributes][1][id]" type="hidden" value="2" />
# ...
Knowing the syntax of nested attributes is the following:
{:group_id => group.id, :_destroy => 0} # Create
{:group_id => group.id, :_destroy => 0, :id => membership.id} # Update
{:group_id => group.id, :_destroy => 1, :id => membership.id} # Destroy
{:group_id => group.id, :_destroy => 1} # Do nothing
Sending every time the id will not work, because it will try to update a record which does not exist instead of creating it, and try to destroy when the record does not exist.
The current solution I found is to remove all the ids, which are wrong anyway (they should be the ids of the memberships, instead of simple indexes), and add the real id when the user already has the group.
(this is called in the controller before create and update)
def clean_memberships_attributes
if membership_params = params[:user][:memberships_attributes]
memberships = Membership.find_all_by_user_id params[:id]
membership_params.each_value { |membership_param|
membership_param.delete :id
if m = memberships.find { |m| m[:group_id].to_s == membership_param[:group_id] }
membership_param[:id] = m.id
end
}
end
end
This seems so wrong, and it adds a lot of logic in the controller, just to control the bad behavior of the view fields_for helper.
Another solution is to create all the form html yourself, trying to mimic the Rails conventions, and avoid the id problem, but that is really noisy in the code and I believe there is a better way.
Is it a way to make fields_for work better?
Is there any helper more appropriate ?
Am I reasoning wrong somewhere in this question?
How would you do to achieve this?
Thanks
I hope I understand you correctly?
The groups are predefined and you want to be able to add a user to a group.
On the edit User screen you show all or some of the predefined groups.
You want to add a user by ticking the checkbox and saving the record.
If you untick the checkbox the membership of this user in the unticked group should be nil.
Here's how I am doing this with companies and projects:
Class Company
has_many :datasets
has_many :projects, :through => :datasets
Class Project
has_many :datasets
has_many :companies, :through => :datasets
<% for company in Company.all %>
<tr>
<td>
<%= check_box_tag 'project[company_ids][]', company.id, #project.companies.include?(company) %>
</td>
</tr>
<% end %>
I list all Companies and check the ones I want to include in the project.
Please tell me if this already helps you? I believe you are using haml in your example. I am not really used to that notation.
If you want to use a subset you could use a scope:
scope :recent, Company.where(:created_at => (Time.now.midnight - 1.day)..Time.now.midnight)
Then you can use this scope like the .all method:
Company.recent
Does this help?