Activerecord reputation system - nested routes - ruby-on-rails

I am trying to follow this video railscast #364 but I am having a lot of trouble with my nested routes. When I use this code:
<%= link_to "up", vote_movie_review_path(#movie, #reviews, type: "up"), method: "post" %>
I get this error when I select up vote:
ActiveRecord::RecordNotFound in ReviewsController#vote
Couldn't find Review with 'id'=# <Review::ActiveRecord_Relation:0x007f0358c1e550>
This is my route:
vote_movie_review POST /movies/:movie_id/reviews/:id/vote(.:format) genre_linkers#vote
I created another model that was not nested using this code:
<%= link_to "up", vote_movie_path(movie, type: "up"), method: "post" %>
and that one worked. So I am thinking it has to be something wrong with my path or how I am calling the objects. I have spent almost all day working on this, I really need help.
review_controller.rb
class ReviewsController < ApplicationController
before_action :set_review, only: [:show, :edit, :update, :destroy]
before_action :set_movie
before_action :authenticate_user!
respond_to :html
def index
#reviews = Review.all
respond_with(#reviews)
end
def show
end
def vote
value = params[:type] == "up" ? 1 : -1
#review = Review.find(params[:id])
#review.add_evaluation(:vote, value, current_user)
redirect_to :back, notice: "thanks for the vote"
end
def new
#review = Review.new
respond_with(#review)
end
def edit
end
def create
#review = Review.new(review_params)
#review.user_id = current_user.id
#review.movie_id = #movie.id
if #review.save
redirect_to #movie
else
render 'new'
end
end
def update
#review.update(review_params)
respond_with(#review)
end
def destroy
#review.destroy
respond_with(#review)
end
private
def set_review
#review = Review.find(params[:id])
end
def set_movie
#movie = Movie.find(params[:movie_id])
end
def review_params
params.require(:review).permit(:genre, :description, :vote)
end
end
routes.rb
Rails.application.routes.draw do
devise_for :users
resources :movies do
resources :reviews do
member { post :vote }
end
end
root 'movies#index'
end
and the model
review.rb
class Review < ActiveRecord::Base
belongs_to :user
belongs_to :movie
has_reputation :votes, source: :user, aggregated_by: :sum
end

The culprit is this part of your link_to: vote_movie_review_path(#movie, #reviews, type: "up"). #reviews is an ActiveRecord::Relation and not a Review record, hence no record with an ID can be found.

Related

NoMethodError - Rails - Can't track down the problem

I am currently working on a blog app. The blog has users, stories, and favourites. 1 user can have many stories and many favourites, and one story can have many favourites (1 from each user) too.
When I try to save a new favourite on the front end (the create route), I receive the following error:
undefined method `favourites' for #<Class:0x00007f850e24e290>
Extracted source (around line #11):
9 def create
10 #story = Story.find(params[:story_id])
11 #favourite = Story.favourites.new
12 #favourite.user = #current_user
13 #favourite.save
14 redirect_to story_path(#story)
I feel like this should be defined, and is almost the same way that I am using create with other controllers. Based on what I've found so far, I suspect that it is because the favourites in Story.favourites.new is nested within the Story, but I haven't found a way to sort it out yet. I'm quite new to Rails, so apologies if the answer is obvious. Any guidance is appreciated! Thanks for your help!
Below are some of the pertinent files.
favourites_controller.rb
class FavouritesController < ApplicationController
before_action :logged_in, except: [:index, :show]
def show
end
def create
#story = Story.find(params[:story_id])
#favourite = Story.favourites.new
#favourite.user = #current_user
#favourite.save
redirect_to story_path(#story)
end
end
stories_controller.rb
class StoriesController < ApplicationController
before_action :logged_in, except: [:index, :show]
# Stories list page (also homepage)
def index
# Topic filtering
#topic = params[:topic]
#stories = if #topic.present?
Story.where(topic: #topic)
else
Story.all
end
end
def new
#story = Story.new
end
def create
#story = Story.new(form_params)
#story.user = #current_user
if #story.save
redirect_to root_path
else
render 'new'
end
end
def show
#story = Story.find(params[:id])
end
def destroy
#story = Story.find(params[:id])
#story.destroy if #story.user == #current_user
redirect_to root_path
end
def edit
#story = Story.find(params[:id])
redirect_to root_path if #story.user != #current_user
end
def update
#story = Story.find(params[:id])
if #story.user == #current_user
if #story.update(form_params)
redirect_to story_path(#story)
else
render 'edit'
end
else
redirect_to root_path
end
end
def form_params
params.require(:story).permit(:title, :topic, :body)
end
end
models/favourite.rb
class Favourite < ApplicationRecord
belongs_to :story
belongs_to :user
validates :story, uniqueness: { scope: :user }
end
models/story.rb
class Story < ApplicationRecord
has_many :comments
has_many :favourites
belongs_to :user
validates :title, presence: true
validates :body, presence: true, length: { minimum: 10 }
def to_param
id.to_s + '-' + title.parameterize
end
end
config/routes.rb
Rails.application.routes.draw do
resources :stories do
resources :comments
resource :favourite
end
resources :users
resource :session
root 'stories#index'
end
the issue is in this line:
11 #favourite = Story.favourites.new
it should be:
11 #favourite = #story.favourites.new
because the class Story itself doesn't have the favourites method but its instances do.

Authorization settings using pundit gem rails

I'm new at rails so bear with me pls. My problem is so specific. I'm creating a User blog, where they could put any posts. So Users has a blogs, and blogs has posts. So when user create a blog, all posts in his blog should be written by him. Other users can't write not on their blogs.
post_controller.rb
class PostsController < ApplicationController
before_action :authenticate_user!
before_action :authorize_user!, only: [:edit, :update, :destroy]
expose :blog
expose :post
def show
end
def new
end
def edit
end
def create
post.user = current_user
post.save
respond_with post, location: user_blog_path(post.blog.user, post.blog)
end
def update
post.update(post_params)
respond_with post, location: user_blog_path(post.blog.user, post.blog)
end
def destroy
post.destroy
respond_with post, location: user_blog_path(post.blog.user, post.blog)
end
private
def authorize_user!
authorize(post, :authorized?)
end
def post_params
params.require(:post).permit(:title, :content, :user_id, :blog_id)
end
end
Here i'm using pundit to authorize user, when they update or destroy posts (users can update or destroy only their own posts) and it works perfectly.
views/posts/new
.row
.columns
h2 = title("New post")
.row
.medium-5.columns
= simple_form_for post do |f|
= f.error_notification
.form-inputs
= f.input :title
= f.input :content
= f.hidden_field :blog_id, value: blog.id
.form-actions
= f.button :submit
Here i'm using the hidden form to set the blog_id which I take from params. Http link looks like http://localhost:3000/posts/new?blog_id=6. The problem is that each user can copy this link to create the post( and they are not the blog owners).
post_policy.rb
class PostPolicy < ApplicationPolicy
def authorized?
record.user == user
end
end
How should I check the blog's owner before post creating? Maybe I have a wrong way to create posts like this(using hidden form).
Link to create new post
= link_to 'New Post', new_post_path(blog_id: blog.id)
I hope, it will work for you
application_controller.rb
class ApplicationController
include Pundit
after_action :verify_authorized, except: :index
after_action :verify_policy_scoped, only: :index
before_action :authenticate_admin_user!
helper_method :current_user
def pundit_user
current_admin_user
end
def current_user
#current_user ||= User.find(current_admin_user.id)
end
end
posts_controller.rb
class PostsController < ApplicationController
before_action :set_blog
def new
authorize(Post)
end
def edit
#post = #blog.posts.find(params[:id])
authorize(#post)
end
def index
#posts = policy_scope(#blog.posts)
end
private
def set_blog
#blog = current_user.blogs.find(params[:blog_id])
end
end
post_policy.rb
class PostPolicy < ApplicationPolicy
def show?
true
end
def index?
true
end
def new?
create?
end
def create?
true
end
def edit?
update?
end
def update?
scope_include_object?
end
def destroy?
scope_include_object?
end
class Scope < Scope
def resolve
scope.joins(:blog).where(blogs: { admin_user_id: user.id })
end
end
def scope_include_object?
scope.where(id: record.id).exists?
end
end
routes.rb
Rails.application.routes.draw do
devise_for :admin_users
resources :blogs do
resources :posts
end
end

Create a has_many :posts for categories in rails

I want to the category I create in relation with the post I want to create. I don't want to add a gem or anything else I think we can do it with has_manyand belongs_to
I create two tables Posts and Category and I want to choose a category in a collection and this is written in the post new#view I want to create and on the post show#view and post index#view.
Models for posts is :
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :category
TAGS = ["Design", "Mode", "Tendance", "Life-Style", "Tradition", "Gastronomie", "Insolite", "Technologie"]
validates :tag, inclusion: { in: Post::TAGS, allow_nil: false }
mount_uploader :cover, ImageUploader
end
and categories is foreign keys for posts here is the model
class Category < ActiveRecord::Base
has_many :posts
NAMES = ["JAPON", "CHINE", "INDE"]
validates :name, inclusion: { in: Category::NAMES, allow_nil: false }
end
Posts Controllers are here
class PostsController < ApplicationController
before_filter :authenticate_user!, except: [:index, :show]
before_action :find_post, only: [:show, :edit, :update, :destroy]
def index
#posts = Post.all
end
def show
# #alert_message = "Vous lisez #{#post.title}"
end
def new
# if current_user and current_user.admin?
#post = Post.new
# else
# redirect_to posts_path
# end
end
def create
# if current_user and current_user.admin?
#post = current_user.posts.new(post_params)
##post = current_user.posts.new(post_params)
if #post.save
redirect_to #post
else
render :new
end
# else
# render 'shared/404.html.erb'
# end
end
def edit
end
def update
if #post.update(post_params)
redirect_to #post
else
render :edit
end
end
def destroy
#post.destroy
redirect_to :back
end
private
def find_post
#post = Post.find(params[:id])
end
# def set_category
# #post_category = Category.find(params[:category_id])
# end
def post_params
params.require(:post).permit(:title, :subtitle, :introduction, :body, :cover, :tag, :category_id)
end
end
and categories_controller are here
class CategoriesController < ApplicationController
before_action :set_category, only: [:show, :new, :create, :destroy]
def show
#category = Category.find(params[:id])
end
def index
#categories = Category.all
end
def create
#category = Category.new(category_params)
if #category.save
redirect_to #post
else
render :new
end
end
def new
#category = Category.new
end
def edit
end
def update
end
def destroy
#category = Category.find(params[:id])
#category.destroy
redirect_to post_path(#post)
end
private
# def set_post
# #post = Post.find(params[:post_id])
# end
# def set_category
# #category = Category.find(params[:category_id])
# end
def set_category
if params[:id].present?
#category = Category.find(params[:id])
else
#category = Category.new
end
end
# def find_category
# #category = Category.find(params[:id])
# end
def category_params
params.require(:category).permit(:name, :description)
end
end
Please could you show the right way to add a category I choose in collection and I show in post new#view show#view index#view.
Thank you for your help.
If a category can belong to more than one post, I would recommend using a has_may_through relationship for your data model.
class Post
has_many :post_categories
has_many :categories, through: post_categories
accepts_nested_attributes_for :categories
end
class Category
has_many :post_categories
has_many :posts, through: post_categories
end
class PostCategory
belongs_to :posts
belongs_to :categories
end
You will need to create a migration to add the 'through' table, PostCategory, which will consist of a post_id and a category_id.
In the controller
def new
#post = Post.new
#post.categories.build
end
def post_params
params.require(:post).permit(:title, :subtitle, :introduction, :body, :cover, :tag, category_ids: [])
end
In your form, you can use fields_for to build the form for categories.
If you set this all up, rails will handle the creation of the category when the post is created. Then you will be able to call category.posts to get all the posts with that category and you can call post.categories to get all the categories assigned to the post.
Assigning a Post to a Category can be accomplished in the create method of your PostsController. You are already passing the category_id to the controller via the params.
def create
#post = current_user.posts.new(post_params)
#category = Category.find(params[:category_id])
if #post.save && (#category.posts << #post)
redirect_to #post
else
render :new
end
end
When you are trying to show a post in a view, you should be able to access that category directly.
<%= post.category.name %>
If you allow some posts to be created without a category, you can simply not show anything, or show a "No Category" message.
# Don't show anything if the post doesn't belong to a category
<%= post.category.name if post.category.present? %>
# Show a "No Categories" message (this uses the ternary operator)
<%= post.category.present? ? post.category.name : "No Category" %>

Nested Comments with nested answers: undefined method `answers' for nil:NilClass | Rails

I am trying to implement nested answers into comments, which are nested into auctions.
There is a auctions.rb model, which:
has_many :comments, dependent: :destroy
has_many :answers, :through => :comments
a comments.rb model, which:
belongs_to :auction
has_many :answers, dependent: :destroy
a answers.rb model, which:
belongs_to :comment
the answers_controller inherits from the comments_controller:
class AnswersController < CommentsController
before_action :all_answers, only: [:index, :create, :update, :destroy]
before_action :set_answer, only: [:edit, :update, :destroy]
respond_to :html, :js
# New Answer (Form)
def new
#answer = Answer.new
#comments.answers.build
end
# Create Answer
def create
#answer = #comment.answers.build(answer_params)
#answer.user_id = current_user.id
#answer.save
end
# Edit Answer
def update
#answer.update!(answer_params)
end
# Delete Answer
def destroy
#answer = Comment.find(params[:id])
#comment = #answer.comment
#answer.destroy
end
private
def all_answers
#answers = #comment.answers.all
end
def set_answer
#answer = #comment.answers.find(params[:id])
end
def answer_params
params.require(:comment).permit(:body)
end
end
The Error:
NoMethodError in Auctions#show app/views/comments/_comment.html.erb
where line #20 raised: undefined method `answers' for nil:NilClass
<div class="col s12" id="answer-form" style="display:none;"></div>
</div>
<div class="row">
<div class="col s12" id="answers"><%= render #comment.answers %></div>
</div>
With <%= render #comment.answers %> I want to display all existing answers below the related comment. What am I doing wrong?
auction_controller
class AuctionsController < ApplicationController
# Index of all auctions
def index
#auctions = Auction.all
end
# Show Auction by :id
def show
#auction = Auction.find(params[:id])
# Find Seller by ID
#seller = User.find(#auction.user_id)
# Find highest bid, by finding all related bids and ordering in descending and picking the first
#highest_bid = Bid.where(auction_id: params[:id]).order("amount DESC").first
# Find product
#product = Product.find(#auction.product_id)
end
# New Auction Form
def new
#auction = Auction.new
end
# Edit Auction
def edit
#auction = Auction.find(params[:id])
end
# Create new Auction
def create
# Create new Auction
#auction = Auction.new(auction_params)
# Save Id of User (Seller)
#auction.user_id = current_user.id
# If auction was created successfully
if #auction.save
# display the created auction
redirect_to #auction, :notice => "Auction created"
else
# display Form again if unsuccessful
render 'new'
end
end
# Update existing Auction
def update
#auction = Auction.find(params[:id])
# Validation
if #auction.update(auction_params)
redirect_to #auction, :notice => "Auction updated"
else
render 'edit'
end
end
# Delete Auction
def destroy
#auction = Auction.find(params[:id])
#auction.destroy
redirect_to auctions_path, :notice => "Auction deleted"
end
private
# set required parameters for new created Auctions
def auction_params
params.require(:auction).permit(:condition, :product_name)
end
end
comments_controller
class CommentsController < ApplicationController
before_action :set_auction
before_action :all_comments, only: [:index, :create, :update, :destroy]
before_action :set_comment, only: [:edit, :update, :destroy]
respond_to :html, :js
# New Comment (Form)
def new
#comment = Comment.new
#auction.comments.build
end
# Create Comment
def create
#comment = #auction.comments.build(comment_params)
#comment.user_id = current_user.id
#comment.save
end
# Edit Comment
def update
#comment.update!(comment_params)
end
# Delete Comment
def destroy
#comment = Comment.find(params[:id])
#auction = #comment.auction
#comment.destroy
end
private
def set_auction
#auction = Auction.find(params[:auction_id])
end
def all_comments
#comments = #auction.comments.all
end
def set_comment
#comment = #auction.comments.find(params[:id])
end
def comment_params
params.require(:comment).permit(:body)
end
end
Normal Comments work. Only Comment Answers don't work.
The error happens in Auctions#show, the error clearly tells you that you are trying to call answers on a nil object. Therefore, it means #comment is nil in that view.
In fact, if you check the show action, you never fetch/assign any object to #comment.
# Show Auction by :id
def show
#auction = Auction.find(params[:id])
# Find Seller by ID
#seller = User.find(#auction.user_id)
# Find highest bid, by finding all related bids and ordering in descending and picking the first
#highest_bid = Bid.where(auction_id: params[:id]).order("amount DESC").first
# Find product
#product = Product.find(#auction.product_id)
end
In order to fix it, make sure #comment is properly assigned to a Comment instance.
There's another problem here:
def new
#answer = Answer.new
#comments.answers.build
end
You haven't got a variable called #comments, hence your form can't actually build answers off it. In fact, you're calling #comment in two other methods, where I can't even see it being declared:
def all_answers
#answers = #comment.answers.all
end
def set_answer
#answer = #comment.answers.find(params[:id])
end
The only time you've declared #commentis in the destroymethod:
def destroy
#answer = Comment.find(params[:id])
#comment = #answer.comment
#answer.destroy
end
Even then, it's weird.
Why are you calling the Comment model with the #answer variable? Surely you'd have an Answer model with comments attached by way of a has_many relationship?
I'd recommend you keeping it brain-dead simple:
#app/models/answer.rb
class Answer < ActiveRecord::Base
has_many :comments
end
#app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :answer
end
This means when you call your actions controller, you'll be able to apply the following:
def show
#answer = Answer.find params[:id]
#comment = #answer.comments.build
end
If you wanted to have comments act as answers, you need to keep the models separate. Use a hierchy gem, like closure tree. This way, you'll be able to keep your Answers/Comments in hierarchy order, whilst keeping the Models consistent.

rails 4. how to create actions using relationships between three models?

I would like to create relationships between three models: user, post and comment.
User have many posts and comments
Post have only one user and many comments
Comment have one user and one post
so i create next migrations:
class Users < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name
t.string :email
t.timestamps
end
end
end
class Posts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.string :content
t.integer :user_id
t.timestamps
end
end
end
class Comments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.string :title
t.string :content
t.integer :user_id
t.integer :post_id
t.timestamps
end
end
end
=============================================
models are next:
user.rb
class User < ActiveRecord::Base
has_many :posts
has_many :comments
end
post.rb
class Post < ActiveRecord::Base
belongs_to :user
has_many :comments
end
comment.rb
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :post
end
===============================================
My users_controller.rb
class UsersController < ApplicationController
before_action :signed_in_user, only: [:index, :edit, :update, :destroy]
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])
#posts = #user.posts.paginate(page: params[:page])
#comments = #user.comments.paginate(page: params[:page])
end
def new
#user = User.new(params[:user])
end
def edit
##user = User.find(params[:id])
end
def update
##user = User.find(params[:id])
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] = "User deleted."
redirect_to users_url
end
def create
#user = User.new(user_params)
if #user.save
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
end
private
def user_params
params.require(:user).permit(: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
now i want to create some actions for next tasks:
For posts_controller.rb
1.1 create a post by user
1.2 delete a post by user
1.3 show user post with all comments
1.4 show all user posts
class PostsController < ApplicationController
before_action :signed_in_user, only: [:create, :destroy]
before_action :correct_user, only: :destroy
def create
#post = user.post.build(post_params)
#post = post.save
end
def destroy
#post.destroy
end
def show_user_post_with_all_comments
???
end
def show_all_user_posts
???
end
private
def post_params
params.require(:post).permit(:title, :content)
end
def correct_user
#post = current_user.posts.find_by(id: params[:id])
redirect_to root_url if #post.nil?
end
end
For comments_controller.rb
2.1 create a comment by user in post
2.2 delete a comment by user in post
2.3 show all user comments
2.4 find and show a post by user comment
class CommentsController < ApplicationController
before_action :signed_in_user, only: [:create, :destroy]
before_action :correct_user, only: :destroy
def create
#comment = user.comment.build(comment_params)
#comment = comment.save
end
def destroy
#comment.destroy
end
def show_comment
???
end
def show_all_user_comments
???
end
def find_and_show_post_by_user_comment
???
end
private
def comment_params
params.require(:comment).permit(:content)
end
def correct_user
#comment = current_user.comments.find_by(id: params[:id])
redirect_to root_url if #comment.nil?
end
end
Pls check for correct my migrations and models and help me with creating of actions with "???" in bodies
Thank you much for your answers.
PostsController
def show_user_post_with_all_comments
#post = Post.find(params[:id]).eager_load(:comments)
end
def show_all_user_posts
#posts = current_user.posts
end
CommentsController
def show_comment
#comment = Comment.find(params[:id])
end
def show_all_user_comments
#comments = current_user.comments
end
def find_and_show_post_by_user_comment
#comment = Comment.find(params[:id]).eager_load(:post)
#post = #comment.post
end
What I've done in the past in a similar situation would be to put all this work in the UsersController and add a few new actions to it:
class UsersController < ApplicationController
...
def new_post
#user = User.find(params[:id])
end
def create_post
#user = User.find(params[:id])
if #user.update_attributes user_post_params
redirect_to somewhere_path
else
render 'new_post'
end
end
def show_post
#post = Post.find(params[:id])
# Not sure how you are implementing sessions, but say you have current_user defined
# for sessions, then your view could have a delete link conditional on
# #post.user_id == current_user.id
#comments = #post.comments
end
def show_all_posts
#user = User.find(params[:id])
#posts = #user.posts
end
def new_comment
#user = current_user
#post = Post.find(params[:id])
end
def create_comment
#user = current_user
#post = Post.find(params[:id])
#comment = Comment.new(comment_params)
if #post.update_attributes comment_params
#user.comments << #comment
if #user.save
redirect_to somewhere
else
render 'new_comment'
end
else
render 'new_comment'
end
end
def show_comments
#user = User.find(params[:id])
#comments = #user.comments
end
...
private
def user_post_params
params.require(:user).permit(:id, posts_attributes: [:title, :content])
end
def comment_params
params.require(:post).permit(:id, comments_attributes: [:content, :user_id])
end
In show_post.html.erb:
<% if #post.user_id == current_user.id %>
<%= link_to 'delete', post, method: :delete, data: { confirm: "you sure?" }
<% end %>
in your routes.rb:
get '/user/:id/new_post' => 'users#new_post', as: :user_new_post
put '/user/:id/create_post' => 'test_takers#create_post', as: :user_create_post
...and similar lines for the other actions.
Hopefully this can get you started...

Resources