So I have two models, Reports and Receipts. Each report has many receipts. I used scaffolding to generate all my views and stuff but Im changing things around so that when a user creates a new report or edits one, they can create and edit receipts in the form.
My models are set up:
class Report < ActiveRecord::Base
has_many :receipts, :dependent => :destroy
accepts_nested_attributes_for :receipts, :allow_destroy => true
attr_protected :id
end
class Receipt < ActiveRecord::Base
belongs_to :report
attr_protected :id
validates_presence_of :vendor, :date, :description, :amount, :acctCode
end
I have the form set up to create a new receipt:
<%= form_for #report do |f| %>
....
<%= f.fields_for :receipts, Receipt.new do |receipt| %>
...
<% end %>
<% end %>
But every time I go to save a report, I get a routing error:
No route matches {:action=>"edit", :controller=>"receipts", :report_id=>#<Receipt id: nil, date: nil, vendor: "", description: "", amount: nil, companyCard: false, lobbyingExpense: false, acctCode: "", created_at: nil, updated_at: nil, report_id: 2>}
and my routes are set up as:
resources :reports do
resources :receipts
end
and my controller for receipts has
# GET /receipts/new
def new
#receipt = Receipt.new
respond_to do |format|
format.html # new.html.erb
end
end
# GET /receipts/1/edit
def edit
#receipt = Receipt.find(params[:id])
end
# POST /receipts
def create
#receipt = Receipt.new(params[:receipt])
respond_to do |format|
if #receipt.save
format.html { redirect_to #receipt.Report, notice: 'Receipt was successfully created.' }
else
format.html { render action: "new" }
end
end
end
I havent touched rails in a while so Im not sure what Im doing wrong. But in my older apps (3.1) when I added images to say, blog posts, I didnt even have a controller for images other than to delete them via ajax. The only reason I have a controller here for receipts is because I used scaffolds to generate the views and such.
edit - I should also point out, that if I go to the new receipt view, I get an error on the form tag:
<%= form_for(#receipt) do |receipt| %>
undefined method `receipts_path'
If you are using accepts_nested_attributes_for you don't need an extra controller to manage the records. Of course if you need specific pages like a "show view" for a receipt you need that controller.
To get accepts_nested_attributes_for you need:
A form for your report
use fields_for :receipts in that form
This way you can edit all created receipts for a given report. If you also want to create new receipts you can add a blank receipt with: #report.receipts.build. You can add this call to your new and edit actions.
Note that you edit the receipts in a form for the report. This means, that you should hit the ReportsController and not the ReceiptsController.
If things do not work here is some debugging advice:
execute rake routes to see if everything is defined correctly.
Inspect the generated HTML from form_for(#report). Especially the 'action=""' attribute of the form tag is relevant. It should point to "/reports/X"
EDIT: I created a Gist with all the relevant files to get a nested form working: https://gist.github.com/4420280
Do checkout cocoon gem for nested resources form. This gem has made a work lot easier dealing with nested resources. https://github.com/nathanvda/cocoon
Related
I'm following the Rails tutorial and making changes where appropriate, with the intention that my tutorial project will become a full-fledged production app after the completion of the tutorial.
I've run into a snag with the second model portion of the tutorial. Here is how I've written my second model.
In my policy.rb:
class Policy < ApplicationRecord
has_one :insured
end
In my insured.rb:
class Insured < ApplicationRecord
belongs_to :policy
end
In my routes.rb:
resources :policies do
resource :insured
end
In my insureds_controller.rb:
def create
#policy = Policy.find(params[:policy_id])
# next line is raising the error
#insured = #policy.insured.create(insured_params)
redirect_to #insured
end
private
def insured_params
params.permit(:name, :address, :phone, :email)
end
I've inspected the #policy object with render plain: #policy.inspect and can confirm that ActiveRecord is retrieving the policy correctly. When I inspect the attributes of #policy, using render plain: #policy.attribute_names.inspect, I don't see an insured attribute, which I thought Rails was supposed to automatically manage for me. In the tutorial, an article has_many :comments, and a comment is supposedly easily created and associated with the parent article with this call: #article.comments.create(comment_params). I also noticed that the tutorial uses params.require(:comment).permit(...) while I have to use params.permit(...), after inspecting the params hash I saw that the :insured attributes existed in the top-level of the hash, instead of being tied to an :insured key within the hash.
I tried manually saving and assigning the #insured object like so:
def create
#policy = Policy.find(params[:policy_id])
#insured = Insured.new(insured_params)
if #insured.save
#policy.insured = #insured
redirect_to #insured
end
end
Only to run into the following error in my .../insureds/new.html.erb:
<h1>New Insured</h1>
<h1><%= #policy.policy_number %></h2>
<%= render 'form' %>
<%= link_to 'Cancel', policy_path(#policy) %>
Which derives from my partial form .../insureds/_form.html.erb:
# the following line raises the error
<%= form_with model: #insured, local: true do |form| %>
# html omitted for brevity
<% end %>
Error: 'undefined method insureds_path'. This is weird because when I inspect the HTML I can see the form action for this view is /policies/[:id]/insured.
Sorry for the massive wall of text, I wanted to show you guys that I did try to figure out what is going wrong.
There is an error in your config/routes.rb file:
resources :policies do
# change it for:
collection do
get 'insured', to: 'policies#show_insured', as: 'show_policy_insured'
# maybe unnecessary to be here
# get 'insured/new', to: 'insureds#new', as: 'new_policy_insured'
# post 'insured/create', to: 'insureds#create', as: 'create_policy_insured'
# delete 'insured/delete', to: 'insureds#delete', as: 'delete_policy_insured'
end
end
# add resources here
resources :insureds
In policy_controller.rb:
def show_insured # 'policy/:id/insureds/
end
In insureds_controller.rb:
def show # '/insureds/:id'
end
def create
...
redirect_to show_policy_insured && return if #insured_policy
end
# before_filter or before_action
#policy = Policy.find(params[:id])
#insured_policy = #policy.insured
Check it and run this to see your routes:
$ bundle exec rake routes
get /policies/:id/insured => 'policies_controller#show_insured'
get /insureds/:id => 'insureds_controller#show'
get /insured/new => 'insureds_controller#new'
post /insureds/create => 'insureds_controller#create'
delete /insureds/:id/delete => 'insureds_controller#delete'
#maguri, that's not all necessary. The stumbling block I was running into was that Rails couldn't automatically determine the correct routes. When I provided my own urls in the form_with declarations, everything went smoothly.
Observe the following change in my _form.html.erb for the Insured model, which belongs_to Policy, which has_one Insured.
<%= form_with model: #insured, url: policy_insured_path(#policy) local: true do |form| %>
In my updated insureds_controller.rb file, using #Phlip's suggestion:
def create
#policy = Policy.find(params[:policy_id])
#insured = #policy.create_insured(insured_params)
if #policy.insured.save
redirect_to policy_insured_path(params[:policy_id])
else
render 'new'
end
end
This allows me to keep routes.rb clean and simple:
resources :policies do
resource: insured
end
Thank you for your answer, it helped me discover the problem was with my routes.
I have two entities with a many-to-one relationship. User has many Addresses. When creating a User I want the form to also create a single Address. The entities are nested.
Approach 1:
The code below works, but only saves the User, no associated Address.
Reading around, I thought that the accepts_nested_attributes_for would automatically save the address. I'm not sure, but it may be that this isn't working because the parameters I'm getting into the Controller don't actually appear to be nested, ie. they look like:
"user"=>{"name"=>"test"}, "address"=>{"address"=>"test"}
Rather than being nested like this:
"user"=>{"name"=>"test", "address"=>{"address"=>"test"} }
I assume this could be due to something wrong in my form, but I don't know what the problem is...
Approach 2:
I have also tried changing the controller - implementing a second private method, address_params, which looked like params.require(:address).permit(:address), and then explicitly creating the address with #user.address.build(address_params) in the create method.
When tracing through this approach with a debugger the Address entity did indeed get created successfully, however the respond_to do raised an ArgumentError for reasons I don't understand ("respond_to takes either types or a block, never both"), and this rolls everything back before hitting the save method...
[EDIT] - The respond_to do raising an error was a red herring - I was misinterpreting the debugger. However, the transaction is rolled back for reasons I don't understand.
Questions:
Is one or the other approach more standard for Rails? (or maybe neither are and I'm fundamentally misunderstanding something)
What am I doing wrong in either / both of these approaches, and how to fix them so both User and Address are saved?
Relevant code below (which implements Approach 1 above, and generates the non-nested params as noted):
user.rb
class User < ApplicationRecord
has_many :address
accepts_nested_attributes_for :address
end
address.rb
class Address < ApplicationRecord
belongs_to :user
end
users_controller.rb
class UsersController < ApplicationController
# GET /users/new
def new
#user = User.new
end
# POST /users
# POST /users.json
def create
#user = User.new(user_params)
respond_to do |format|
if #user.save
format.html { redirect_to #user, notice: 'User was successfully created.' }
format.json { render :show, status: :created, location: #user}
else
format.html { render :new }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
private
def user_params
params.require(:user).permit(:name, address_attributes: [:address])
end
end
_form.html.erb
<%= form_for(user) do |f| %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<%= fields_for(user.address.build) do |u| %>
<div class="field">
<%= u.label :address %>
<%= u.text_field :address %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
UPDATE 1:
After making the changes suggested by #Ren, I can see that the parameters look more like what I would've expected for nested resources:
"user"=>{"name"=>"test", "addresses_attributes"=>{"0"=>{"address"=>"test"}}}
However, when trying to save the user, the transaction is still rolled back for reasons I don't understand. The output I get from the users.new page is:
2 error prohibited this user from being saved:
Addresses user must exist
Addresses user can't be blank
However, using byebug, after the #user = User.new(user_params) call, things look as I would expect them:
(byebug) #user
#<User id: nil, name: "test", created_at: nil, updated_at: nil>
(byebug) #user.addresses
#<ActiveRecord::Associations::CollectionProxy [#<Address id: nil, user_id: nil, address: "test", created_at: nil, updated_at: nil>]>
Obviously the user.id field is not set until the record is written to the DB, so equally the address.user_id field cannot be set until user is saved, so maybe this is caused by some sort of incorrect ordering when ActiveRecord is saving to the database? I will continue to try to understand what's going on by debugging with byebug...
UPDATE 2:
Using rails console to test, saving User first and then adding the Address works (both records get written to the DB, although obviously in 2 separate transactions):
> user = User.new(name: "consoleTest")
> user.save
> user.addresses.build(address: "consoleTest")
> user.save
Saving only once at the end results in the same issues I'm seeing when running my program, ie. the transaction is rolled back for some reason:
> user = User.new(name: "consoleTest")
> user.addresses.build(address: "consoleTest")
> user.save
As far as I can tell from debugging with rails console, the only difference between the state of user.addresses in these two approaches is that in the first address.user_id is already set, since the user.id is already known, while as in the second, it is not. So this may be the problem, but from what I understand, the save method should ensure entities are saved in the correct order such that this is not a problem. Ideally it would be nice to be able to see which entities save is trying to write to the DB and in which order, but debugging this with byebug takes me down an ActiveRecord rabbit-hold I don't understand at all!
UPDATE: As opposed to previous versions, Rails 5 now makes it required that in a parent-child belongs_to relationship, the associated id of the parent must be present by default upon saving the child. Otherwise, there will be a validation error. And apparently it isn't allowing you to save the parent and child all in one step... So for the below solution to work, a fix would be to add optional: true to the belongs_to association in the Address model:
class Address < ApplicationRecord
belongs_to :user, optional: true
end
See my answer in a question that branched off from this one:
https://stackoverflow.com/a/39688720/5531936
It seems to me that you are mixing up the singular and plural of your address object in such a way that is not in accordance with Rails. If a User has many addresses, then your Model should show has_many :addresses and accepts_nested_attributes_for should have addresses:
class User < ApplicationRecord
has_many :addresses
accepts_nested_attributes_for :addresses
end
and your strong params in your controller should have addresses_attributes:
def user_params
params.require(:user).permit(:name, addresses_attributes: [:id, :address])
end
Now if you want the User to just save One Address, then in your form you should have available just one instance of a nested address:
def new
#user = User.new
#user.addresses.build
end
By the way it seems like your form has fields_for when it should be f.fields_for:
<%= f.fields_for :addresses do |u| %>
<div class="field">
<%= u.label :address %>
<%= u.text_field :address %>
</div>
<% end %>
I highly recommend that you take a look at the Rails guide documentation on Nested Forms, section 9.2. It has a similar example where a Person has_many Addresses. To quote that source:
When an association accepts nested attributes fields_for renders its
block once for every element of the association. In particular, if a
person has no addresses it renders nothing. A common pattern is for
the controller to build one or more empty children so that at least
one set of fields is shown to the user. The example below would result
in 2 sets of address fields being rendered on the new person form.
def new
#person = Person.new
2.times { #person.addresses.build}
end
I was wondering whether anybody felt kind enough to help me figure out why this isn't working.
I have a Model lets call it Task which belongs to a Project Model. I basically want each Task to have a unique name per project (Project1 could have a task called task1 and so could Project2 but both could only have one called task1) . This seems to be what the :scope option is for but it doesn't seem to be working for me.
The task model is a nested resource within project and as such I call the create action via project_tasks_path(#project). It works fine creating tasks and assigning them to projects but the scope of the uniqueness validation is not taking hold. If I create a task task1 in Project1 I can't create one with the same name in task 2.
This is my setup:
Task.rb
class Task < ActiveRecord::Base
belongs_to :project
validates :name, presence: true, uniqueness: {scope: :project_id}
tasks_controller.rb
def create
#project = Project.find_by(id: params[:project_id])
#task = Task.new(model_params)
#print task to stdout
puts "#task"
ap #task
respond_to do |format|
if #task.save
flash[:notice] = "Successfully created task"
format.js
else
# no flash as form handles errors
format.js { render action: 'new' }
format.html { render action: 'new' }
end
end
end
for some reason when I output the contents of the newly created task, I get the following
#<Task:0x007ff7c7c3b178> {
:id => nil,
:name => "test",
:project_id => nil,
:created_at => nil,
:updated_at => nil
}
It seems that because project_id hasn't been set at this point it's using 'nil' as the value.
What's the best way to get around this? would it just be a custom validator?
Edit 1
def model_params
params.require(:model).permit(:name, :project_id)
end
Right, having been playing around with this, it seems that the way to make this type of validation is pretty straight forward. All it requires is that the nested resource be built in relation to it's project, this forces the :parent_id to be passed through to the validation as expected.
In the case of this toy example, that means that the create action has to look something like:
#project = Project.find_by(id: params[:project_id])
#task = #project.tasks.build(model_params)
It should be noted that because of Rails not supporting generation of nested resources from the command line, the way that the scaffold generated controllers handle creation is by Model.new(model_params) and then saving, this doesn't seem to pick up the :parent_id in time for the validation and so will need changing as above (in terms of the parent).
I'm on Rails 4. Let's say I have three models: House, Color, and HouseColoring.
class House < ActiveRecord::Base
has_many :house_colorings
has_many :colors, through: :house_colorings
accepts_nested_attributes_for :house_colorings, allow_destroy: true
end
class Color < ActiveRecord::Base
has_many :house_colorings
has_many :houses, through: :house_colorings
end
class HouseColoring < ActiveRecord::Base
belongs_to :house
belongs_to :color
end
houses_controller.rb:
class HousesController < ApplicationController
before_action :set_house
...
def new
#house = House.new
#house.house_colorings.build
end
def create
#house = House.create(house_params)
if #house.save
redirect_to #house
else
render 'new'
end
end
def edit
#Gets #house from set_house
end
def update
if #house.update(house_params)
redirect_to #house
else
render 'edit'
end
end
...
private
def set_house
#house = House.find(params[:id])
end
def house_params
params.require(:house).permit(:some_parameters, house_colorings_attributes: [:id, :color_id])
end
end
Here is my _form.html.erb partial for my house new and edit
<%= form_for #house do |f| %>
<div id="house_colorings">
<%= f.fields_for :house_colorings do |c| %>
<%= render "house_colorings", f: c %>
<% end %>
<%= link_to "Add color", add_color_path, remote: true %>
</div>
<% end %>
_house_colorings.html.erb:
<%= f.collection_select :color_id, Color.all, :id, :name, {include_blank: "Select color"} %>
In the houses_controller, I've added:
def add_color
respond_to do |format|
format.js
end
end
add_color.js.erb:
$("#house_colorings").append("<%= escape_javascript render 'house_colorings', f: c %>");
I added a route for my add_color method:
GET "/add_color" => "houses#add_color"
When I click my add color link, nothing happens on screen, but in my log I get a 500 internal server error.
Started GET "/add_color" for 127.0.0.1 at 2013-10-26 21:11:41 -0700
Processing by HousesController#add_color as JS
Rendered houses/add_color.js.erb (11.3ms)
Completed 500 Internal Server Error in 14ms
ActionView::Template::Error (undefined local variable or method `f' for #<#<Class:0x007fc317428538>:0x007fc31710d060>):
1: $("#house_colorings").append("<%= escape_javascript render 'house_colorings', f: c %>");
app/views/houses/add_color.js.erb:1:in `_app_views_houses_add_color_js_erb__1847085463095078116_70237941180700'
As of now, I only have one field to add a house_coloring to my house. I want to add some ajax, and have a link in my form that adds a new field after the one that is there, but I'm not sure how to do this.
I've gone through the "Nested model form" from Railscasts and used parts of them to get to the point I am now, but I would like to use the "data_remote" helpers provided by rails if I can. I've edited my question and included the log for the error I'm getting when I click my add color link. I'm pretty sure that I need to change either my add_color.js.erb or the add_color action in my houses controller.
Any suggestions?
Well, you do have couple alternatives here.
Use the information in the Nested model forms railscasts: Part 1 and Part 2
Use the FormObject Pattern to make thet nesting a bit easier. The pattern is described in a dozen places and also on railscasts (subscribtion needed).
Use a js framework like Angular.js to add new fields on the client side on the fly. Angular.js is also covered in a railscast (subscribtion needed) and has very rich documentation.
UPDATE
The error tells you pretty much all of it. You send a c object to the partial as a form-builder object. And seems like you don't instantiate it in the houses#add_color action.
Check out these two railscasts episode:
Nested Forms 1
Nested Forms 2
The 2nd one explains in depth what you're exactly looking for.
I am trying to get a basic form to work and am struggling because I keep getting the error
undefined method `profiles_index_path' for #<#<Class:0x4fe1ba8>:0x4fccda0>
I have checked through and can't seem to work out where I am going wrong.
In my view (new.html.erb) I have:
<%= form_for #profile do |f| %>
<%= f.text_field :name %>
<%= f.text_field :city %>
<%= f.text_field :country %>
<%= f.text_field :about %>
<%= f.submit "Create Profile" %>
<% end %>
In my profiles controller I have:
class ProfilesController < ApplicationController
def new
#title = "New Profile"
#profile = Profiles.new
end
def create
#user = current_user
#profile = #user.profiles.new(params[:profile])
if #profile.save
redirect_to profile_path, :notice => "Welcome to your new profile!"
else
render "profiles#new"
end
end
def edit
#user = current_user
#profile = #user.profiles.find(params[:id])
end
def update
#title = "Update Profile"
#user = current_user
#profile = #user.profiles.find(params[:id])
if #profile.update_attributes(params[:profile])
redirect_to profile_path
else
render action: "edit"
end
end
def index
#user = current_user
#profile = #user.profiles.all
#title = "Profile"
end
end
And finally in my profiles model I have
class Profiles < ActiveRecord::Base
belongs_to :user
end
Any help people can offer really would be much appreciated because I am stumped. :)
Sorry forgot to include routes:
controller :profiles do
get "newprofile" => "profiles#new"
get "updateprofile" => "profiles#update"
get "profile" => "profiles#home"
end
resources :profiles, :controller => 'profiles'
The problem is indeed the way you've pluralized your model name. Don't do that. It should be a Profile, not a Profiles. There my be some work around to allow you to use a plural model name, but the answer is to stick to Rails convention rather than fighting the framework. Rename your model to Profile and the url_for helpers will understand how to correctly turn a new Profile object into a /profiles URL.
If you run "rake routes" command, do "profiles_index" appear in your routes? Usually for the index page of a model, the work 'index' is left out so the route is profiles_path
You error probably comes from a view where you've used profiles_index_path instead of profiles_path
I think it's failing due to the convention not being followed with your model name.
So I think you're problem is mostly around that you aren't following the convention on the model name, which would classically be singular, since each instance represents one profile. I think the form_for helper is trying to figure out what to do with it and failing as a result. So you have two options to try and resolve. Refactor the model name to singular (I'm not clear exacly how difficult that would be) or pass the :url paramater to form_for so it knows where to post to.
<% form_for #profile, :url => path_to_create_action do |f| %>
more information here:
I'm working with Rails 5 and I got the same error and it was specific using the word Media as my model and RoR used Medium as the plural so I got different routes when executing rake routes.
What I did to fix it was:
Delete the model I just have created.
rails d scaffold Media
Edit config/initializers/inflections.rb with:
ActiveSupport::Inflector.inflections(:en) do |inflect|
# Here you can put the singular and plural form you expect
inflect.irregular 'media', 'medias'
end
Now execute the scaffold again:
rails g scaffold Media
Now you must have everything in the way you expected. Because you have overwritten the Pluralizations and Singularizations (Inflections) in Ruby on Rails.
I hope it could be useful.
Have you tried to replace your form_for tag with the following?
<%= form_for #profile, :as => :post do |f| %>
It looks like it's trying to treat it as a GET request to "/profile". And, since it is not finding the index action, it craps out. I think forcing it to do a POST will fix this issue.