Nested model/form doesn't create an entry - ruby-on-rails

While delving into ruby's nested models I encountered an issue.
Consider the following scenario,
I have the following models:
Author
Book
With the following specifications:
Author:
class Author < ActiveRecord::Base
attr_accessible :name
has_many :books, dependent: :destroy
accepts_nested_attributes_for :books #I read this is necessary here: http://stackoverflow.com/questions/12300619/models-and-nested-forms
# and some validations...
end
Book:
class Book < ActiveRecord::Base
attr_accessible :author_id, :name, :year
belongs_to :author
#and some more validations...
end
I would like to add a book to an author. Here is my authors_controller:
def new_book
#author = Author.find(params[:id])
end
def create_book
#author = Author.find(params[:id])
if #author.books.create(params[:book]).save
redirect_to action: :show
else
render :new_book
end
end
And this is the form with which I try doing it:
<h1>Add new book to <%= #author.name %>'s collection</h1>
<%= form_for #author, html: { class: "well" } do |f| %>
<%= fields_for :books do |b| %>
<%= b.label :name %>
<%= b.text_field :name %>
<br/>
<%= b.label :year %>
<%= b.number_field :year %>
<% end %>
<br/>
<%= f.submit "Submit", class: "btn btn-primary" %>
<%= f.button "Reset", type: :reset, class: "btn btn-danger" %>
<% end %>
Problem:
When I type in the data and click "submit" it even redirects me to the correct author, but it doesn't save a new record for that author.
After a lot of research I can't seem to find what I am doing wrong here.

Change authors_controller to:
def new_book
#author = Author.find(params[:id])
#book = Book.new
end
Your form to:
<h1>Add new book to <%= #author.name %>'s collection</h1>
<%= form_for ([#author, #book]), html: { class: "well" } do |f| %>
And, routes.rb
resources :authors do
resources :books
end

You're missing a couple of things.
Controller:
...
def new_book
#author = Author.find(params[:id])
#author.books.build
end
...
View, it is f.fields_for and not just fields_for:
<%= f.fields_for :books do |b| %>
<%= b.label :name %>
<%= b.text_field :name %>
<br/>
<%= b.label :year %>
<%= b.number_field :year %>
<% end %>

You also need to add :nested_attributes_for_books on your Author model accessible methods. The create method on you Author controller doesn't need any code additions to get going.
Note: You set the Books controller to render 'books#show' on success. If the app is redirecting you to the author that means the Author controller is handling the creation of the book, unless you set it to redirect to author and not the book.

Related

Rails 5 - has_many through: and nested fields_for in forms

I am new to RoR (using rails 5) and have problems with a has_many through: association.
I want to create Categories with different labels for different Languages.
Here is my model:
class Language < ApplicationRecord
has_many :category_infos
has_many :categories, through: :category_infos
end
class Category < ApplicationRecord
has_many :category_infos
has_many :languages, through: :category_infos
accepts_nested_attributes_for :category_infos
end
class CategoryInfo < ApplicationRecord
belongs_to :language
belongs_to :category
accepts_nested_attributes_for :language
end
The controller:
class CategoriesController < ApplicationController
def new
#category = Category.new
#languages = Language.all
#languages.each do |language|
#category.category_infos.new(language:language)
end
end
def create
#category = Category.new(category_params)
if #category.save
redirect_to #category
else
render 'new'
end
end
private
def category_params
params.require(:category).permit(:name, category_infos_attributes:[:label, language_attributes: [:id, :language]])
end
end
The form:
<%= form_with model: #category, local: true do |form| %>
<% if #category.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(#category.errors.count, "error") %>:
</h2>
<ul>
<% #category.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<br/>
<% end %>
<p>
<%= form.label :name %>
<%= form.text_field :name %>
</p>
<p>
Labels:
</p>
<table>
<% #category.category_infos.each do |category_info| %>
<tr>
<td>
<%= category_info.language.name %>
</td>
<td>
<%= form.fields_for :category_infos, category_info do |category_info_form| %>
<%= category_info_form.fields_for :language, category_info.language do |language_form| %>
<%= language_form.hidden_field :id, value: category_info.language.id %>
<%= language_form.hidden_field :name, value: category_info.language.name %>
<% end %>
<%= category_info_form.text_field :label %>
<% end %>
</td>
</tr>
<% end %>
</table>
<p>
<%= form.submit %>
</p>
<% end %>
When I create a new category, I get this error :
Couldn't find Language with ID=1 for CategoryInfo with ID=
on Line :
#category = Category.new(category_params)
However I already have registered several languages in database (1 = English, 2 = French etc...)
How do I need to write the form so that I can create a Category and its CategoryInfos in English, French etc... at the same time?
Thanks in advance for your answers
You're making a classic newbie misstake and using fields_for when you just want to create an association by passing an id.
<%= form_with model: #category, local: true do |f| %>
# ...
<%= f.fields_for :category_infos do |cif| %>
<%= cif.collection_select(:language_id, Language.all, :name, :id) %>
<%= cif.text_field :label %>
<% end %>
<% end %>
While you could also pass the attributes to let a users create languages at the same time its very much an anti-pattern as it adds a crazy amount of responsibilities to a single controller. It will also create an authorization problem if the user is allowed to create categories but not languages.
Use ajax to send requests to a seperate languages controller instead if you need the feature.

having issues converting slim to erb

I have this slim syntax:
= form_for(#influencer.relationships.build(followed_id: #influencer.id)) do |f|
div = f.hidden_field :followed_id
= f.submit "Follow", class: "btn btn-large btn-primary"
This is the erb i came up but it doesn't work.
<%= form_for(#influencer.relationships.build(followed_id: #influencer.id)) do |f| %>
<% f.hidden_field :followed_id %>
<%= f.submit "Follow" %>
<% end %>
<%= form_for #influencer.relationships.build(followed_id: #influencer.id) do |f| %>
<%= f.hidden_field :followed_id %>
<%= f.submit "Follow" %>
<% end %>
Conversely, it's bad practice to build an object out of the controller scope. #influencer.relationships.build(followed_id: #influencer.id) should be done inside the controller:
#app/models/influencer.rb
class Influencer < ActiveRecord::Base
has_many :relationships, foreign_key: :follower_id
end
#app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
def new
#influencer = Influencer.find params[:influencer_id]
#relationship = #influencer.relationships.build #-> "followed_id" SHOULD be a foreign key in the model. If you have it in the model, you won't need to explicitly define it
end
end
Thus, you'll get:
<%= button_to "Follow", #relationship %>

rails: mass-assign error caused by updating child (Post) from parent (Topic) controller (edit view)

I am trying to edit a Topic which has many Posts.
Edit page for a Topic has Topic's name and Post's content that can be edited.
The mass-assignment error occurs in topics_controller.rb, update method, post.update_attributes(params[:post]).
How do I avoid mass-assignment error.
topic.rb
class Topic < ActiveRecord::Base
has_many :posts, :dependent => :destroy
belongs_to :forum
accepts_nested_attributes_for :posts, :allow_destroy => true
attr_accessible :name, :last_post_id, :posts_attributes
end
post.rb
class Post < ActiveRecord::Base
belongs_to :topic
attr_accessible :content
end
topics_controller.rb
def update
#topic = Topic.find(params[:id])
post = #topic.posts.first
if #topic.update_attributes(params[:topic]) && post.update_attributes(params[:post])
topic = Topic.find(#post.topic_id)
flash[:success] = "Success!"
redirect_to topic_posts_path(topic)
else
render 'edit'
end
end
views/topics/edit.html.erb
<%= form_for #topic do |f| %>
<!-- render 'shared/error_messages_topic' -->
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for #topic.posts.first do |post| %>
<%= render :partial => "posts/form", :locals => {:f => post} %>
<% end %>
<%= f.submit "Edit", class: "btn btn-large btn-primary" %>
<% end %>
views/posts/_form.html.erb
<%= f.label :content %>
<%= f.text_area :content %>
In update method you don't have to update attributes of both the models instead of if #topic.update_attributes(params[:topic]) && post.update_attributes(params[:post]) it should this only if #topic.update_attributes(params[:topic]) it will update the posts automatically.
And change your view from this <%= f.fields_for #topic.posts.first do |post| %> to <%= f.fields_for :posts, #topic.posts.first do |post| %> it will work fine.
For more information read this http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-fields_for

How do I generate multiple inputs in formtastic?

Adapted from the nested form Railscast, I have:
In my model
class Post < ActiveRecord::Base
has_many :fields
accepts_nested_attributes_for :fields
end
class Field < ActiveRecord::Base
belongs_to :post
end
In my controller
def new
#post = Post.new
4.times { #post.fields.build }
respond_to do |format|
format.html
end
end
In my view
<%= semantic_form_for #post do |f| %>
<%= f.inputs do %>
<%= f.input :title %>
<% end %>
<%= semantic_fields_for :fields do |h| %>
<%= h.input :name %>
<% end %>
<%= f.buttons do %>
<%= f.commit_button %>
<% end %>
<% end %>
The problem is that this only generates one :field input even though i ran #post.fields.build four times. I can't figure out how to generate multiple inputs so the user can enter multiple fields.
Sorry if this is obvious but I'm new to Rails and pretty new to programming overall.
Your nested form ain't correct
Change <%= semantic_fields_for :fields do |h| %> to
<%= f.inputs :for => :fields do |h|%>

undefined method `my_teas_path' for #<#<Class:0xa8930c8>:0xa578cf8>

My current goal in my first rails project is to have a button that will create a #my_tea using the attributes of a #tea (show page). This is the error I am getting:
'undefined method `my_teas_path' for #<#:0xa578cf8>
I have tried having the form in a _new partial inside my_teas/ and inside teas/_add_tea both have given me the same error. Anyway here is my code as it stands.
View:
<%= form_for([#user, #my_tea]) do |f| %>
<%= f.hidden_field :name, :value => #tea.name %>
<%= f.hidden_field :tea_type, :value => #tea.tea_type %>
<%= f.hidden_field :store, :value => #tea.store %>
<%= f.hidden_field :user_id, :value => current_user.id %>
<%= fields_for [#user, #tea_relationship] do |r| %>
<%= r.hidden_field :tea_id, :value => #tea.id %>
<% end %>
<%= f.submit "Add Tea", class: "btn btn-large btn-primary" %>
<% end %>
my_tea controller
def new
#my_tea = MyTea.new
end
def show
#my_tea = MyTea.find(params[:id])
end
def create
#my_tea = MyTea.new(params[:my_tea])
if #my_tea.save
flash[:success] = "Tea added to your teas!"
else
redirect_to user_path
end
end
Teas controller:
def show
#tea = Tea.find(params[:id])
#my_tea = MyTea.new
#tea_relationship = TeaRelationship.new
end
Routes
resources :users do
resources :my_teas
end
resources :teas
Models:
class User < ActiveRecord::Base
has_many :my_teas, :dependent => :destroy
has_many :tea_relationships, :dependent => :destroy
class MyTea < ActiveRecord::Base
belongs_to :user
class TeaRelationship < ActiveRecord::Base
belongs_to :user, class_name: "User"
end
Tea model doesn't belong to anything.
Please help rails community your my only hope :p
Update
changing my form to this
<%= form_for([#user, #my_tea]) do |f| %>
<%= f.hidden_field :name, :value => #tea.name %>
<%= f.hidden_field :tea_type, :value => #tea.tea_type %>
<%= f.hidden_field :store, :value => #tea.store %>
<%= f.hidden_field :user_id, :value => current_user.id %>
<%= fields_for #tea_relationship do |r| %>
<%= r.hidden_field :tea_id, :value => #tea.id %>
<% end %>
<%= f.submit "Add Tea", class: "btn btn-large btn-primary" %>
<% end %>
it works and the #my_tea submits but the #tea_relationship doesn't.
So by the look of things and glimpsing through, it seems like you are trying to do some nested forms. It also appears like you have a many-to-many relationship( tea.rb <=> tea_relationship.rb <=> my_tea.rb) Make sure your Models are set up correctly.
Many to Many
I am not sure why you are trying to do [#user, #my_tea]
Nested Forms
Should be more in the lines of
<%= form_for #my_tea, :url => posting_path do |f| %>
<%= f.simple_fields_for :teas, #my_tea.teas.build do |x| %>
...
<%end%>
...
<%end%>
hope that helps!

Resources