New to rails and this one has been troubling me for a while now. All the tutorials on polymorphic associations seem to be only two levels and never a polymorphic model nested in a polymorphic model. really need help on this.
I have a app that has post, comment and link models. Both comments and links are polymorphic as I want to add link urls to both comments and posts, as well as comments to other things, similar to how facebook works. I want to nest the links model so can send as one form. Post and links work but comments and links has an MassAssignmentSecurity error.
Erorr
ActiveModel::MassAssignmentSecurity::Error in CommentsController#create
Can't mass-assign protected attributes: link
Request Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"
J1JfIINrtvax77M3JcbDDWvHFpyGG1ciK1DisGOLu6M=",
"comment"=>{"content"=>"z",
"link"=>{"link_url"=>"z"}},
"commit"=>"Add comment",
"forum_post_id"=>"16"}
Routes
resources :forum_posts do
resources :comments
resources :links
end
resources :comments do
resources :links
end
Forum_Post Model
class ForumPost < ActiveRecord::Base
attr_accessible :content, :links_attributes, :comments_attributes , :link_url
has_many :links, :as => :linkable
has_many :comments, :as => :commentable
accepts_nested_attributes_for :links, :allow_destroy => true #, :reject_if => lambda { |t| t[:link].nil?}
end
Comment Model
class Comment < ActiveRecord::Base
attr_accessible :commentable_id, :commentable_type, :content,:links_attributes, :link_url
belongs_to :commentable, :polymorphic => true
has_many :links, :as => :linkable
accepts_nested_attributes_for :links, :allow_destroy => true #, :reject_if => lambda { |t| t[:link].nil?}
end
Link Model
class Link < ActiveRecord::Base
attr_accessible :description, :image_url, :link_url, :linkable_id, :linkable_type, :title, :link_id
belongs_to :linkable, :polymorphic => true
end
Forum_Post Controller
class ForumPostsController < ApplicationController
...............
def new
#forum_post = ForumPost.new
#link = #forum_post.links.build
respond_to do |format|
format.html # new.html.erb
format.json { render json: #forum_post }
end
end
........
end
Comments Controller
class CommentsController < ApplicationController
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
def index
#commentable = find_commentable
#comments = #commentable.comments
end
def create
#commentable = find_commentable
#comment = #commentable.comments.build(params[:comment])
#comment.links.build
if #comment.save
flash[:notice] = "Successfully saved comment."
redirect_to :id => nil
else
render :action => 'new'
end
end
end
Links Controller
class LinksController < ApplicationController
def find_linkable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
def index
#linkable = find_linkable
#links = #linkable.links
end
def create
#linkable = find_linkable
#link = #linkable.link.build(params[:link])
if #link.save
flash[:notice] = "Successfully saved link."
redirect_to :id => nil
else
render :action => 'new'
end
end
end
Comment Partial View
<h2>Comments</h2>
<% if commentable.comments.empty? %>
No comments to display.
<% else %>
<% for comment in commentable.comments %>
<%= comment.content %>
<% end %>
<% end %>
<% :link_url %>
<h2>New Comment</h2>
<%= form_for [#commentable, Comment.new] do |f| %>
<div class="field">
<%= f.label :content %><br />
<%= f.text_area :content, :rows => 5 %>
</div>
<%= f.fields_for [#linkable, Link.new] do |link| %>
<%= render :partial => 'links/link', :locals => { :f => link } %>
<% end%>
<div class="actions">
<%= submit_tag "Add comment" %>
</div>
<% end %>
Link Partial View
<h2>Link Previews</h2>
<div class="field">
<%= f.label :link_url %><br />
<%= f.text_field :link_url, :id => "url_field" %>
</div>
Related
Albums have many reviews and users through reviews, Reviews belong to album and belong to user, and Users have many reviews and have many albums through reviews.
On my albums show page, Reviews do not display. But Reviews display on the reviews index page. I can't seem to figure out what I'm missing. I'm not getting any errors.
Here is my Albums controller:
class AlbumsController < ApplicationController
before_action :set_album, only: [:show, :edit, :update]
def index
#albums = Album.all
#current_user
end
def show
#review = #album.reviews.build
end
def new
#album = Album.new
end
def create
#album = Album.new(album_params)
if #album.save
redirect_to album_path(#album)
else
render :new
end
end
def edit
end
def update
if #album.update(album_params)
redirect_to album_path(#album), notice: "Your album has been updated."
else
render 'edit'
end
end
private
def set_album
#album = Album.find(params[:id])
end
def album_params
params.require(:album).permit(:artist, :title, :avatar)
end
end
Reviews Controller:
class ReviewsController < ApplicationController
def index
#reviews = Review.all
end
def show
#review = Review.find(params[:id])
##reviews = Review.where("album_id = ?", params[:album_id])
end
def new
#review = Review.new
end
def create
#review = current_user.reviews.build(review_params)
if #review.save
redirect_to reviews_path
else
render :new
end
end
def update
end
private
def review_params
params.require(:review).permit(:title, :date, :content, :user_id, :album_id, album_attributes:[:artist, :title])
end
end
Here is my albums show page:
<% if #album.avatar.attached? %>
<image src="<%=(url_for(#album.avatar))%>%" style="width:350px;height:350px;">
<% end %>
<br>
<%= #album.artist %> -
<%= #album.title %>
<br>
<%= link_to "Edit Album", edit_album_path %>
<br><br><br>
<%= render '/reviews/form' if logged_in? %>
<h3>Album Reviews</h3>
<% #album.reviews.each do |review| %>
<%= review.title %>
<%= review.content %>
<% end %>
<br><br><br>
<%= link_to "All Albums", albums_path %><br>
<%= link_to "Upload a New Album", new_album_path %>
Here is my routes file:
Rails.application.routes.draw do
get '/signup' => 'users#new', as: 'signup'
post '/signup' => 'users#create'
get '/signin' => 'sessions#new'
post '/signin' => 'sessions#create'
get '/signout' => 'sessions#destroy'
resources :albums do
resources :reviews, except: [:index]
end
resources :users
resources :reviews, only: [:index]
root to: "albums#index"
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end
Here are my Models:
class Album < ApplicationRecord
has_many :reviews
has_many :users, through: :reviews
has_one_attached :avatar
end
class Review < ApplicationRecord
belongs_to :album, optional: true
belongs_to :user
accepts_nested_attributes_for :album
end
class User < ApplicationRecord
has_secure_password
has_many :reviews
has_many :albums, through: :reviews
accepts_nested_attributes_for :reviews
end
Review form:
<%= form_for :review, method: "POST",:url => { :action => :create } do |f| %>
<!-- <%= f.collection_select :album_id, Album.order(:artist),:id,:artist %> -->
<br>
<%= f.label :title %>
<%= f.text_field :title%>
<br><br>
<%= f.label :date %>
<%= f.datetime_field :date%>
<br><br>
<%= f.label :content %>
<%= f.text_area :content %>
<br><br>
<%= f.submit "Submit" %>
<% end %>
The problem is the iteration, because you use the instance variable coming from the controller instead of the block variable, it should be:
<% #album.reviews.each do |review| %>
<%= review.title %>
<%= review.content %>
<% end %>
#review is a new instance of a review (empty) which you created for the form I guess.
So instead of showing each review's title and content you showed the one from the empty review saved in #review in each iteration.
I have these models:
class Review < ActiveRecord::Base
belongs_to :reviewable, polymorphic: true
end
class Article < ActiveRecord::Base
has_one :review, as: :reviewable, dependent: :destroy
accepts_nested_attributes_for :review
end
And a form like this:
<%= form_for #article do |f| %>
<%= f.fields_for(:review, Review.new) do |r| %>
<%= r.label :content %>
<%= r.text_field :content %>
<% end %>
<%= f.label :description %>
<%= f.text_field :description %>
<% end %>
Inside my ArticlesController I create article simple like this:
#article = Article.new(article_params)
#article.save
def article_params
params.require(:article).permit(:description, review_attributes: [:id, :content])
end
What am I doing wrong? Thank you.
Try adding accepts_nested_attributes_for
Updated:
app/models/article.rb
class Article < ActiveRecord::Base
has_one :review, as: :reviewable, dependent: :destroy
accepts_nested_attributes_for :review
end
app/controllers/articles_controller.rb
def new
#article = Article.new
end
def create
#article = Article.new(article_params)
if #article.save
redirect_to #article, notice: "Article created!"
else
render :new
end
end
private
def article_params
params.require(:article).permit(:description, review_attributes: [:content])
end
view
<%= form_for(#article) do |form| %>
<%= form.fields_for(:review_attributes, #article.build_review) do |review| %>
<%= review.label :content %>
<%= review.text_field :content %>
<% end %>
<%= form.label :description %>
<%= form.text_field :description %>
<% end %>
I want to build a many-to-many association through tagging between posts and tags. Users can create posts with tags by checking the existed tags. But I don't know how to create a nested form and save the association.
My form
<%= form_for(#post) do |f| %>
<div class="field">
<%= f.label :text %><br />
<%= f.text_field :text %>
</div>
<div>
<%= hidden_field_tag "post[tag_ids][]", nil %>
<% Tag.all.each do |tag| %>
<%= check_box_tag "post[tag_ids][]", tag.id, #post.tag_ids.include?(tag.id) %>
<%= tag.name %>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
posts controller here:
class PostsController < ApplicationController
def index
#posts = Post.all
end
def new
#post = Post.new
end
def create
#post = Post.new(post_params)
if #post.save
redirect_to #post
else
render 'new'
end
end
def show
#post = Post.find(params[:id])
end
private
def post_params
params.require(:post).permit(:text)
end
end
Model
class Post < ActiveRecord::Base
has_many :taggings
has_many :tags, :through => :taggings
end
class Tag < ActiveRecord::Base
has_many :taggings
has_many :posts, :through => :taggings
end
class Tagging < ActiveRecord::Base
belongs_to :tag
belongs_to :post
end
I don't think you actually need to have a nested form in this case. Rails automagically figures out the association, when given the attribute :tag_ids
This is what I did for a multiselect form_helper, which would need minor changes for a check_box helper
f.select :tag_ids, Tag.all.collect {|tag| [tag.name, tag.id]}, {}, :multiple => true
having a lot of trouble setting up polymorphic. I have Comments which works fine, but the Links which should work exactly the same has NameError in Links#index. If I try to forum_posts/1/comments all is good but not forum_posts/1/links.
I was following the railscast tutorial http://railscasts.com/episodes/154-polymorphic-association?view=asciicast
Appreciate the help.
Error
NameError in Links#index
uninitialized constant ForumPost::Link
Extracted source (around line #4):
1: <h1>Links</h1>
2:
3: <ul id="links">
4: <% #links.each do |link| %>
5: <li><%= link.display_name %></li>
6: <% end %>
7: </ul>
routes
resources :forum_posts do
resources :comments
resources :links
end
Models
*models/forum_posts.rb*
class ForumPost < ActiveRecord::Base
attr_accessible :content, :display_name, :section, :user_id
has_many :comments, :as => :commentable
has_many :links, :as => :linkable
end
models/comments.rb
class Comment < ActiveRecord::Base
attr_accessible :commentable_id, :commentable_type, :content, :user_id
belongs_to :commentable, :polymorphic => true
end
models/comments.rb
class Links < ActiveRecord::Base
attr_accessible :description, :display_name, :inamge, :linkable_id, :linkable_type, :user_id
belongs_to :linkable, :polymorphic => true
end
Controllers
*controllers/comments_controller.rb*
class CommentsController < ApplicationController
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
def index
#commentable = find_commentable
#comments = #commentable.comments
end
def create
#commentable = find_commentable
#comment = #commentable.comments.build(params[:comment])
if #comment.save
flash[:notice] = "Successfully saved comment."
redirect_to :id => nil
else
render :action => 'new'
end
end
end
*controller/links_controller.rb*
class LinksController < ApplicationController
def find_linkable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
def index
#linkable = find_linkable
#links = #linkable.links
end
def create
#linkable = find_linkable
#link = #linkable.links.build(params[:link])
if #link.save
flash[:notice] = "Successfully saved link."
redirect_to :id => nil
else
render :action => 'new'
end
end
end
Views
views/comments/index.html.erb
<h1>Comments</h1>
<ul id="comments">
<% #comments.each do |comment| %>
<li><%= comment.content %></li>
<% end %>
</ul>
<h2>New Comment</h2>
<%= form_for [#commentable, Comment.new()] do |form| %>
<%= form.label :content %><br/>
<%= form.text_area :content, :rows => 5 %><br/>
<%= form.submit "Add comment" %>
<% end %>
views/links/index.html.erb
<h1>Links</h1>
<ul id="links">
<% #links.each do |link| %>
<li><%= link.display_name %></li>
<% end %>
</ul>
<h2>New Link</h2>
<%= form_for [#linkable, Link.new()] do |form| %>
<%= form.label :display_name %><br/>
<%= form.text_area :display_name %><br/>
<%= form.submit "Add link" %>
<% end %>
Your Links model should be placed at app/models/link.rb and should be called Link, not Links.
I'm trying to add a fields_for attribute to a nested rails form. Any time I try to create a new object, it returns
Message(#58819400) expected, got
Array(#18903800) ...
app/controllers/discussions_controller.rb:53:in
`create'
If I try to access nested fields_for from forms based on non-nested resources (aka "form_for #parent" instead of "form_for [#parent, #child]" it works fine. Code below - any help with this really appreciated.
Controller:
# GET /discussions/new
# GET /discussions/new.xml
def new
#forum = Forum.find(params[:forum_id])
#discussion = Discussion.new
#discussion.messages.build
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #discussion }
end
end
def create
#forum = Forum.find_by_id(params[:forum_id])
#discussion = #forum.discussions.new(params[:discussion])
#discussion.user = current_user
respond_to do |format|
if #discussion.save
format.html { redirect_to([#forum, #discussion], :notice => 'Discussion was successfu#ly created.') }
format.xml { render :xml => [#forum, #discussion], :status => :created, :location => #discussion }
else
format.html { render :action => "new" }
format.xml { render :xml => #discussion.errors, :status => :unprocessable_entity }
end
end
end
Models:
class Forum < ActiveRecord::Base
belongs_to :user
has_many :discussions, :dependent => :destroy
validates :title, :presence => true
accepts_nested_attributes_for :discussions, :allow_destroy => true
end
class Discussion < ActiveRecord::Base
belongs_to :user
belongs_to :forum
has_many :messages, :dependent => :destroy
validates :title, :presence => true
end
class Message < ActiveRecord::Base
belongs_to :user
validates :user, :presence => true
validates :content, :presence => true
end
The view:
<%= form_for [#forum, #discussion] do |f| %>
<% if #discussion.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#discussion.errors.count, "error") %> prohibited this discussion from being saved:</h2>
<ul>
<% #discussion.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :title %><br />
<%= f.text_field :title %>
</div>
<%= f.fields_for :messages do |builder| %>
<%= builder.label :content, "Message" %>
<%= builder.text_area :content, :rows => 10 %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
And finally, the routing:
resources :forums do
resources :discussions do
resources :messages
end
end
Any help with this really appreciated - I'm completely stumped.
Arghhh - really sorry folks...I just realised I'd forgot the accepts_nested_attributes_for in the discussions model, & consequently forums could access discussions, but discussions couldn't get down to messages.
Thanks anyhow.