how to update nested attribute in form - ruby-on-rails

I wanna update the nested attribute but failed, for example there is a Article, and a book has many comments. when I find the comment I have written has some mistakes, so I wanna modify it.
here is my code.
In code_snippet.rb:
class CodeSnippet < ApplicationRecord
has_many :annotations, dependent: :destroy
accepts_nested_attributes_for :annotations ,update_only: true ,reject_if: :all_blank, allow_destroy: true
end
In annotation.rb:
class Annotation < ApplicationRecord
belongs_to :code_snippet
end
In code_snippet_controller.rb:
def edit
#code_snippet = CodeSnippet.find(params[:id])
end
def update
#code_snippet = CodeSnippet.find(params[:id])
if #code_snippet.update(code_snippet_params)
redirect_to #code_snippet
else
render 'edit'
end
end
private
def code_snippet_params
params.require(:code_snippet).permit(:snippet)
end
In annotation.rb:
def edit
#code_snippet = CodeSnippet.find(params[:code_snippet_id])
#annotation = #code_snippet.annotations.find(params[:id])
end
def update
#code_snippet = CodeSnippet.find(params[:id])
#annotation = #code_snippet.annotations.find(params[:id])
if #annotation.update(annotation_params)
redirect_to #code_snippet
else
render 'edit'
end
end
In 'views/code_snippets/show.html.rb'
<div>
<h2>Annotations</h2>
<%= render #code_snippet.annotations %>
</div>
In 'views/annotations/_annotation.html.erb'
<p>
<strong>User:</strong>
<%= annotation.user %>
</p>
<p>
<strong>Line:</strong>
<%= annotation.line %>
</p>
<p>
<strong>Body:</strong>
<%= annotation.body %>
</p>
<p>
<%= link_to "Edit", edit_code_snippet_annotation_path(annotation.code_snippet,annotation) ,controller: 'annotation'%>
</p>
In 'views/annotations/edit.html.erb':
<%= form_for(#code_snippet) do |f| %>
<%= f.fields_for :annotation,method: :patch do |builder| %>
<p>
<%= builder.label :user %><br>
<%= builder.text_field :user %>
</p>
<p>
<%= builder.label :line %><br>
<%= builder.text_field :line %>
</p>
<p>
<%= builder.label :body %><br>
<%= builder.text_area :body %>
</p>
<p>
<%= builder.submit %>
</p>
<% end %>
<% end %>
what I wanna update the annotation without change the codesnippets. what should I do to change my code.

So.... There's a lot going on here so I'm going to start by suggesting a careful look at the docs
Firstly, let's look at your form:
CodeSnippet has_many :annotations
So your fields_for statement should be for :annotations, not :annotation. The fields for statement also should not take a method options key.
Next your code_snippets_controller:
As indicated by the docs, the parameter sent back from the nested attributes form will be under the key: annotations_attributes and will contain a hash of arrays.
You need to allow this attribute, and any attribute you wish to pass onto the annotation model with strong parameters:
params.require(:code_snippet).permit(annotations_attributes: [:some, : permitted, :params])
I believe this is all you need to get your example working. But, if you run into more troubles, I recommend a spending some quality time with a few binding.pry statements to introspect on the actual behaviour of your code.

Related

In Rails 5 how can I create records in separate models with a single controller action?

I'm working through a Rails tutorial creating a database of James Bond movies. I have two models, movie.rb and theme_song.rb. The two have the following association:
class Movie < ApplicationRecord
has_one :theme_song
end
class ThemeSong < ApplicationRecord
belongs_to :movie
end
I've also represented this association in my routes.rb file:
resources :movies do
resources :theme_songs
end
I need to enable a user to add the details of an additional movie into the database including general movie information and information about the theme song. The general information belongs in the Movie model, and the theme song information belongs in the ThemeSong model, as shown in the migrations below:
create_table :movies do |t|
t.string :title
t.datetime :release_date
t.text :plot
t.timestamps
end
create_table :theme_songs do |t|
t.string :song
t.string :artist
t.references :movie, foreign_key: true
t.timestamps
end
I have a movies_controller.rb controller and a new.html.erb view linked to the controller. The new.html.erb view has the following form coded into it:
<%= form_with model: #movie, local: true do |form| %>
<p><strong>Movie Details</strong></p>
<p>
<%= form.label 'Title:' %>
<%= form.text_field :title %>
</p>
<p>
<%= form.label 'Release date:' %>
<%= form.text_field :release_date %>
</p>
<p>
<%= form.label 'Plot:' %>
<%= form.text_area :plot %>
</p>
<p><strong>Soundtrack</strong></p>
<p>
<%= form.label 'Theme song:' %>
<%= form.text_area :song %>
</p>
<p>
<%= form.label 'Artist:' %>
<%= form.text_area :artist %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
I need users to be able to fill out that form, submit it, and my controller's create action to add a new record in Movie with the general information, add a new record in ThemeSong with the soundtrack information, with both records linked in the association.
This is the code I've got so far in the relevant methods of my movies_controller.rb:
def create
#movie = Movie.new(movie_params)
if #movie.save
redirect_to #movie
else
render 'new'
end
end
private
def movie_params
params.require(:movie).permit(:title, :release_date, :plot, :song, :artist)
end
Can anyone tell me what code I need to have in my create/movie_params methods? Or if I'm going about it in completely the wrong way and if so, how I should implement this?
I'm using Rails 5.1.4
There is no information provided for the theme_song model, what fields need to be created or what data, but you probably don't want the data duplicated in both tables.
You're not going the wrong way, it might be easiest to add both sets of data to a single form uses accepts_nested_attributes. Then tell your movies_controller params to also accept the nested traits. Then it's rails magic to save the data in corresponding fashion.
1 Add the nesting option to the movie model
class Movie < ApplicationRecord
has_one :theme_song
accepts_nested_attributes_for :theme_song
end
2 Nest the forms together
<%= form_with model: #movie, local: true do |form| %>
<p><strong>Movie Details</strong></p>
<p>
<%= form.label 'Title:' %>
<%= form.text_field :title %>
</p>
<p>
<%= form.label 'Release date:' %>
<%= form.text_field :release_date %>
</p>
<p>
<%= form.label 'Plot:' %>
<%= form.text_area :plot %>
</p>
<p><strong>Soundtrack</strong></p>
<%= form.fields_for :theme_song do |theme_song| %>
<p>
<%= theme_song.label 'Theme song:' %>
<%= theme_song.text_area :song %>
</p>
<p>
<%= theme_song.label 'Artist:' %>
<%= theme_song.text_area :artist %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
3 Update Controller Params + Create new object
def create
#movie = Movie.new(movie_params)
if #movie.save
redirect_to #movie
else
render 'new'
end
end
def new
#movie = Movie.new
#movie.build_theme_song
end
private
def movie_params
params.require(:movie).permit(:title, :release_date, :plot, :song, :artist, theme_song_attributes: [:song, :artist, :id])
end
Two things to note here, the first is that in the new action you need to make sure to build the nested record. Secondly the nested attributes are added to the params in the form of an array, it has to be this way.
SideNote
Using this method has it's limitations. You should know that this means that if two different movies use the same Theme Song, you'll have 2 movie records and 2 theme song records. That might make it slower/harder to say "show me all the movies that this song is in", because then you have to account for user error and spelling in searching by name. You might find, that in the future, it would be better to have a Movie record, a Theme Song record and a MovieThemeSongs record, which essentially joins the two.
What you need to use is accepts_nested_attributes_for which allows you to have a nested form for theme_song inside your movie form:
Add it to your Movie model:
class Movie < ApplicationRecord
has_one :theme_song
accepts_nested_attributes_for :theme_song
end
in your movies controller:
def new
#movie = Movie.new
#movie.build_theme_song
end
private
def movie_params
params.require(:movie).permit(:title, :release_date, :plot, :song, :artist, theme_song_attributes: [:song, :artist])
end
finally, inside your movie form use fields_for helper to add fields for the movie's theme_song:
<%= form_for #movie do |f| %>
theme song:
<ul>
<%= f.fields_for #movie.theme_song do |theme_song_form| %>
<li>
<%= theme_song_form.label :song %>
<%= theme_song_form.text_field :song %>
...
</li>
<% end %>
</ul>
<% end %>
Now, when you submit the form it will create a movie record and theme_song record for that movie.

undefined method `reviews' for nil:NilClass

I'm trying to create a reviews model for company pages. For this I have:
Models
user.rb
has_many :reviews
class Review < ActiveRecord::Base
belongs_to :user
belongs_to :company
end
class Company < ActiveRecord::Base
has_many :reviews
end
My reviews controller is:
def create
#company = Company.find_by_slug(params[:id])
#review = #company.reviews.create(params[:review])
#review.save
redirect_to company_path(#company)
end
and I have this code in the company show page:
<% #company.reviews.each do |review| %>
<p>
<strong>Title:</strong>
<%= review.title %>
</p>
<p>
<strong>Avantage:</strong>
<%= review.avantage %>
</p>
<p>
<strong>Inconvenient:</strong>
<%= review.inconvenient %>
</p>
<% end %>
</br>
<%= form_for([#company, #company.reviews.build]) do |f| %>
<div class="field">
<%= f.label :title %><br>
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.label :avantage %><br>
<%= f.text_area :avantage %>
</div>
<div class="field">
<%= f.label :inconvenient %><br>
<%= f.text_area :inconvenient %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
However, when I go to a specific company page and try to create a review for this company I'm getting this error message undefined method reviewsfor nil:NilClass
Instead of #company = Company.find_by_slug(params[:id]) use this code #company = Company.friendly.find(params[:company_id])
There are a couple of things you may find useful:
If you're using Rails 4, you may encounter a further problem. In the third line of your create method, you are using unsecure params directly in a .create call. Check out the Rails Guide page on "strong params".
If you implement strong parameters as mentioned above, you should probably deliberately omit the company_id field from the list of permitted params.
Assuming your users are allowed to write a review for any company in your system, it might be simpler for you to embed the company_id as a hidden field in your form. This would allow you to also simplify the controller method. For example:
# _form.html.erb
<%= form_for(#review) do |f| %>
<%= f.hidden_field :company_id, value: #company.id %>
...bla bla bla
<% end %>
Then, in your reviews_controller...
# reviews_controller.rb
def create
#review = Review.new(approved_params)
if #review.save
flash[:success] = 'Review created!'
else
flash[:error] = "Review wasn't saved"
end
#company = #review.company
redirect_to #company
end
def approved_params
params.require(:review).permit(:title, :avantage, :inconvenient, :company_id)
end
In your companies_controller, you should add this to your show method
# companies_controller.rb
def show
#company = Company.find(params[:id]
# add this line below...
#review = Review.new
end
I hope this helps.

Nested form shows too many fields! Rails Beginner

I as Rails Beginner created an simple demo app to experiment with nested forms.
But somehow my code shows strange byproducts:
My only aim was to create new treatments for patients on the patients show page, and now
it show input fields with yet created treatments and some other crazy stuff!! What did i wrong? My steps so far:
rails new hama
cd hama
rails g scaffold Patient name:string
rails g model Treatment content:string
rake db:migrate
Patient model:
attr_accessible :name, :treatments_attributes
has_many :treatments, :dependent => :destroy
accepts_nested_attributes_for :treatments
Treatment model:
attr_accessible :content
belongs_to :patient
In patient/show:
<b>Name:</b>
<%= #patient.name %>
</p>
<p>
<b>Treatments:</b>
<%= #patient.treatments.each do |treatment| %>
<%= treatment.content %>
<% end %>
</p>
<%= form_for #patient do |f| %>
<%= f.fields_for :treatments do |ff| %>
<%= ff.text_field :content %>
<% end %>
<%= f.fields_for :treatments do |ff| %>
<%= ff.text_field :content %>
<% end %>
<%= f.submit %>
<% end %>
And in Patient controller:
def show
#patient = Patient.find(params[:id])
treatment = #patient.treatments.build
respond_to do |format|
format.html # show.html.erb
format.json { render json: #patient }
end
end
Are you talking about where it shows all the internals of your Treatment objects?
Change this:
<%= #patient.treatments.each do |treatment| %>
to this:
<% #patient.treatments.each do |treatment| %>
Using <%= %>, with the =, means to output the result of that Ruby line on to the page. Without it, it's just code that Ruby runs.
Firstly you should remove the = from this line:
<%= #patient.treatments.each do |treatment| %>
You don't want to display the output of the each. The loop contents provide the output. Just use:
<% #patient.treatments.each do |treatment| %>
All the other fields are output since that's what your code asks for. This part of your code is showing all of the same fields twice:
<%= f.fields_for :treatments do |ff| %>
<%= ff.text_field :content %>
<% end %>
<%= f.fields_for :treatments do |ff| %>
<%= ff.text_field :content %>
<% end %>
If there are two specific input fields for a treatment, then there needs to be two different attributes.

Forms with nested models

I'm trying to make a site that has a form with one model ("Requests") nested in another ("Orders"). Specifically, I'd like the orders/new page to have a form composed of requests that a user can fill and then submit, so they're all associated with the order that is created.
On someone here's suggestion, I looked into the Railscast on the topic (#196), and I've run into a problem I can't figure out, so I figured I'd ask. The problem is, I'm following his directions, but the request forms just aren't showing up. The form fields associated with the Order model ARE showing up, however, and I'm really confused, cause I basically copied the Railscast verbatim. Thoughts?
Here's my form code. It's specifically the part in the fields_for tag that isn't showing up on my site:
<%= form_for(#order) do |f| %>
<% if #order.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#order.errors.count, "error") %> prohibited this order from being saved:</h2>
<ul>
<% #order.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :user_name %><br />
<%= f.text_field :user_name %>
</p>
<p>
<%= f.label :user_email %><br />
<%= f.text_field :user_email %>
</p>
<% f.fields_for :requests do |builder| %>
<p>
<%= builder.label :item, "Item" %><br />
<%= builder.text_field :item %>
</p>
<p>
<%= builder.label :lbs, "LBs" %> <br />
<%= builder.text_field :lbs %>
</p>
<% end %>
<p><%= f.submit "Submit Order" %></p>
<% end %>
And here's my code for the two models, Order first:
class Order < ActiveRecord::Base
attr_accessible :order_status, :price_estimate, :price_final, :user_email, :user_name, :user_phone
has_many :requests, :dependent => :destroy
accepts_nested_attributes_for :requests
end
class Request < ActiveRecord::Base
attr_accessible :item, :lbs, :notes, :order_id, :status
belongs_to :order
end
Finally, in case it's relevant, the bit I added, per the cast's suggestions, to my Order controller create action:
def new
#order = Order.new
3.times { #order.requests.build }
respond_to do |format| # this part I copied from a tutorial. But when I commented it out, it didn't change anything, so I don't think this is the problem
format.html # new.html.erb
format.json { render json: #order }
end
end
Help? I'm super confused by this. I'm guessing the problem might have something to do with the "builder," but I'm new to this, and an hour or so of fiddling around hasn't yielded much.
Really appreciate any help. (Also, as a post-script. I'm giving up and going to bed, to look at this tomorrow. Sorry if I don't follow up on any questions til then).
Thanks!
I see two mistakes here.
In the view, replace:
<% f.fields_for :requests do |builder| %>
With:
<%= f.fields_for :requests do |builder| %>
In your order modeil, replace:
attr_accessible :order_status, :price_estimate, :price_final, :user_email, :user_name, :user_phone
with:
attr_accessible :order_status, :price_estimate, :price_final, :user_email, :user_name, :user_phone, :requests_attributes

Nested model form with collection in Rails 2.3

How can I make this work in Rails 2.3?
class Magazine < ActiveRecord::Base
has_many :magazinepages
end
class Magazinepage < ActiveRecord::Base
belongs_to :magazine
end
and then in the controller:
def new
#magazine = Magazine.new
#magazinepages = #magazine.magazinepages.build
end
and then the form:
<% form_for(#magazine) do |f| %>
<%= error_messages_for :magazine %>
<%= error_messages_for :magazinepages %>
<fieldset>
<legend><%= t('new_magazine') %></legend>
<p>
<%= f.label :title %>
<%= f.text_field :title %>
</p>
<fieldset>
<legend><%= t('new_magazine_pages') %>
<% f.fields_for :magazinepages do |p| %>
<p>
<%= p.label :name %>
<%= p.text_field :name %>
</p>
<p>
<%= p.file_field :filepath %>
</p>
<% end %>
</fieldset>
<p>
<%= f.submit :save %>
</p>
</fieldset>
<% end %>
problem is, if I want to submit a collection of magazinepages, activerecord complaints because it's expected a model and not an array.
create action:
def create
#magazine = Magazine.new params[:magazine]
#magazine.save ? redirect_to(#magazine) : render(:action => 'new')
end
In magazine:
accepts_nested_attributes_for :magazinepages
Magazine.new(params[:magazine]) will then handle the object hierarchy for you automatically
I'm not 100% sure what you're asking, but if you're trying to instantiate a new magazine, with many magazinepages, you'll need to iterate over each magazine page. Something like this:
def create
#magazine = Magazine.new(params[:magazine])
if params[:magazinepages]
params[:magazinepages].each do |page|
#magazine.magazinepages.build(page)
end
end
# Save the model, do your redirection or rendering invalid model etc
end

Resources