I am trying to set up a 5 star rating system so users can rate other users. At the moment everything is working, (create, delete, update etc...) but only the logged in user can rate himself. I cannot rate other users. I get no errors, it just redirects to the user profile page as it should but without added a rating to that user.
user.rb
class User < ActiveRecord::Base
has_many :reviews
review.rb
class Review < ActiveRecord::Base
belongs_to :user
end
reviews_controller.rb
class ReviewsController < ApplicationController
before_action :find_user
before_action :find_review, only: [:edit, :update, :destroy]
def new
#review = Review.new
end
def create
#review = Review.new(review_params)
#review.user_id = current_user.id
if #review.save
redirect_to user_path(#user)
else
render 'new'
end
end
def edit
end
def update
if #review.update(review_params)
redirect_to user_path(#user)
else
render 'edit'
end
end
def destroy
#review.destroy
redirect_to user_path(#user)
end
private
def review_params
params.require(:review).permit(:rating, :comment)
end
def find_user
#user = User.find(params[:user_id])
end
def find_review
#review = Review.find(params[:id])
end
end
_form which then gets rendered on show page:
<%= simple_form_for([#user, #user.reviews.build]) do |f| %>
<div id="rating-form">
<label>Rating</label>
</div>
<%= f.input :comment %>
<%= f.button :submit %>
<% end %>
<script>
$('#rating-form').raty({
path: '/assets/',
scoreName: 'review[rating]'
});
</script>
Any help getting this to work would be greatly appreciated!!
Do this:
#config/routes.rb
resources :users do
resources :reviews, only: [:new, :create]
end
#app/models/review.rb
class Review < ActiveRecord::Base
belongs_to :user
belongs_to :reviewed, class_name: "User", foreign_key: :reviewed_id
end
#app/controllers/reviews_controller.rb
class ReviewsController < ApplicationController
def new
#review = current_user.reviews.new
end
def create
#review = current_user.reviews.new review_params
#review.save
end
private
def review_params
params.require(:review).permit(:rating, :comment).merge(reviewed_id: params[:user_id])
end
end
#app/views/reviews/new.html.erb
<%= form_for #review do |f| %>
<%= f.number_field :rating %>
<%= f.text_field :comment %>
<%= f.submit %>
<% end %>
This would mean you'll have to include a reviewed_id column in your reviews table.
You'll be able to access it using: url.com/users/:user_id/reviews/new
The application will automatically fill the user_id and reviewed_id fields, so the rest of your code should work with the upgrade.
The big problem you have is that you're basically recording the user_id (presumably of who created the review)... but have no way of stipulating who the review is about.
The above code fixes that for you.
Related
I would like to route to the parent (journal) show page after deleting the child (habit) using the child_id.
In my app, a User can create journals, which then have multiple habits. I would like to be able to delete (and edit) a habit and then return to the journal show page, which displays all of the habits.
Getting the following and similar errors:
ActiveRecord::RecordNotFound in HabitsController#destroy
journal.rb
class Journal < ApplicationRecord
has_many :entries
has_many :habits
belongs_to :user
end
habit.rb
class Habit < ApplicationRecord
belongs_to :journal
has_many :completed_dates
end
show.html.erb
<h3>Habits</h3>
<% #journal.habits.each do |habit| %>
<div iv class="habit-list">
<div class="habit-name"><%= habit.name %></div>
<div class="habit-status">
<%= simple_form_for [#journal, #habit] do |f| %>
<%= f.input :status, label: false, :inline_label => true %>
<% end %>
</div>
<div>
<%= link_to habit_path(habit), method: :delete do %>
<i class="fas fa-trash btn-edit"></i>
<% end %>
</div>
</div>
<% end %>
habits_controller.rb
class HabitsController < ApplicationController
before_action :set_journal, only: [:new, :create, :edit, :update, :destroy]
before_action :set_habit, only: [:show, :edit, :update, :destroy]
def index
#habits = Habit.all.sort_by &:name
end
def new
#habit = Habit.new
end
def show
end
def create
#habit = #journal.habits.new(habit_params)
if #habit.save
redirect_to journal_path(#journal)
else
render :new
end
end
def edit
end
def update
if #journal.habits.update(habit_params)
redirect_to journals_path(#journal)
else
render :edit
end
end
def destroy
#habit.destroy
redirect_to journal_path(#habit, #journal)
end
private
def habit_params
params.require(:habit).permit(:name, :status, :user_id, :journal_id)
end
def set_journal
#journal = Journal.find(params[:journal_id])
end
def set_habit
#habit = Habit.find(params[:id])
end
end
Your trying to create URL to a deleted resource (#habit).
Change
def destroy
#habit.destroy
redirect_to journal_path(#habit, #journal)
end
to
def destroy
#habit.destroy
redirect_to journal_path(#journal)
end
I am new to rails and building my first app. I can't figure how to assign a car_id to new instances of awards that get created with the views/awards/new.html.erb file. Can anyone take a look and see where I am going wrong?
awards controller:
class AwardsController < ApplicationController
before_action :set_award, only: [:show]
def new
#award = Award.new
end
def index
#awards = Award.all
end
def create
#car = Car.params
#award = Award.new(award_params)
#award.save
redirect_to user_path(current_user)
end
def show
end
def destroy
end
private
def set_award
#award = Award.find(params[:id])
end
def award_params
params.require(:award).permit(:title, :year, :description)
end
end
cars controller
class CarsController < ApplicationController
before_action :set_car, only: [:show, :edit, :update, :destroy]
def new
#car = Car.new
end
def index
#cars = Car.all
end
def create
#car = Car.new(car_params)
#car.save
current_user.cars << #car
redirect_to current_user, :flash => { :success => "car created!" }
end
def edit
end
def update
#car.update(car_params)
if #car.valid?
#car.save
redirect_to user_path(current_user)
else
render :edit
end
end
def show
#car = Car.find(params[:id])
end
def destroy
#car.destroy
redirect_to user_path(current_user)
end
private
def set_car
#car = Car.find(params[:id])
end
def car_params
params.require(:car).permit(:make,:model,:year,:color)
end
end
car.rb
class Car < ApplicationRecord
belongs_to :user
has_many :awards
end
award.rb
class Award < ApplicationRecord
belongs_to :car
has_one :user, through: :cars
end
routes
Rails.application.routes.draw do
resources :awards
resources :cars
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
resources :users
root 'welcome#home'
resources :users do
resources :cars
end
resources :cars do
resources :awards
end
end
#awards form
<%= form_for #award do |f| %>
<%= f.label :title %>
<%= f.text_field :title %><br>
<%= f.label :year %>
<%= f.text_field :year %><br>
<%= f.label :description %>
<%= f.text_area :description %><br>
<%= f.submit %>
<%end%>
I need to be able to assign awards to specific cars, does anyone know how I can go about doing this?
Since you are using nested routes:
resources :cars do
resources :awards
end
Then:
app/controllers/awards_controller.rb
class AwardsController < ApplicationController
# add more into the array as you need
before_action :set_car, only: [:new, :create]
def new
#award = #car.awards.new
end
def create
#award = #car.awards.new(award_params)
#award.save
redirect_to user_path(current_user)
end
private
def set_car
#car = Car.find(params[:car_id])
end
end
app/views/awards/new.html.erb
<%= form_for [#car, #award] do |f| %>
...
Trivia:
because you are using nested routes, then your links would be something like:
<%= link_to 'New Award', new_car_award_path(SOMECAR) %>
<%= link_to 'Awards', car_awards_path(SOMECAR) %>
<%= link_to 'Edit Award', edit_car_award_path(SOMECAR, SOMEAWARD) %>
<%= link_to 'Delete Award', car_award_path(SOMECAR, SOMEAWARD), method: :delete %>
...just replace SOMECAR and SOMEAWARD with a Car and Award instances respectively, that you want to be in that link.
Recommendations:
handle validations errors. See tutorial
If possible, use shallow nesting
I am using Devise as my authentication system and simple form. I get a NoMethodError in Groups#show and an undefined method 'name' for nil:NilClass error. I use model associations to tie the groups and posts together. When I do puts post.user.name it correctly displays in my terminal but that line causes the above error and it's referencing Groups#show for some reason. Any thoughts?
Routes
resources :groups do
resources :posts
end
Group Model
class Group < ActiveRecord::Base
validates :user_id, presence: true
belongs_to :user
has_many :posts
has_many :comments
has_many :attachments
end
Post Model
class Post < ActiveRecord::Base
validates :user_id, presence: true
validates :caption, presence: true
belongs_to :user
belongs_to :group
has_many :comments, dependent: :destroy
end
Group Controller
class GroupsController < ApplicationController
before_action :authenticate_user!
def new
#group = current_user.groups.build
end
def create
#group = current_user.groups.build(group_params)
#group.user_id = current_user.id
if #group.save
redirect_to groups_path
else
render :new
end
end
...
private
def group_params
params.require(:group).permit(:group_name, :description, :user_id)
end
end
Posts Controller
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy]
before_action :owned_post, only: [:edit, :update, :destroy]
before_action :authenticate_user!
def index
#posts = Post.paginate(page: params[:page], per_page: 3).order('created_at DESC')
#post = current_user.posts.build
#attachments = Attachment.all
end
...
def new
#post = current_user.posts.build
end
def create
#post = current_user.posts.build(post_params)
#group = Group.find(params[:group_id])
#post.group_id = #group.id
if #post.save
redirect_to groups_path
else
render :new
end
end
...
private
def post_params
params.require(:post).permit(:caption, :user_id)
end
def set_post
#post = Post.find(params[:id])
end
def owned_post
unless current_user == #post.user
redirect_to root_path
end
end
end
groups/show.html.erb
<%= render "posts/index" %>
...
posts/_index.html.erb
<%= render 'posts/form' %>
<%= render 'posts/posts' %>
...
posts/_form.html.erb
<%= simple_form_for([#group, #group.posts.build]) do |f| %>
...
posts/_posts.html.erb
<% #group.posts.each do |post| %>
<%= puts post.user.name %> ISSUE
<%#<%= render 'posts/post', post: post %>
<% end %>
SOLUTION: After asking on Reddit Rails, a generous user offered a solution that works. Apparently the <%= simple_form_for([#group, #group.posts.build]) do |f| %> creates a new post and adds it to the groups.posts array and so this causes issues when it iterates over _posts.html.erb and there is no user. More information can be found here: https://www.reddit.com/r/rails/comments/4lygix/unidentified_method_for_nil_class_devise_and/
But replace the above simple_form line of code with <%= simple_form_for([#group, Post.new(group: #group)]) do |f| %> seemed to do the trick, as suggested by the generous user in the reddit link above.
I have three models, User, Movie, and Review. Here is the relation:
# User.rb
has_many :movies
has_many :reviews
# Movie.rb
belongs_to :user
has_many :reviews
# Review.rb
belongs_to :movies
belongs_to :users
Here is the routes:
# routes.rb
resources :movies do
resources :reviews
end
Here is the controller:
# reviews_controller.rb
class ReviewsController < ApplicationController
before_action authenticate_user!
before_action :find_movie
before_action :find_review, only: [:edit, :update, :destroy]
def new
#review = Review.new
end
def create
#review = Review.new(review_params)
if #review.save
redirect_to movie_path(#movie)
else
render 'new'
end
end
def edit
end
def update
if #review.update(review_params)
redirect_to movie_path(#movie)
end
end
private
def find_movie
#movie = Movie.find(params[:movie_id])
end
def find_review
#review = Review.find(params[:id])
end
def review_params
params.require(:review).permit(:rating, :comment)
end
end
I created a new and partial form and then in the show page of the movie, I create this line of code to show the button of creating new review for a particular movie:
# views/movies/show.html.erb
<%= link_to 'Give review', new_movie_review_path(#movie) %>
I don't want the user to create another review after they submit a review for the same movie. That's why I want to hide the "Give review" button if the user is already gave the feedback. How do I do that?
Something like:
<% unless current_user.reviews.select{|review| review.movie_id == #movie.id}.count > 0 %>
<%= link_to 'Give review', new_movie_review_path(#movie) %>
<% end %>
Could also use where instead:
Review.where(user_id: current_user.id, movie_id: #movie.id).count > 0
You should add a custom validation in the review model which checks for a preexisting review from the same user for the same movie.
If you have the current_user available to views then you can have something like the following to hide Give Review link:
# views/movies/show.html.erb
<%= link_to 'Give review', new_movie_review_path(#movie) unless current_user.movies.where(id: #movie.id).first.comments.any? %>
I have three models: User, Publisher and Interest all with many to many relationships linked through three join models but only 2 out of 3 join models record the id's of their 2 parent models. my UsersPublisher model does not link User to Publisher.
My Interestscontroller proccesses a form (see code) through which I ask the user to provide Interest and Publisher. The latter gets processed via the fields_for method which allows you to pass Publisher attributes via the InterestsController. the UsersPublisher join model records the user_id but the publisher_id is nil.
I've tried putting #users_publishers in both the new and create methods of Publishers- and InterestsController. My latest attempt of using after_action in the InterestsController (see code) has also failed. I've also tried the after_action way in the PublishersController
Your helped is highly appreciated!
The UsersPublisher join model
class UsersPublisher < ActiveRecord::Base
belongs_to :user
belongs_to :publisher
end
InterestsController
class InterestsController < ApplicationController
before_action :find_user
after_action :upublisher, only: [:new]
def index
#interests = policy_scope(Interest)
end
def show
#interest = Interest.find(params[:id])
end
def new
#interest = Interest.new
#interest.publishers.build
authorize #interest
end
def create
#interest = Interest.new(interest_params)
#users_interests = UsersInterest.create(user: current_user, interest: #interest)
authorize #interest
if #interest.save
respond_to do |format|
format.js
format.html {redirect_to root_path}
end
flash[:notice] = 'Thank you, we will be in touch soon'
else
respond_to do |format|
format.js { render }
format.html { render :new }
end
end
end
def edit
#interest = Interest.find(params[:id])
authorize #interest
end
def update
#interest = Interest.find(params[:id])
#interest.update(interest_params)
if #interest.save
flash[:notice] = 'Your interest has been added'
else
flash[:notice] = 'Oops something went wrong'
end
end
private
def interest_params
params.require(:interest).permit(:name, publishers_attributes: [:publisher,:id, :feed])
end
def find_user
#user = current_user
end
def upublisher
#users_publishers = UsersPublisher.create(publisher: #publisher, user: current_user)
end
end
Form
<%= form_for [#user, #interest] do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for :publishers do |ff| %>
<%= ff.label :publisher %>
<%= ff.text_field :publisher %>
<%= ff.label :feed %>
<%= ff.text_field :feed %>
<%end%>
<%= f.submit "Submit" %>
<%end%>
Since you're using fields_for, you'll want to make sure you have accepts_nested_attributes_for:
class UsersPublisher < ActiveRecord::Base
belongs_to :user
belongs_to :publisher
accepts_nested_attributes_for :publisher
end
This should fix your issue (if it's as you outlined).
Your question is pretty broad, so I don't know whether the above will work. Below are my notes...
From the looks of it, your structure is very complicated; you should work to make it as simple as possible. In the case of creating "interests", you may wish to get rid of the form completely:
#config/routes.rb
resources :publishers do
resources :interests, path: "interest", only: [:create, :destroy] #-> url.com/publishers/:publisher_id/interest
end
#app/controllers/interests_controller.rb
class InterestsController < ApplicationController
before_action :set_publisher
def create
current_user.interests.create publisher: #publisher
end
def destroy
#interest = current_user.interests.find_by publisher_id: #publisher.id
current_user.interests.delete #interest
end
private
def set_publisher
#publisher = UserPublisher.find params[:publisher_id]
end
end
You'd be able to use the above as follows:
<%= link_to "Add Interest", publisher_interest_path(#publisher), method: :post %>
<%= link_to "Remove Interest", publisher_interest_path(#publisher), method: :delete %>
Thinking about it properly, you've got a pretty bad structure.
I'd do something like this:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :interests
has_many :publishers, through: :interests
end
#app/models/interest.rb
class Interest < ActiveRecord::Base
belongs_to :user
belongs_to :publisher
accepts_nested_attributes_for :publisher
end
#app/models/publisher.rb
class Publisher < ActiveRecord::Base
has_many :interests,
has_many :users, through: :interests
end
This should give you the ability to create interests for any number of users and publishers. If you create a publisher for a specific user, you can use accepts_nested_attributes_for to pass the appropriate data:
#config/routes.rb
resources :users do
resources :interest, only: [:new, :create, :destroy] #-> url.com/users/:user_id/interests/new
end
#app/controllers/interests_controller.rb
class InterestsController < ApplicationController
def new
#user = User.find params[:user_id]
#interest = #user.interests.new
#interest.publisher.build
end
def create
#user = User.find params[:user_id]
#interest = #user.interests.new interest_params
end
private
def interest_params
params.require(:interest).permit(:user, :publisher)
end
end
#app/views/interests/new.html.erb
<%= form_for [#user, #interest] do |f| %>
<%= f.fields_for :publisher do |p| %>
<%= p.text_field :name %>
<% end %>
<%= f.submit %>
<% end %>