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.
Related
How can I solve this error No route matches {:action=>"show", :controller=>"events", :id=>nil}, missing required keys: [:id]. When I click submit at form to create event rails show me this error.
My routes
Rails.application.routes.draw do
devise_for :users, controllers: {registrations: 'registrations'}
devise_scope :user do
get 'logout', to: 'user_sessions#destroy'
end
resources :topics do
member do
put 'vote' => "topics#vote"
get 'refresh_statistic' => "topics#refresh_statistic"
end
end
resources :subtopics do
member do
put 'vote' => "subtopics#vote"
get 'refresh_statistic' => "subtopics#refresh_statistic"
end
end
root :to => "topics#show"
resources :comments do
member do
put "like" => "comments#like"
end
end
resources :events
resources :users do
collection do
get 'update_password'
end
end
end
Form to add new event
<%= form_for #event do |f| %>
<div class="field">
<%= f.label :title %>
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.label :content %>
<%= f.text_field :content %>
</div>
<div class="selector">
<%= f.label 'Owner type' %>
<%= f.select :owner_type, options_for_select([['Topic','Topic'], ['Subtopic', 'Subtopic']]) %>
<%= f.label 'Owner id' %>
<% if params[:owner_type] == 'Topic' %>
<%= f.collection_select(:owner_id, Topic.all, :id, :name, selected: (params[:owner_id ])) %>
<% else %>
<%= f.collection_select(:owner_id, Subtopic.all, :id, :name, selected: (params[:owner_id])) %>
<% end %>
</div>
<%= f.submit(#event) %>
<% end %>
Event controller
class EventsController < ApplicationController
before_action :set_event, only: %i[show edit update create]
def new
#event = Event.new
end
def create
#event = Event.new(create_params)
redirect_to event_path(#event)
end
def update
if #event.update_attributes(update_params)
redirect_to event_path(#event)
else
render :edit
end
end
def show
end
def index
#events = Event.all
end
private
def set_event
#event = Event.find_by_id(params[:id])
end
end
Rails show error at create action, redirect_to event_path(#event) line. Help me if possible, I'm at a time with this problem. Thanks.
Your create action needs to call #save on the new #event in a similar pattern to your update action.
It would be like:
def create
#event = Event.new(create_params)
if #event.save
redirect_to event_path(#event)
else
render :new
end
end
I have question and answer models with the comment to both as commentable. Comments on the question work fine but on the answer I am getting: "undefined method `comments' for nil:NilClass." Following is the code:
controller/comment_controller.rb
class CommentsController < ApplicationController
before_action :authenticate_user!
def create
#comment = #commentable.comments.new comment_params
#comment.user = current_user
#comment.save
redirect_to #commentable, notice: "Thanks for your comment."
end
private
def comment_params
params.require(:comment).permit(:content)
end
end
controller/questions/comment_controller.rb
class Questions::CommentsController < CommentsController
before_action :set_commentable
private
def set_commentable
#commentable = Question.find_by_permalink(params[:question_id])
end
end
controller/questions/answers/comment_controller.rb
class Answers::CommentsController < CommentsController
before_action :set_commentable
private
def set_commentable
#question = Question.find_by_permalink(params[:question_id])
#commentable = #question.answers.find(params[:answer_id])
end
end
Models
class Question < ApplicationRecord
belongs_to :user
has_many :answers, dependent: :destroy
has_many :comments, as: :commentable, dependent: :destroy
end
class Answer < ApplicationRecord
belongs_to :user
belongs_to :question
has_many :comments, as: :commentable, dependent: :destroy
end
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
belongs_to :user
end
Routes:
resources :questions do
resources :comments, module: :questions
resources :answers, :only => [:create, :destroy, :update] do
resources :comments, module: :answers
end end
Question: show.html.erb:
<h1><%= link_to #question.title, question_path(#question)%></h1>
<p><%= #question.description %></p>
<%= render partial: "comments/comments", locals: {commentable: #question } %>
<%= render partial: "comments/form", locals: {commentable: #question } %>
<% #question.answers.each do |answer| %>
<%= render partial: "comments/comments", locals: {commentable: #answer } %>
<%= render partial: "comments/form", locals: {commentable: #answer } %>
<% end %>
_comments.html.erb
<div class="content">
<h2 class="title"><%= pluralize(commentable.comments.count, 'Comment', plural: 'Comments') %></h2>
<% commentable.comments.each do |comment| %>
<small><%= time_ago_in_words(comment.created_at) %></small>
<p><%= comment.content %></p>
<% end %>
</div>
_form.html.erb
<%= form_for [commentable, Comment.new] do |f| %>
<%= f.text_area :content, class:'textarea', placeholder:'Add a comment' %>
<%= f.submit 'Comment', class:'button is-primary' %>
<% end %>
Update:
Strange thing happend. I reset the database and altest I can add comment to the answer now, not sure how it got fixed.
However I am getting a new error which make more sense: undefined method `answer_comments_path' for #<#:0x007f89e04905b8>. I have put <%= answer.inspect %> just before the comment input form to the answer and I am getting as expected answer model data.
So when I do the rake routes i dont see answer_comments_path.
The route is question_answer_comments_path. How can I pass this path to the answer/comments form.
I'm a beginner and have a serious problem with making two steps signing up.
I keep getting that error:
First argument in form cannot contain nil or be empty
<%= form_for(#Profile) do |f| %>
Users can sign up with their user information which are on the user table(DB). And all is fine with them. But I really want them to have profiles. So they can put their bio for example.
I tried to create MVC structure for the profiles based on users' MVC but it doesn't work.
I have been trying to find the answer for days. Tried hundreds of variations but nothing worked out. Please help!
views/users/new.html.erb (This is working)( I just wanted to create profile for the users and give them the ability to fill out their information as the second steps of the signing up)
<div class="container">
<% provide(:title, 'Sign up') %>
<h1 class="center">Sign up</h1>
<div class="row">
<div class="col s12 m10 l8 offset-m1 offset-l2">
<%= form_for(#user) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :first_name %>
<%= f.text_field :first_name %>
<%= f.label :genre %>
<%= f.text_field :genre %>
<%= f.label :middle_name %>
<%= f.text_field :middle_name %>
<%= f.label :last_name %>
<%= f.text_field :last_name %>
<%= f.label :preferred_name %>
<%= f.text_field :preferred_name %>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation %>
<div class="row center">
<%= f.submit "Create my account", class: "btn btn-large btn-primary" %>
</div>
<% end %>
</div>
</div>
</div>
models/user.rb
class User < ActiveRecord::Base
has_one :profile
has_many :microposts, dependent: :destroy
has_many :relationships, foreign_key: "follower_id", dependent: :destroy
has_many :followed_users, through: :relationships, source: :followed
has_many :reverse_relationships, foreign_key: "followed_id",
class_name: "Relationship",
dependent: :destroy
has_many :followers, through: :reverse_relationships, source: :follower
before_save { self.email = email.downcase }
before_create :create_remember_token
validates :first_name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, length: { minimum: 6 }
def User.new_remember_token
SecureRandom.urlsafe_base64
end
def User.digest(token)
Digest::SHA1.hexdigest(token.to_s)
end
def feed
Micropost.from_users_followed_by(self)
end
def following?(other_user)
relationships.find_by(followed_id: other_user.id)
end
def follow!(other_user)
relationships.create!(followed_id: other_user.id)
end
def unfollow!(other_user)
relationships.find_by(followed_id: other_user.id).destroy
end
private
def create_remember_token
self.remember_token = User.digest(User.new_remember_token)
end
end
controllers/users_controller.rb
class UsersController < ApplicationController
before_action :signed_in_user,
only: [:index, :edit, :update, :destroy, :following, :followers]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
def index
#users = User.paginate(page: params[:page])
end
def show
#user = User.find(params[:id])
#microposts = #user.microposts.paginate(page: params[:page])
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
sign_in #user
flash[:success] = "Welcome to the my app"
redirect_to #user
else
render 'new'
end
end
def edit
end
def update
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to #user
else
render 'edit'
end
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "just got destroyed."
redirect_to users_url
end
def following
#title = "Connections"
#user = User.find(params[:id])
#users = #user.followed_users.paginate(page: params[:page])
render 'show_follow'
end
def followers
#title = "known by"
#user = User.find(params[:id])
#users = #user.followers.paginate(page: params[:page])
render 'show_follow'
end
private
def user_params
params.require(:user).permit(:first_name, :middle_name, :last_name, :preferred_name, :email, :password,
:password_confirmation)
end
# Before filters
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
second signup page
views/users/signup2.html.erb
<div class="container">
<% provide(:title, 'Sign up2') %>
<h1 class="center">Sign up2</h1>
<div class="row">
<div class="col s12 m10 l8 offset-m1 offset-l2">
<%= form_for(#Profile) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :primary_instrument %>
<%= f.text_field :primary_instrument %>
<%= f.label :bio %>
<%= f.text_field :bio %>
<div class="row center">
<%= f.submit "Create my account2", class: "btn btn-large btn-primary" %>
</div>
<% end %>
</div>
</div>
</div>
models/profile.rb
class Profile < ActiveRecord::Base
belongs_to :user
end
controllers/profiles_controller.rb
class ProfilesController < ApplicationController
before_filter :get_user
def get_user
#profile = User.find(params[:user_id])
end
# generate new-profile form
def new
#user.profile = Profile.new
#profile = #user.profile
end
# process new-profile-form post
def create
#user.profile = Profile.new(params[:profile])
#profile = #user.profile
respond_to do |format|
if #profile.save
flash[:notice] = 'Profile was successfully created.'
format.html { redirect_to(#profile) }
format.xml { render :xml => #profile, :status => :created, :location => #profile }
...
end
end
end
# generate edit-profile form
def edit
#profile = Profile.find(params[:id])
end
# generate edit-profile-form post
def update
#profile = #user.profile
respond_to do |format|
if #profile.update_attributes(params[:profile])
flash[:notice] = 'Profile was successfully updated.'
# format.html { redirect_to(#profile) }
format.html { redirect_to(user_profile(#user)) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #profile.errors, :status => :unprocessable_entity }
end
end
end
Routes.rb
root to: 'static_pages#home'
match '/signup', to: 'users#new', via: 'get'
match '/signup2', to: 'profiles#new', via: 'get'
match '/signin', to: 'sessions#new', via: 'get'
match '/signout', to: 'sessions#destroy', via: 'delete'
match '/help', to: 'static_pages#help', via: 'get'
match '/about', to: 'static_pages#about', via: 'get'
match '/contact', to: 'static_pages#contact', via: 'get'
end
Your #Profile variable is capitalized in the markup, while the variable set in your controller is downcased. Rewrite the variable in the markup like so:
<%= form_for(#profile) do |f| %>
Hope it helps!
Apart from #Zoran's answer you can use the following gem which would make your life easy with multi step forms.
Wicked gem
I have a many_to_many association between Categories and Articles, using has_and_belongs_to_many in a Rails 4 app:
Here are the corresponding migration and classes:
class CategoriesArticles < ActiveRecord::Migration
def change
create_table :categories_articles, id: false do |t|
t.belongs_to :category, index: true
t.belongs_to :article, index: true
end
add_index :categories_articles, [:category_id, :article_id]
end
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :articles
end
class Article < ActiveRecord::Base
has_and_belongs_to_many :categories
end
When a user creates a new article, I simply want to give him or her the option to select categories that he/she wants to associate with the new article. I want the user to be able to select these categories with checkboxes.
Here's the ArticlesController:
class ArticlesController < ApplicationController
before_action :set_article, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, only: [:new, :create, :edit, :destroy, :update]
before_action :verify_own_article, only: [:destroy]
respond_to :html
def index
if user_signed_in?
#articles = current_user.article_feed
# TODO: if there are too few articles on a user's feed, we want to display more articles
else
#articles = Article.all
end
#articles = #articles.page(params[:page] || 1).per(12)
respond_with(#articles)
end
def show
respond_with(#article)
end
def new
#categories = Category.all
#article = Article.new
respond_with(#article)
end
def edit
if current_user.articles.find_by_id(params[:id]).nil?
flash[:notice] = "You do not have permission to edit this article."
redirect_to #article
end
end
def create
# Creates article object with current_user_id, initial_comment, and URL
#article = current_user.articles.build(article_params)
# Uses Pismo (gem) to grab title, content, photo of URL
#article.populate_url_fields
if #article.save
flash[:success] = "Article created!"
# Might need to change the location of this redirect
redirect_to root_url
else
flash[:notice] = "Invalid article."
redirect_to new_article_path
end
end
def update
#article.update(article_params)
flash[:notice] = "Article successfully updated."
respond_with(#article)
end
def destroy
if #article
#article.destroy
flash[:notice] = "Article successfully destroyed."
else
flash[:notice] = "You do not have permission to delete this article."
end
# TODO: change this to another redirect location
redirect_to root_path
end
private
def set_article
#article = Article.find(params[:id])
end
def article_params
params.require(:article).permit(:url, :title, :datetime, :content, :photo, :initial_comment)
end
# Ensure that a signed in user can only delete articles that they have posted
def verify_own_article
#article = current_user.articles.find_by_id(params[:id])
end
end
Here's the article new.html.erb view:
<h1>New article</h1>
<%= render 'form' %>
<%= link_to 'Back', articles_path %>
... and the form partial:
<%= form_for(#article) do |f| %>
<% if #article.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#article.errors.count, "error") %> prohibited this article from being saved:</h2>
<ul>
<% #article.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :url %><br>
<%= f.text_field :url %>
</div>
<div class="field">
<%= f.label :initial_comment %><br>
<%= f.text_field :initial_comment %>
</div>
<% #categories.each do |t| %>
<div class="field">
<%= f.label t.name %>
<%= f.check_box "categories[#{t.id}]" %>
<br />
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
However, this is erroring for me, specifically the lines:
<% #categories.each do |t| %>
<div class="field">
<%= f.label t.name %>
<%= f.check_box "categories[#{t.id}]" %>
<br />
</div>
<% end %>
Specifically, it's telling me:
undefined method 'categories[1]' for #<Article:0x007f401193d520>. How do I fix this? Thanks.
I note your join table is named
create_table :categories_articles, id: false do |t|
The name needs to be in alphabetical order.
create_table :articles_categories, id: false do |t|
My pick is that Rails cannot find your join table. This means that the form when it calls #article.categories cannot find what it needs. The same will happen if you call #category.articles (ie: being able to find the join table is not related to the arbitrary order of the objects).
See my answer to this question
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>