I am trying to understand how to do a simple thing - save both an id and the text from a collection_select.
I am just beginning work with RoR so I don't have a good grasp on everything yet.
I have experience mostly in Java plus some Perl, so perhaps I did not choose the correct way.
I have a Defect which has a DefectRootCause.
There is another entity, DefaultRootCauses from which I can choose the DefectRootCause.root_cause to associate with a Defect.
I am trying to save the Defect with its DefectRootCause. The DefectRootCause should have the root_cause field as text, the values chosen from the DefaultRootCause.
The problem is that I am only getting to save one of the root_cause or default_root_cause_id for the DefectRootCause.
More, the DefectRootCause.root_cause gets the id form the DefaultRootCause instead of the text.
Please let me know if I am right in trying to get everything to happen "magically" or if I should do some processing of the data in a controller.
See below for the code.
I have the models:
class Defect < ActiveRecord::Base
has_one :defect_root_cause, :dependent => :destroy
has_many :default_root_cause, :through => :defect_root_cause
end
class DefectRootCause < ActiveRecord::Base
belongs_to :defect
belongs_to :default_root_cause
end
class DefaultRootCause < ActiveRecord::Base
has_many :defect_root_causes
has_many :defects, :through => :defect_root_causes
end
Controller (I only need changes to one of them for this I think):
# POST /defects
# POST /defects.json
def create
#I am doing this because the attributes are not named :defect_root_cause_attribues, I don't know yet why
#defect = Defect.new(defect_params.except(:defect_root_cause))
#defect.defect_root_cause = DefectRootCause.new(defect_params[:defect_root_cause])
respond_to do |format|
if #defect.save
format.html { redirect_to #defect, notice: 'Defect was successfully created.' }
format.json { render :show, status: :created, location: #defect }
else
format.html { render :new }
format.json { render json: #defect.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /defects/1
# PATCH/PUT /defects/1.json
def update
respond_to do |format|
#I am doing this because the attributes are not named :defect_root_cause_attribues, I don't know yet why
if #defect.update(defect_params.except(:defect_root_cause)) && #defect.defect_root_cause.update(defect_params[:defect_root_cause])
format.html { redirect_to #defect, notice: 'Defect was successfully updated.' }
format.json { render :show, status: :ok, location: #defect }
else
format.html { render :edit }
format.json { render json: #defect.errors, status: :unprocessable_entity }
end
end
end
# Never trust parameters from the scary internet, only allow the white list through.
def defect_params
#I am doing this because the attributes are not named :defect_root_cause_attribues, I don't know yet why
params.require(:defect).permit(:details, defect_root_cause: [:id, :details, :root_cause, :default_root_cause_id])
end
Now the view:
<%= form_for #defect do |f| %>
<% if #defect.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#defect.errors.count, "error") %> prohibited this defect from being saved:</h2>
<ul>
<% #defect.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :details %><br>
<%= f.text_field :details %>
<%= f.fields_for #defect.defect_root_cause do |drc| %>
<%= drc.label :root_cause%>
<%= drc.collection_select(:default_root_cause_id, DefaultRootCause.all, :id, :root_cause, :prompt => true) %>
<%= drc.label :details%>
<%= drc.text_field :details %>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
In the routes.rb I only have the following:
resources :default_root_causes
resources :defects
resources :defect_root_causes
# You can have the root of your site routed with "root"
root 'defects#index'
Finally, my problem (root_cause is nil):
irb(main):006:0> DefectRootCause.find(10)
DefectRootCause Load (0.3ms) SELECT `defect_root_causes`.* FROM `defect_root_causes` WHERE `defect_root_causes`.`id` = 10 LIMIT 1
=> #<DefectRootCause id: 10, details: "sadsad", defect_id: 9, default_root_cause_id: 2, created_at: "2016-02-19 05:52:06", updated_at: "2016-02-19 05:52:06", root_cause: nil>
if I change the select to:
<%= drc.collection_select(:root_cause, DefaultRootCause.all, :id, :root_cause, :prompt => true) %>
I get the following (root_cause is saved but it is a number not the text - in this case I want the following text: "second root cause", plus the default_root_cause_id is nil):
irb(main):005:0> DefectRootCause.find(9)
DefectRootCause Load (0.3ms) SELECT `defect_root_causes`.* FROM `defect_root_causes` WHERE `defect_root_causes`.`id` = 9 LIMIT 1
=> #<DefectRootCause id: 9, details: "das", defect_id: 8, default_root_cause_id: nil, created_at: "2016-02-19 05:42:36", updated_at: "2016-02-19 05:42:36", root_cause: "2">
EDIT:
Now I am posting what I have done to save all the information where I need it.
I am now posting as an answer because I still don't know if this is the right way of doing this.
So please, if you know it should be done differently let me know. I am looking especially at how to send all the information from the view. I saw the view "knows" both the id and the text of the selected DefaultRootCause, but I don't get both in the params sent.
So, now the code.
View:
<%= drc.select :default_root_cause_id, DefaultRootCause.all.collect{|r| [r.root_cause, r.id]}, :prompt => true %>
defects_controller.rb
def create
#defect = Defect.new(defect_params.except(:defect_root_cause))
#defect.defect_root_cause = DefectRootCause.create(defect_params[:defect_root_cause].merge(:root_cause => DefaultRootCause.find(defect_params[:defect_root_cause][:default_root_cause_id]).root_cause))
respond_to do |format|........
def update
respond_to do |format|
if #defect.update(defect_params.except(:defect_root_cause)) && #defect.defect_root_cause.update(defect_params[:defect_root_cause].merge(:root_cause => DefaultRootCause.find(defect_params[:defect_root_cause][:default_root_cause_id])))
With those I get the following params and also the following inserts:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"jGUQIxeXWZwg5lqsPFrE4vO9z4ioRnAN/Z1/K9scQiIeQ0MTHBTvZoDcwHKtGvrgrG53y4yr7tX4wnCShoZ/oA==", "defect"=>{"details"=>"a", "defect_root_cause"=>{"default_root_cause_id"=>"2", "details"=>"a"}}, "commit"=>"Create Defect"}
DefaultRootCause Load (0.1ms) SELECT `default_root_causes`.* FROM `default_root_causes` WHERE `default_root_causes`.`id` = 2 LIMIT 1
CACHE (0.0ms) SELECT `default_root_causes`.* FROM `default_root_causes` WHERE `default_root_causes`.`id` = 2 LIMIT 1 [["id", "2"]]
CACHE (0.0ms) SELECT `default_root_causes`.* FROM `default_root_causes` WHERE `default_root_causes`.`id` = 2 LIMIT 1 [["id", "2"]]
(0.1ms) BEGIN
SQL (0.2ms) INSERT INTO `defect_root_causes` (`details`, `default_root_cause_id`, **`root_cause`**, `created_at`, `updated_at`) VALUES ('a', 2, **'second root cause'**, '2016-02-23 07:10:59', '2016-02-23 07:10:59')
Now, please let me know if this is "the right" way to do this.
Thank you,
Victor
You are trying to update an associated attributes through the parent which means you need to use accepts_nested_attributes_for. Add this method to your Defect model like so:
class Defect < ActiveRecord::Base
has_one :defect_root_cause, :dependent => :destroy
has_many :default_root_cause, :through => :defect_root_cause
accepts_nested_attributes_for :defect_root_cause
end
After doing that, go to your defects_controller.rb and update the #defect_params method to:
params.require(:defect).permit(:details, defect_root_cause_attributes: [:id, :details, :root_cause, :default_root_cause_id])
You Can Use the following code:
<%=f.select :default_root_cause_id, DefaultRootCause.all.collect{|r| [r.route_cause,"#{r.route_cause} - #{r.id}"]} %>
Related
I'm new to Ruby and also Rails, and I'm trying to put together a nested form that ultimately creates a page but also allows the user to create individual parts inline before submitting the page. The inline form contains two buttons, one adds a part and the other one removes it. Here are my relevant files (please let me know if you need to see any other) and I'll list the problems I'm having after:
FYI, the gems I'm using are: Slim, Simple Form, cocoon and Bootstrap. On Rails 4.
_form.html.slim
= simple_form_for(#page, html: { class: 'form-horizontal' }) do |f|
= f.input :title, label: 'Title'
= f.input :description, label: 'Desc'
.form-group
.col-xs-10
label Parts
.form-inline
= f.simple_fields_for :parts do |part|
= render 'part_fields', f: part
.links
= link_to_add_association 'Add Part', f, :parts
= f.button :submit
_parts_fields.html.slim
= f.input :part_type, collection: ['String 1', 'String 2'], prompt: 'Part type', label: false
= f.input :part_title, placeholder: 'Part title', label: false
= f.input :part_desc, placeholder: 'Part description', label: false
= link_to_remove_association 'Remove Part', f
/models/page.rb
class Page < ActiveRecord::Base
belongs_to :project
has_many :parts
accepts_nested_attributes_for :parts
end
/models/part.rb
class Part < ActiveRecord::Base
belongs_to :page
end
/controllers/pages_controller.rb
class PagesController < ApplicationController
def index
#pages = Page.all
respond_to do |format|
format.html
format.json { render json: #pages }
end
end
def new
#page = Page.new
respond_to do |format|
format.html
format.json { render json: #page }
end
end
def edit
#page = Page.find(params['id'])
end
def create
#page = Page.new(page_params)
respond_to do |format|
if #page.save
format.html { redirect_to(#page) }
format.json { render json: #page, status: :created, location: #page }
else
format.html { render action: 'new' }
format.json { render json: #page.errors, status: :unprocessable_entity }
end
end
end
def update
#page = Page.find(params['id'])
respond_to do |format|
if #page.update_attributes(page_params)
format.html { redirect_to(action: 'index') }
format.json { head :ok }
else
format.html { render action: 'edit' }
format.json { render json: #page.errors, status: :unprocessable_entity }
end
end
end
def page_params
params.require(:page).permit(:title, :description, parts_attributes: [:id, :part_type, :part_title, :part_desc])
end
end # End Pages Controller
Routes
resources :projects do
resources :pages
end
resources :pages
resources :parts
Problems:
1) The form is not saving any data (can't edit/update current pages or create new ones). Update: fixed, see my own answer below.
2) On the partial I'm using a collection to have a dropdown menu, but those test values are hard-coded right now. How can I have a dropdown that populates each field with columns from the db?
3) The inline form elements which come from the partial are not being rendered back on the main form. All I see is the "Parts" label and no elements underneath it. Update: #pages.parts.build solved this.
Appreciate any help you guys can give me.
You have a #page object with no parts. So when it tries to render the fields_for, it does! Just, zero times. Add #page.parts.build in your controller to add one.
Not sure; too tired right now. Try changing save to save! and update_attributes to update! so Rails throws an error instead of failing silently. BTW, why do you have if params[:page] inside your page_params? That isn't needed; that's what require(:page) does.
ActiveRecord provides a column_names method, that returns an array of column names.
Actually, #page.parts.build will always build a new part, even if the user did not request this (it could be what you want). Now you only show the Add button for each nested part. I would assume you would want only one Add button, as follows:
.form-inline
= f.simple_fields_for :parts do |part|
= render 'parts_fields', f: part
= link_to_add_association 'Remove Part', f, :parts
= link_to_add_association 'Add Part', f, :parts
This will always show the Add Part link (and only once).
As to not saving, possible reasons are:
the data is not posted to the server (check your logfile, possible reasons are errors in your html, e.g. multiple identical ids)
the data is blocked by your strong-parameters command
the data is blocked by the reject_if condition of the accepts_nested_attributes_for
a failed validation
Now, if the code displayed is your actual code everything seems ok. So could you show us what is posted to the server? (check your logfile).
The form is now saving data as expected. In one of my partials I had two fields that were identical and thus creating a silent conflict for me. In addition, after I resolved this I started to get an Unpermitted parameter: _destroy error, and the fix was to add :_destroy to the list of required params for parts_attributes.
I've been trying, for the last few months, to figure out how to set up an evaluation model so that some users can evaluate other users on their joint projects.
I previously asked this question, but have since had some advice that the suggestion to make this model polymorphic was not the right approach. I wasn't actually able to get the advice in this post working, but my question is now slightly different.
http://stackoverflow.com/questions/36394489/rails-4-polymorphic-associations-and-concerns
I have a user model and an evaluation model.
User
has_many :given_evaluations, foreign_key: :evaluator_id, dependent: :destroy, class_name: Evaluation
has_many :received_evaluations, foreign_key: :evaluatee_id, dependent: :destroy, class_name: Evaluation
Evaluation
belongs_to :evaluator, foreign_key: :evaluator_id, class_name: User
belongs_to :evaluatee, foreign_key: :evaluatee_id, class_name: User
I'm now trying to figure out how to setup the form so that the right user gets the evaluation and to limit the which user can leave the feedback.
In the evaluation form, I have:
<%= simple_form_for(#evaluation) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :overall_score, collection: 1..10, autofocus: true, :label => "How do you rate this project experience (1 being did not meet expectations - 10 being met all expectations) ?" %>
<%= f.input :project_score, collection: 1..10, :label => "How successful was the project (1 being did not meet expectations - 10 being met all expectations)?" %>
<%= f.input :continue_project?, as: :boolean, checked_value: true, unchecked_value: false, :label => "Do you intend to continue working on the project?" %>
<%= f.input :remark, as: :text, :label => "Evaluate your project experience", :input_html => {:rows => 10} %>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
In my evaluations controller, I have:
class EvaluationsController < ApplicationController
before_action :set_evaluation, only: [:show, :edit, :update, :destroy]
# before_filter :get_user, only: [:show, :edit, :update, :destroy]
# GET /evaluations
# GET /evaluations.json
def index
#evaluations = Evaluation.all
end
# GET /evaluations/1
# GET /evaluations/1.json
def show
#received_evaluations = #user.received_evaluations
end
# GET /evaluations/new
def new
#evaluation = Evaluation.new
end
# GET /evaluations/1/edit
def edit
end
# POST /evaluations
# POST /evaluations.json
def create
#evaluation = Evaluation.new(evaluation_params)
respond_to do |format|
if #evaluation.save
format.html { redirect_to #evaluation, notice: 'Evaluation was successfully created.' }
format.json { render :show, status: :created, location: #evaluation }
else
format.html { render :new }
format.json { render json: #evaluation.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /evaluations/1
# PATCH/PUT /evaluations/1.json
def update
respond_to do |format|
if #evaluation.update(evaluation_params)
format.html { redirect_to #evaluation, notice: 'Evaluation was successfully updated.' }
format.json { render :show, status: :ok, location: #evaluation }
else
format.html { render :edit }
format.json { render json: #evaluation.errors, status: :unprocessable_entity }
end
end
end
# DELETE /evaluations/1
# DELETE /evaluations/1.json
def destroy
#evaluation.destroy
respond_to do |format|
format.html { redirect_to evaluations_url, notice: 'Evaluation was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_evaluation
#evaluation = Evaluation.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def evaluation_params
params[:evaluation].permit(:overall_score, :project_score, :personal_score, :remark, :work_again?, :continue_project?)
end
end
Have I got the controller action for show correct? Do I need to put something in the controller to identify which user is receiving the evaluation and which is giving the evaluation. Do I need to add the :user_id to the permitted params in the evaluations controller?
How do I change the form to identify the correct user that's receiving the evaluation.
My routes are:
resources :evaluations
devise_for :users, #class_name: 'FormUser',
:controllers => {
:registrations => "users/registrations",
# :omniauth_callbacks => "users/authentications"
:omniauth_callbacks => 'users/omniauth_callbacks'
}
# get '/auth/:provider/callback' => 'users/authentications#create'
# get '/authentications/sign_out', :to => 'users/authentications#destroy'
# PER SOURCEY TUTORIAL ----------
match '/users/:id/finish_signup' => 'users#finish_signup', via: [:get, :patch], :as => :finish_signup
Workflow
A user creates a project (owner)
The owner invites classmates to join the project (team mates)
If team mates accept by the cut off date, the users involved in the project complete the project.
Once complete, each user evaluates the other users involvement in the project and the project itself. To do that, each user that is involved in the project sees a button on their user page, to get a link to the evaluation form. I need to figure out how to reference the other user ids for the team mates as well as the project they worked on.
The reason why I didn't nest the evaluation route inside the user routes is that I might try (if I can figure out this part first) to separate project evaluation from team mate evaluation, in which case, I'd like to use evaluation for two purposes. I'll come back to that later. For now, the evaluation model is on a user.
Finally, I use devise gem for user authentication.
PROBLEMS IDENTIFYING EVALUATEE
Taking Paul's suggestion for how to identify the evaluate_id, I added this select to my evaluation form:
<%= f.select :evaluatee_id, User.all.map{|u| [u.formal_name]} %>
Paul suggested including u.id inside the [] of this line. I don't understand how all the pieces fit together in this line of code (or what map means) but I will try again to find explanations of those issues separately. I removed the u.id, because I was getting the error below (it turns out that removing it still leads to the error below):
Couldn't find Evaluation with 'id'=7 [WHERE "evaluations"."evaluatee_id" = ?]
I can see from the console that the evaluation is saving, but it is not recording the evaluatee_id.
Evaluation.last
Evaluation Load (10.3ms) SELECT "evaluations".* FROM "evaluations" ORDER BY "evaluations"."id" DESC LIMIT 1
=> #<Evaluation id: 7, evaluatee_id: 0, overall_score: nil, project_score: nil, personal_score: nil, remark: "edededededede", work_again?: nil, continue_project?: nil, created_at: "2016-06-10 02:08:44", updated_at: "2016-06-10 02:08:44", evaluator_id: 34>
The error message points to the show action in my evaluations controller, which has:
def show
# #received_evaluations = #user.received_evaluations
#received_evaluation = current_user.received_evaluations.find params[:id]
end
I agree that polymorphic associations are the wrong approach here.
You should do this in your #create method to automatically record evaluations with the correct evaluator:
def create
# change this: #evaluation = Evaluation.new(evaluation_params)
# to this:
#evaluation = current_user.given_evaluations.build(evaluation_params)
# ... the rest is fine as-is ...
end
The current_user comes from Devise and returns whatever user is currently logged in.
I would also make sure that #update does current_user.given_evaluations.find(params[:id]), so that you can't change evaluations you haven't written. Same for #destroy.
Depending on your requirements you may want to access other methods in a similar way.
Your #show method looks wrong to me. It should only find one evaluation:
def show
#received_evaluation = current_user.received_evaluations.find params[:id]
end
Also #index should probably be scoped to the current user. Possibly you want to show evaluations you gave plus evaluations you received:
def index
#given_evaluations = curent_user.given_evaluations
#received_evaluations = curent_user.received_evaluations
end
EDIT: To set the evaluatee_id, just pass it like any other param. First, permit it to come from the front end:
def evaluation_params
params[:evaluation].permit(
:overall_score, :project_score, :personal_score,
:remark, :work_again?, :continue_project?, :evaluatee_id)
end
Then add a widget to your form to let users provide it. For instance it could be a <select> listing all the users:
<%= f.select :evaluatee_id, User.all.map{|u| [u.id, u.name]} %>
This will generate HTML something like this:
<select name="evaluation[evaluatee_id]">
<option value="1">Joe</option>
<option value="2">Sarah</option>
<option value="3">Fred</option>
</select>
In the above, 1, 2, and 3 are the IDs of the users you can choose to evaluate. The contents of each <option> are their names. The browser will submit the option value (the ID) of whichever evaluatee you choose.
Of course you should change u.name to whatever column/method you actually use to name users, e.g. email or full_name or whatever. Also you might want to sort and filter the list---whatever is appropriate for your app.
Also note that using a <select> is just one option. You could have evaluators choose the evaluatee on a prior page, and pass it through the form as a hidden field instead. Or whatever you like! The point is that as long as the front end is sending an evaluation[evaluatee_id], you can save it like any other param.
I'm trying to create a list of items within a "Todo list", however, I'm not sure if I'm doing this correctly with nested attributes. I think using a nested attribute is the right attempt because there's going to be a large list of items, and it will be associated with the correct "Todo list" based on ids.
Example of what the tables might look like when records are populated
Todo table
id list
1 grocery shopping
2 health insurance
Item table
id todo_id name
1 1 buy milk
2 1 buy cereal
3 2 Blue Shield
4 2 Healthnet
5 1 buy cherries
Although, with my attempt below, my application is not saving any of the data into the Item database.
Todo Controller
class TodoController < ApplicationController
def new
#todo = Todo.new
#todo.items.build
end
end
Todo Model
class Todo < ActiveRecord::Base
belongs_to :user
has_many :items
accepts_nested_attributes_for :items
end
Item Model
class Item < ActiveRecord::Base
belongs_to :todo
end
Todo View
<%= simple_form_for(#todo) do |f| %>
<%= f.input :list %>
<%= f.simple_fields_for :items do |g| %>
<%= g.input :name %>
<% end%>
<%= f.button :submit %>
<% end %>
I was able to have the name field show up in my view, but when I save it, it doesn't save into the database, however, I'm able to save the list into the database, and then when I try to edit the record, the name field doesn't show up anymore to be able to edit.
EDIT: to show create method
This is my current Create Method in Todo Controller
def create
#todo = Todo.new(todo_params)
respond_to do |format|
if #todo.save
format.html { redirect_to #todo, notice: 'Todo was successfully created.' }
format.json { render :show, status: :created, location: #todo }
else
format.html { render :new }
format.json { render json: #todo.errors, status: :unprocessable_entity }
end
end
end
Not sure if Edit needs to have something, but I only have this from generating a scaffold of Todo
def edit
end
EDIT 2 show todo_params
def todo_params
params.require(:todo).permit(:user_id, :list)
end
You must add the nested params to your strong params
def todo_params
params.require(:todo).permit(:user_id, :list, items_attributes: [:id, :text, ...])
end
Note about todo_id :
You don't need to add :todo_id in items_attributes list, because you already have the TODO as context.
#todo = Todo.new(todo_params)
In the above code, your todo_params will contain some item_attributes linked to #todo. ie, it's similar to doing
#todo.items.build
It will already create an item with a todo_id corresponding to #todo.id
You need to add the items to the list of whitelisted attributes
def todo_params
params.require(:todo).permit(
:user_id,
:list,
items_attributes: [ # you're missing this
:id,
:name
]
)
end
I have a Rails 4 Application where I have the following code:
my _form_html.erb
<%= nested_form_for #store, :html => {:multipart => true, :honeypot => true} do |f| %>
<%= f.text_field :name %>
<% if params[:action] == "new" %>
<textarea name="store[products_attributes][0][product_fields_attributes][0][text_content]"></textarea>
<% else %>
<textarea name="store[products_attributes][0][product_fields_attributes][0][text_content]">VALUE</textarea>
<% end %>
<%= f.submit%>
<% end %>
My controller looks like:
before_action :set_store, only: [:show, :edit, :update, :destroy]
def new
#store = Store.new
end
def edit
end
def create
#store = Store.new(store_params)
respond_to do |format|
if #store.save
format.html { redirect_to #store, notice: 'Store was successfully created.'}
format.json { render action: 'show', status: :created, location: #store }
else
format.html { render action: 'new' }
format.json { render json: #store.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #store.update(store_params)
format.html { redirect_to #store, notice: 'Store was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #store.errors, status: :unprocessable_entity }
end
end
def set_store
#store = Store.find(params[:id])
end
def store_params
params.require(:store).permit(:name, products_attributes: [:id, { product_fields_attributes: [:id, :text_content] } ])
end
Also my edit.html.erb looks like:
<h3>Edit</h1>
<%= render 'form' %>
and my new.html.erb looks like:
<h3>Add New</h1>
<%= render 'form' %>
and in my rails console when I click "Update" looks like:
Started PATCH "/stores/sNx92thyjcP_jw" for 127.0.0.1 at 2014-05-27 17:10:46 -0600
Processing by StoresController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"nFUg4ynXYyg99rPPPoa3uO/iHP4LT1XlOz3Vm3Zm4Z0=", "store"=>{"name"=>"Testing", "description"=>"", "products_attributes"=>{"0"=>{"type_of"=>"Book", "product_fields_attributes"=>{"0"=>{"text_content"=>"testing testing testing 1"}}}}}, "commit"=>"Update Store", "token"=>"sNx92thyjcP_jw"}
Site Load (0.7ms) SELECT "stores".* FROM "stores" WHERE "stores"."token" = 'sNx92thyjcP_jw' LIMIT 1
(0.2ms) BEGIN
SQL (0.5ms) INSERT INTO "products" ("created_at", "store_id", "type_of", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Tue, 27 May 2014 23:10:46 UTC +00:00], ["store_id", 102], ["type_of", "Book"], ["updated_at", Tue, 27 May 2014 23:10:46 UTC +00:00]]
SQL (0.7ms) INSERT INTO "product_fields" ("created_at", "text_content", "updated_at", "product_id") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Tue, 27 May 2014 23:10:46 UTC +00:00], ["text_content", "testing testing testing 1"], ["updated_at", Tue, 27 May 2014 23:10:46 UTC +00:00], ["product_id", 111]]
(15.5ms) COMMIT
Redirected to http://localhost:3000/products/sNx92thyjcP_jw
Completed 302 Found in 30ms (ActiveRecord: 17.6ms)
My store model:
class Store < ActiveRecord::Base
before_create :generate_token
has_many :products
accepts_nested_attributes_for :products
def to_param
token
end
private
def generate_token
self.token = loop do
random_token = SecureRandom.urlsafe_base64(10, false)
break random_token unless Store.exists?(token: random_token)
end
end
My product model:
class Product < ActiveRecord::Base
belongs_to :store
has_many :product_fields
accepts_nested_attributes_for :product_fields
end
My product fields model:
class ProductField < ActiveRecord::Base
belongs_to :product
mount_uploader :image_content, ImageUploader
end
But when you go to edit the store, instead of updating, it adds a new record. For example, on the new page, you put in the textarea "Testing 1", and then save. Then you go to the edit page and edit the textarea that says "Testing 1" to be "Testing 2", and click save. Now I have two records: "Testing 1" and "Testing 2".
What is going on here? Thanks for all help!
Ok, for some reason you are using the nested_form_for helper, but you are not using nested fields at all, instead you write your html for the nested textarea manually, with a fixed id [0]? This is why it always creates a new nested field. When saving the store, it will check if the given ids exist, and if not (e.g. id 0 never exists), it will create a new record for it.
Using nested fields in rails is actually pretty simple, you should just write
<%= form_for #store, :html => {:multipart => true, :honeypot => true} do |f| %>
<%= f.text_field :name %>
<%= f.fields_for :products do |product| %>
<%= product.text_area :text_content %>
<% end %>
<%= f.submit%>
<% end %>
You are currently not using any dynamic adding (afaik), so you do not need to use the nested_form_for. From the exmaple, I am assuming you always want just one product?
In your controller you will have to change your new action to also create the initial product to make this work.
def new
#store = Store.new
#store.products.build
end
This will add one empty/new product, which you can then fill in.
You are using nested attributs in the model so when editing the name you're creating a new associated model. The ID of the nested model should not be editable.
Check this documentation:
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
It's creating a new instance with new ID.
:update_only
For a one-to-one association, this option allows you to
specify how nested attributes are to be used when an associated record
already exists. In general, an existing record may either be updated
with the new set of attribute values or be replaced by a wholly new
record containing those values. By default the :update_only option is
false and the nested attributes are used to update the existing record
only if they include the record's :id value. Otherwise a new record
will be instantiated and used to replace the existing one. However if
the :update_only option is true, the nested attributes are used to
update the record's attributes always, regardless of whether the :id
is present. The option is ignored for collection associations.
I assumed that my _form_html.erb is actually _form.html.erb partial which you're calling from new and edit view.
Surely your form code is sending request to create action otherwise looking at your code there is no reason it will create new record. You should recheck it. Btw, I dont see any reason to use nested_form_for you can also use form_for field.
Anyway, when you visit the edit action ie /stores/12/edit like path, it should pre-populate all field. check it, may be made mistake in defining routes. Or, you might be sending ajax request in a wrong way. Possibility exist.
One more thing, there is no reason to use if-else condition, since the outcome of both condition seems similar.
<%= nested_form_for #store, :html => {:multipart => true, :honeypot => true} do |f| %>
<%= f.text_field :name %>
<textarea name="store[products_attributes][0][product_fields_attributes][0][text_content]"></textarea>
<%= f.submit%>
<% end %>
Params are not correctly formated, they should have this format:
params = { member: { avatar_attributes: { id: '2', icon: 'sad' } } }
I'ts important to send the id in order to allow rails to search for the associated model. You are sending the "0" as a key instead of value.
Follow this guide to create the form:
http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html
And the documentation about the nested params:
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
Also an alternative:
http://matthewrobertson.org/blog/2012/09/20/decoupling-rails-forms-from-the-database/
I am trying to create an article.
class Article < ActiveRecord::Base
belongs_to :article_skill
attr_accessible :articles_skill_attributes
accepts_nested_attributes_for :articles_skill
end
class ArticlesSkill < ActiveRecord::Base
attr_accessible :description, :name
has_many :articles
end
This is my form in the article/new.html.erb
<%= article_form.fields_for :articles_skill, #article.articles_skill do |b|%>
<label class="medium"><span class="red">*</span> Skill</label>
<%= b.select :id, options_for_select(ArticlesSkill.all.collect{|m| [m.name, m.id]}) %>
<%end%>
Here the article_form is the builder for the #article form object.
If I try to save the #article object its showing this error.
Couldn't find ArticlesSkill with ID=1 for Article with ID=
I've been struggling with this problem for a few days. Did a lot of searching.. it took going to the rails console and searching by the exception being thrown instead to make any progress with this.
Check out this answer on this question for why it's happening, and possible workarounds.
Use rails nested model to *create* outer object and simultaneously *edit* existing nested object?
Be aware that using the first option presented here creates a security hole as described in http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2010-3933
The second parameter in your fields_for call seems unnecessary. ActiveRecord is performing a lookup on the association articles_skill for #article when it reaches that param, but since the #article is new and has yet to be saved, it has no ID and triggers an error.
<%= article_form.fields_for :articles_skill do |b|%>
<label class="medium"><span class="red">*</span> Skill</label>
<%= b.select :id, options_for_select(ArticlesSkill.all.collect{|m| [m.name, m.id]}) %>
<% end %>
I can suggest only a workaround. It works, but I don't like it - I want some out-of-the-box solution.
I assume you have a function:
def articles_skill_params
params.require(:articles_skill).permit(:description, :name,
article_attributes: []) end
Add a function
def articles_skill_params2
params.require(:articles_skill).permit(:description, :name)
end
Add another function:
def set_article
article_id = articles_skill_params[:article_attributes][:id]
article = Article.find(article_id)
#articles_skill.articles << article
#articles_skill.save
end
Change your ArticlesSkillController#create:
def create
#articles_skill = ArticlesSkill.new(articles_skill_params2)
set_article
respond_to do |format|
if #articles_skill.save
format.html { redirect_to #articles_skill, notice: 'Article skill was successfully created.' }
format.json { render :show, status: :created, location: #articles_skill }
else
format.html { render :new }
format.json { render json: #articles_skill.errors, status: :unprocessable_entity }
end
end
end
As you can see, we simply exclude the nested attributes from the parent object creation (thus eliminating the error), then manually add them later.
If you just want people to be able to select an existing skill you don't need nested attributes at all (that's useful for when you might want people to be able to create an article skill from the same form that creates an article). You just want to set article_skill_id to an existing value, so you can just do
<%= form_for(#article) do |f| %>
...
<label class="medium"><span class="red">*</span> Skill</label>
<%= f.select :article_skill_id, ArticlesSkill.all.collect{|m| [m.name, m.id]}) %>
<% end %>