I know there are a couple of different posts on here with folks trying to get their Rails 3 Forms with Nested Models to "work." I've look at a bunch of them, including Subclass Isn't Saving, and Nested Model with Polymorphic Association, along with watching Ryan Bates' Railscasts on Nested Model Forms and his two tutorials on Complex Forms.
That said, I'm stumped as to why the Object in my form isn't saving.
Here are my Models:
course.rb
class Course < ActiveRecord::Base
attr_accessible :course_name, :course_semester, :course_summary, :course_year, :objectives_attributes
has_many :objectives, as: :objectiveable
accepts_nested_attributes_for :objectives, :reject_if => lambda { |a| a[:objective].blank? }, :allow_destroy => true
objective.rb
class Objective < ActiveRecord::Base
attr_accessible :objective
belongs_to :objectiveable, polymorphic: true
My Form:
new.html.erb
<%= form_for [current_user, #course] do |course| %>
<%= course.label "Course Name" %>
<%= course.text_field :course_name, id:"course_name" %>
<%= course.label "Semester" %>
<%= course.select(:course_semester,[['Fall', 'Fall'], ['Spring', 'Spring'], ['Summer', 'Summer'], ['Winter','Winter']] ) %>
<%= course.label "Course Year" %>
<%= course.text_field :course_year, id:"course_year" %>
<p>Course Objectives</p>
<%= course.fields_for :objectives do |objective| %>
<%= objective.text_field :objective, name: "objective" %>
<% end %>
<%= course.label "Course Summary" %>
<%= course.text_area :course_summary, id:"course_summary" %>
<%= course.submit "Save and Return", name: "save_and_return" %>
<%= course.submit "Create a Unit", name: "course_to_unit" %>
<% end %>
My Controller:
courses_controller.rb
class CoursesController < ApplicationController
before_filter :authenticate_user!
load_and_authorize_resource
def new
#course = current_user.courses.new
#course.objectives.build
end
def create
#course = current_user.courses.new(params[:course])
if #course.save && params[:save_and_return]
redirect_to user_path(current_user)
elsif #course.save && params[:course_to_unit]
redirect_to new_course_unit_path(#course)
else
flash[:notice] = "Sorry, there was a mistake with the form"
render 'new'
end
end
My Log:
test.log
Started POST "/users/1/courses" for 127.0.0.1 at 2012-11-27 22:23:00 -0500
Processing by CoursesController#create as HTML
Parameters: {"utf8"=>"✓", "course"=>{"course_name"=>"Physics 1", "course_semester"=>"Fall", "course_year"=>"2012", "course_summary"=>"This is a valid course summary."}, "objective"=>"An objective", "save_and_return"=>"Save and Return", "user_id"=>"1"}
[1m[36mUser Load (0.2ms)[0m [1mSELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1[0m
[1m[35m (0.0ms)[0m begin transaction
[1m[36mSQL (0.7ms)[0m [1mINSERT INTO "courses" ("course_name", "course_semester", "course_summary", "course_year", "created_at", "updated_at", "user_id") VALUES (?, ?, ?, ?, ?, ?, ?)[0m [["course_name", "Physics 1"], ["course_semester", "Fall"], ["course_summary", "This is a valid course summary."], ["course_year", 2012], ["created_at", Wed, 28 Nov 2012 03:23:00 UTC +00:00], ["updated_at", Wed, 28 Nov 2012 03:23:00 UTC +00:00], ["user_id", 1]]
[1m[35m (6.1ms)[0m commit transaction
Redirected to http://www.example.com/users/1
Completed 302 Found in 17ms (ActiveRecord: 7.0ms)
I can see the objective in the parameters hash in my log, but nothing is being inserted in the Objective table when the data is being committed. In my console, I'm able to create objectives using #course.objectives.create or #course.objectives.build, so I know the association is working (plus I've tested the models in Rspec). But I can't figure out what I'm doing wrong here, at the Form level.
I should also note that I'm running this as a test in Rspec, which looks like this:
courses_pages_spec.rb
it "Adds at least one objective to the course" do
expect {
fill_out_course_form_with_valid_info
click_button save_button
}.to change(Course, :count).by_at_least(1)
Course.last.objectives.count.should == 1
end
Although I've also fired up Rails Server, input values in the form and get the same result.
Thanks in advance for your help.
So, the answer was way easier than I thought it'd be. In my Nested Form, I had the following:
<p>Course Objectives</p>
<%= course.fields_for :objectives do |objective| %>
<%= objective.text_field :objective, name: "objective" %>
<% end %>
It turns out that by defining "name" as "objective", I was over-writing the default "name", "course[objectives_attributes][0][objective]", which is where/how the parameter is passed.
According to the Rails Docs,
Form helpers are designed to make working with resources much easier compared to using vanilla HTML.
Forms for models are created with form_for. That method yields a form builder that knows the model the form is about. The form builder is thus able to generate default values for input fields that correspond to model attributes, and also convenient names, IDs, endpoints, etc.
Conventions in the generated field names allow controllers to receive form data nicely structured in params with no effort on your side.
The reason I had the "name" attribute in the first place was to give Capybara something to latch on to in my tests. Guess that wasn't such a good idea :-)
(What I did instead was use Firebug to figure out what the ID of the form field was and test against that instead.)
Related
I am creating a small app to manage my own online portfolio but I can't solve an issue with update.
The main table of my database is works, then there's the tables authors and clients where I set the fields authorName and clientName as unique. Authors and clients can have multiple works, but a work can only have one of each.
In the form where I create works I have a field for the authorName and another one for clientName: if the author doesn't exist in the authors table it gets created, otherwise the existing one gets linked to the work. Same thing with the client. To achieve this I am using first_or_initialize and it works perfectly. Problems start when I try to use the same method inside the update action. These are my models:
Work Model
class Work < ApplicationRecord
has_one :description
belongs_to :author
belongs_to :client
accepts_nested_attributes_for :client
accepts_nested_attributes_for :author
scope :active, lambda {where(:isActive => true)}
scope :descOrder, lambda {order(:date => :desc)}
scope :cover, lambda {where(:isCover => true)}
end
Author Model
class Author < ApplicationRecord
has_many :works
end
Client Model
class Client < ApplicationRecord
has_many :works
end
Quick edit: the models as you see them are exactly as they are in my app. No code has been removed.
This is the form in my view:
<%= form_for(#work, :url => { :controller => "projects", :action => "update"} ) do |f| %>
<%= f.label("title") %>
<%= f.text_field(:title) %>
<%= f.fields_for(:author) do |author| %>
<%= author.label("author") %>
<%= author.text_field(:authorName) %>
<% end %>
<%= f.fields_for(:client) do |client| %>
<%= client.label("client") %>
<%= client.text_field(:clientName) %>
<% end %>
<%= f.label("date") %>
<%= f.date_field(:date) %>
<%= f.submit("update") %>
<% end %>
And this is how I am handling it in the controller:
def edit
#work = Work.find(params[:id])
if #work.client.nil?
#work.build_client
end
if #work.author.nil?
#work.build_author
end
end
def update
#work = Work.find(params[:id])
#work.client = Client.where(clientName: work_params["client_attributes"]["clientName"]).first_or_initialize
#work.author = Author.where(authorName: work_params["author_attributes"]["authorName"]).first_or_initialize
if #work.update(work_params)
flash[:notice] = "work: #{#work.title} updated successfully."
redirect_to(project_path(#work))
else
redirect_to new_project_path
end
end
private
def work_params
params.require(:work).permit(:title, :date, client_attributes: [:id, :clientName], author_attributes: [:id, :authorName])
end
This is the error that I get:
Started PATCH "/projects/21" for ::1 at 2019-10-14 20:00:27 -0700
Processing by ProjectsController#update as HTML
Parameters: {"authenticity_token"=>"rw3X7VJg8CDnOibniv1jKHTVTGp7pjE4ep6xHpHy0Zp8Xv/0uQd6y5xqq629M2FOOQNoYyOAXH//w5/VoeNEOA==", "work"=>{"title"=>"Progetto1", "author_attributes"=>{"authorName"=>"Autore1", "id"=>"34"}, "client_attributes"=>{"clientName"=>"Cliente4", "id"=>"30"}, "date"=>""}, "commit"=>"update", "id"=>"21"}
Work Load (0.7ms) SELECT `works`.* FROM `works` WHERE `works`.`id` = 21 LIMIT 1
↳ app/controllers/projects_controller.rb:42:in `update'
Client Load (0.6ms) SELECT `clients`.* FROM `clients` WHERE `clients`.`clientName` = 'Cliente4' ORDER BY `clients`.`id` ASC LIMIT 1
↳ app/controllers/projects_controller.rb:44:in `update'
Author Load (0.6ms) SELECT `authors`.* FROM `authors` WHERE `authors`.`authorName` = 'Autore1' ORDER BY `authors`.`id` ASC LIMIT 1
↳ app/controllers/projects_controller.rb:45:in `update'
Completed 404 Not Found in 15ms (ActiveRecord: 1.9ms | Allocations: 3805)
ActiveRecord::RecordNotFound (Couldn't find Client with ID=30 for Work with ID=21):
app/controllers/projects_controller.rb:47:in `update'
Even though those record do exist in the database (with those IDs that you see in the error) and their foreign keys are correctly stored in the works table (I checked in mysql).
What I am expecting to achieve is the same behaviour of the new action (that I described in the beginning of my post).
How can I solve this? Thank you!
Small update: if I change first_or_initialize with first_or_create it does create the author (or client), if not existing, but in the same time it still gives me the same error.
After dozens of tests I eventually came up with a sort of solution - it's not "elegant" and probably it's not the best, but at least it works without interruptions:
def edit
#work = Work.find(params[:id])
if #work.client.nil?
#work.build_client
end
if #work.author.nil?
#work.build_author
end
end
def update
#work = Work.find(params[:id])
#work.client = Client.where(clientName: work_params["client_attributes"]["clientName"]).first_or_create
#work.author = Author.where(authorName: work_params["author_attributes"]["authorName"]).first_or_create
if #work.update(update_params)
flash[:notice] = "work: #{#work.title} updated successfully."
redirect_to(project_path(#work))
else
redirect_to new_project_path
end
end
private
def work_params
params.require(:work).permit(:title, :date, client_attributes: [:id, :clientName], author_attributes: [:id, :authorName])
end
def update_params
params.require(:work).permit(:title, :date)
end
Being that first_or_initialize works smoothly with save method, but it gets stuck - at least in my code - with update, I replaced it with first_or_create.
This time, however, I am using another private method update_params which simply ignores :client_attributes and :authors_attributes.
This results in an Unpermitted parameters: :author_attributes, :client_attributes error, as expected, but at least everything goes through and gets updated.
Honestly I am not quite sure that I totally understood why it's working. But it does.
Anyway, if anyone have a better solution to this problem I am totally open to improve it.
I am using a link from an index page that has a group of nested records (row) that I need to update all at once. The link goes to an edit action that I need to make update the attributes of nested records (prisms).
I tried using the simple_form gem methods for nested models. It gives me a field for all of the objects, when I only want one field to enter a value to them all. The builder from that looks usable, but I don't know how to use it to update the fields. Either way, the form isn't right.
I have tried every variation of form_for and fields_for I could find on Google to develop the edit form. It looks like I'm the only one on Earth trying to solve this problem.
This is how I have my routes set up:
resources :gardens, shallow: true do
resources :prisms
resources :rows
Here is how my garden model is now:
class Garden < ApplicationRecord
mount_uploader :picture, ImageUploader
belongs_to :user
has_one :photo
has_many :rows, :dependent => :destroy
has_many :prisms
geocoded_by :address
after_validation :geocode
after_commit :populate_garden!
def id
self[:id]
end
def populate_garden!
# row 0
(0..length-1).each do |i|
Row.create(row_num: i, garden_id: id)
end
end
end
The garden model creates my rows through the populate_garden! method.
Here is the row model:
class Row < ApplicationRecord
belongs_to :garden
has_many :prisms, :dependent => :destroy
accepts_nested_attributes_for :prisms
after_commit :populate_rows
def id
self[:id]
end
def populate_rows
# row 0
(0..garden.width-1).each do |i|
Prism.create(:row_id => self.id, :row_num => self.row_num, :col_num => i, :garden_id => self.garden_id)
end
end
end
The row model creates prisms in the populate_rows method.
Here is the prism model:
class Prism < ApplicationRecord
belongs_to :row
belongs_to :garden
include RankedModel
ranks :column_order
end
Here is the table from my index.html.erb that I click to open the edit action.
<table>
<% #rows.each_with_index do |gardenrow, index| %>
<% #rows.select { | row | row.row_num == index}.each do |row| %>
<td class="prism-cols">
<%= link_to 'Edit Row', edit_row_path(row), class:"dark-link" %>
<br /><i class="fas fa-arrow-down"></i>
</td>
<% end %>
<% end %>
</table>
The row passes nicely into the edit action, and I currently have this incorrect form:
<h1>The row brought into this form page is: </h1>
<%= #row.inspect %>
<div class="container">
<%= simple_form_for #row do |m| %>
<%= m.simple_fields_for :prisms do |p| %>
<%= p.input :crop_name %>
<% end %>
<%= m.button :submit %>
<% end %>
</div>
The rows_controller update method looks like this:
def update
#row = Row.find(params[:row_id])
#row.prisms.build
redirect_to root_path
end
I need one form field for crop_name that will change all of the prisms in the selected row with a single submit. I don't have any problems updating one prism at a time through an edit action on the prism. The difficulty I'm having is working through the nesting of prisms inside of a specific row.
With the help of my mentor below I was able to develop a form that works with the controller to make this work. Here is the updated code for later use with this type of problem.
Here is the form data:
<%= form_tag({controller: "rows", action: "update"}, method: "patch") %>
<%= label_tag(:crop_name, "Crop Name") %>
<%= text_field_tag(:crop_name) %>
<%= hidden_field_tag(:row_id, #row.id) %>
<%= submit_tag("submit") %>
Here is the controller update method:
def update
#row = Row.find(params[:id])
#garden = Garden.find_by_id(:garden_id)
#row.prisms.each do |p|
p.crop_name = params[:crop_name]
p.save!
end
redirect_to :controller => 'gardens', :action => 'show', id: #row.garden_id
end
Thanks for the help. I don't think I could have figured this out from the documentation alone.
If I'm understanding correctly, I think simple_form may be limiting you. A basic ruby form may do what you want. I'm not 100% sure what the best way is to do a simple_form on nested fields but this stackoverflow answer may be able to help more.
Using a basic ruby form
You want a form that has one field. When submitted, it will take the value from the submitted form and update that field for all prisms of that row. I would recommend digging more into the basics of ruby forms for this kind of scenario and then do something like this.
// html.erb
<%= form_tag({controller: "rows", action: "update_prism_crop_name"}, method: "post", class: "nifty_form") %>
<%= label_tag(:crop_name, "Crop name") %>
<%= text_field_tag(:crop_name) %>
<%= hidden_field_tag(:row_id, #row.id) %>
<%= submit_tag("Submit") %>
<% end %>
// rows_controller
def update_prism_crop_name
#row = Row.find(params[:row_id])
#row.prisms.each do |prism|
prism.crop_name = params[:crop_name]
prism.save!
end
# other redirect stuff
end
The form_tag explicitly calls out an action but I have to imagine that you'll need to build a route for this custom action as well.
I haven't tested any of this and I'm a bit rusty in rails but I believe something like this would work.
I am attempting to run a form destined for the app's home page, which uses simultaneously tableless and database-stored variables. I need to run validations on the form. I need to run validations, one in particular which compares two of the tableless values.
My eventual goal is to get the validations running client-side via the following gem:
https://github.com/bcardarella/client_side_validations
Initially though, I want to get the validations working upon form submission.
A first question that arises is which model should hold the mail validation logic. The homePage has to do many things, is not a model and, above all, the search results lead to a different controller.
Subsequently, although some validations are one-off and don't really need to be factored out, I would place the validation logic in app/validators/different_objects_validator.rb
class DifferentObjectsValidator < ActiveModel::Validator
def different_objects
errors.add(:different_objects, "cannot be less than quantity") unless
self.quantity >= self.different_objects
end
end
Form data is:
<%= form_tag result_quote_request_path, :method => :get do %>
Base <%= number_field :quote, :base, :size => 5 %>
Height <%= number_field :quote, :height, :size => 5 %>
Quantity <%= number_field :quote, :quantity, :size => 5, :value => 1 %>
Different source objects <%= number_field :quote, :different_objects, :size => 5, :value => 1 %>
<%= submit_tag "find", :name => nil %>
<% end -%>
Then
class Quote < ActiveRecord::Base
include ActiveModel::Validations
validates :different_objects, different_objects_valid => true
After restarting Thin web server, this results in:
ActionView::Template::Error (undefined method `name' for nil:NilClass):
But this is a bit of a red herring: without the validation, the form generates a search result
I realize many things may be wrong here. I am having trouble getting the proper focus of where the validations are invoked combined with tableless states.
Your error comes from the following:
<%= submit_tag "find", :name => nil %>
Simply do
<%= submit_tag "find" %>
Ok, I've searched high and low, read tutorials, watched videos and I am still not getting any where with this. I've read similar questions here, but questions were more complex or lacked answers - so here goes...
I have models Account and Invoice. When showing an Account, I'd like a link to 'Create new invoice' which relates to that account. (Later I'd actually like a select field to choose an Account when creating an Invoice, but I'll leave that to another excruciation).
Here are my models...
Account:
class Account < ActiveRecord::Base
attr_accessible :name, :invoice
attr_accessible :name, :invoice
has_many :invoices
end
and Invoice:
class Invoice < ActiveRecord::Base
belongs_to :account
attr_accessible :amount_pretax, :amount_total, :date_sent, :project, :status, :tax, :account, :account_id
end
Now, in my /views/accounts/show.html.erb
<p id="notice"><%= notice %></p>
<p>
<b>Name:</b>
<%= #account.name %>
</p>
<%= link_to 'New Invoice', new_invoice_path(:account_id=>#account.id) %>
<%= link_to 'Edit', edit_account_path(#account) %> |
<%= link_to 'Back', accounts_path %>
So, what's happening is, when I click on the New Invoice link it shows the new form, with the account field populated with this weird text: #<Account:0x10fe16bc0> and then when I submit the form I get this error:
ActiveRecord::AssociationTypeMismatch in InvoicesController#create
with this statement: Account(#2281084000) expected, got String(#2267210740)
along with this:
app/controllers/invoices_controller.rb:45:in `new'
app/controllers/invoices_controller.rb:45:in `create'
This is what is in the Invoices Controller:
def new
#invoice = Invoice.new(:account_id => params[:account_id])
respond_to do |format|
format.html # new.html.erb
format.json { render :json => #invoice }
end
end
def create
#invoice = Invoice.new(params[:invoice])
....
end
The above is where I think I'm going wrong, but what to put this those lines is beyond me at the moment. I'm totally a beginner, any help to solve this functionality will surely teach me loads.
Thanks for your time.
When you click the New invoice link on the /views/accounts/show page, I suppose that you want that your new invoice belongs to this account.
So in your form, you don't have to let the user choose an account. You can for example replace the corresponding field by a hidden_field:
<%= f.hidden_field :account_id, :value => params[:account_id] %>
Also in the new action of your controller, replace #invoice = Invoice.new(:account_id => params[:account_id]) by #invoice = Invoice.new
Hope this helps.
you did not post the code of your form, but i guess that you are using a text-field for handling the account association. THIS IS WRONG!
if you use a text-field, then rails will try storing it as a string => Account(#2281084000) expected, got String(#2267210740)
you need to use some kind of relational field like a dropdown or whatever to select one of the accounts that are already there.
there are tons of good examples out there, this might help you: http://railscasts.com/episodes/102-auto-complete-association-revised
I have tried to get this working, looked at multiple tutorials, questions on here tried different things for about a week now and I can't get the view to work correctly.
I have teams of users. A team has_many users and a user belongs_to a team (one team at a time). I know the association works because I got it working using the console (with some help there). I'm not sure how to get it working in the view. Below is the code, please let me know if more is needed.
What am I missing?
_join_team_button
<%= form_for(#user) do |f| %>
<%= f.submit "Join Team", class: "btn btn-large btn-primary" %>
<% end %>
Team Show Page
<%= render 'shared/join_team_button %>
Teams Controller
def show
#team = Team.find(params[:id])
#team_members = #team.users
#user = current_user.users.build if signed_in?
end
Users Controller
def show
#user = User.find(params[:id])
#teams = #user.team
end
I tried to put a complete demonstration of what you are looking for. Let me know if it fits for you.
#FILE: models/team.rb
class Team < AR::Base
has_many :users
end
#FILE: models/user.rb
class User < AR::Base
belongs_to :team
end
#FILE: config/routes.rb
#Here you are defining "users" as a nested resource of "teams"
resources :teams do
resources :users do
member do
put :join
end
end
end
#if you run "rake routes" it will show you the following line along with others
join_team_user PUT /teams/:team_id/users/:id/join(.:format) users#join
#FILE: controllers/team_controller.rb
def show
#team = Team.find(params[:id])
#team_members = #team.users
#user = current_user.users.build if signed_in?
end
#FILE: views/teams/show.html.erb
<% if(#user) %>
<%= form_for #user, :url => join_team_user_path(#team, #user) do |f| %>
<%= f.submit "Join Team", class: "btn btn-large btn-primary" %>
<% end %>
<% end %>
#You dont really need a form for this. You can simply use `link_to` like below
<%= link_to 'Join', join_team_user_path(#team, #user), method: :put %>
#FILE: controllers/users_controller.rb
def join
# params[:id] => is the user_id
#user = User.find(params[:id])
# params[:team_id] => is the team_id
#team = Team.find(params[:team_id])
# Now make the relationship between user and team here.
#user.update_attribute(:team, #team)
end
Update:Based on your comment
Q: Do I create a new user's resource and nest that or do I nest the already establishes user's resource?
Ans: Based on your requirements any resource can be defined both independently or nestedly. But yes you can control that which method will be available in which way. Like in your case, you can allow only join method when your user is nested under team resource.
resources :users, :only=>:join do
member do
put :join
end
end
resource :users
run rake routes with and without :only=>:join option and see differences in available routes.
Q: Will that affect other things?
Ans: If you strictly define your routes following above example, it should not affect other things. You should confirm all the available routes to your application by rake routes.
Q: Should I put my current routes.rb file up there?
Ans: Assuming your current routes.rb will be modified in the above way. Could I answer the question?
Q: Confused about the comments controller?
Ans: Im extreamely sorry. Yes it must be users_controller.rb as the rake routes command is showing. Result of copy and paste from my own example code :P
Q: what should I put there? the build method
Ans: In your case both the user and team is already exists in your database. All you need to do is just setup a relationship. So you can just use update_attribute option. Ive changed the join method. Please check. But yes if want to create new entries you might need build methods.
Sorry for the late reply :)
I figured it out. Still not perfect, but it gets the association working. The team, and the user were already created, I just needed to establish the association, so the build method would not have worked. Here's what I have:
View:
<%= form_for(#user) do |f| %>
<%= f.hidden_field :team_id, :value => #team.id %>
<%= f.submit "Join Team", class: "btn btn-large btn-primary" %>
<% end %>
Teams Controller:
def show
#team = Team.find(params[:id])
#team_members = #team.users
#user = User.find(params[:id]) if signed_in?
end