so I'm trying to create a video game review website for practice.
A game has many reviews, and votes. The idea is, in order to post a review, you must vote "Good" or "Bad" first, THEN submit a review. You can't post a text review without voting.
I'm trying to do this without the acts_as_voteable gem...
The data format for votes is boolean. "Good" is true, "Bad" is false.
How do I get the votes to save? below are my routes.rb, _review partial, reviews controller, and show page.
many thanks guys :)
edit****: also I'm trying to only one vote per user. I was thinking of using a token variable which equals to 1, and when a vote is cast, the token is -1. Is that a good approach? But the data type for vote is boolean, so how would that work -- or should I change the data type for vote from boolean to integer?
edit#2 -- so I added :vote into my params.
routes.rb
upvote_game_review_path
POST /games/:game_id/reviews/:id/upvote(.:format) reviews#upvote
downvote_game_review_path
POST /games/:game_id/reviews/:id/downvote(.:format) reviews#downvote
Rails.application.routes.draw do
devise_for :users
root "games#index"
resources :games do
resources :news
resources :reviews, except: [:show, :index] do
member do
post "upvote"
post "downvote"
end
end
end
resources :platforms
resources :genres
end
reviews_controller.rb
class ReviewsController < ApplicationController
before_action :set_review, only: [:show, :update, :edit, :destroy]
before_action :set_game
before_action :authenticate_user!
def new
#review = Review.new
end
def create
#review = Review.new(review_params)
#review.user_id = current_user.id
#review.game_id = #game.id
if #review.save
redirect_to #game
else
render "review"
end
end
def upvote
#review.vote.create = true
redirect_to #game
end
def downvote
#review.vote.create
#review.vote = false
redirect_to #game
end
def edit
#review.update(review.params)
end
def destroy
#review.destroy
redirect_to #game
end
private
def set_review
#review = Review.find(params[:id])
end
def set_game
#game = Game.find(params[:game_id])
end
def review_params
params.require(:review).permit(:comment, :vote)
end
end
_review partial <-- to create a new review
<%= form_for [#game, #reviews.new] do |r| %>
<h3 class="post_review">Review this game</h3>
<p>
<%= r.text_area :comment %>
</p>
<p>
<%= button_to "Good", upvote_game_review_path(#game.id, r) %>
</p>
<p>
<%= button_to "Bad", downvote_game_review_path(#game.id, r) %>
</p>
<p>
<%= r.hidden_field :game_id, value: #game.id %>
<p>
<%= r.submit %>
<% end %>
show.html.erb
<p><%= link_to "<< Home", games_path %></p>
<span><%= link_to "Edit", edit_game_path(#game) %></span>
<span><%= link_to "Delete", game_path(#game), method: :delete %></span>
<div class="game_summary">
<h2><%= #game.title %></h2>
<%= image_tag #game.image %>
<p>Release Date: <%= #game.release_date %> </p>
<p>Genre: <%= #game.genre_id %> </p>
<p>Platforms: <%= #game.platform_id %></p>
</div>
<%= link_to "Add News", new_game_news_path(#game) %>
<h2>News & Articles</h2>
<%= link_to "view all", game_news_index_path(#game) %>
<% #news.each do |n| %>
<ol>
<li><%= link_to n.title, game_news_path(#game.id, n.id) %></li>
</ol>
<% end %>
<div class="game_review submit">
<%= render "review" %>
</div>
<% #reviews.each do |review| %>
<p><%= review.comment %></p>
<p><%= link_to "delete", game_review_path(#game.id, review.id), method: :delete %></p>
<% end %>
You don't specify which review you're loading in. The reason is here:
before_action :set_review, only: [:show, :update, :edit, :destroy]
You don't pull in the request's review instance when you go to either of those actions. Further, it doesn't look like you're actually saving them.
So, two things I'd recommend:
Add those methods to your before_action:
before_action :set_review, only: [:show, :update, :edit,
:destroy, :upvote, :downvote]
(May not be necessary, write tests to confirm this!) Actually save the entity after you've changed its value.
def upvote
#review.vote.create = true
#review.save
redirect_to #game
end
def downvote
#review.vote.create unless #review.vote
#review.vote = false
#review.save
redirect_to #game
end
Related
I'm trying to display all albums in the albums#index page but I'm getting an error in my Albums controller "cannot find album without ID". I understand the issue is that there are no params, but I've used the find method with params[:id] a bunch of times in my app and haven't had this issue thus far.
For reference, Albums have many Reviews and have many Users through Reviews.
Users have many Reviews and have many Albums through Reviews.
I haven't built out my Reviews controller yet so that's unrelated.
Here is the error:
ActiveRecord::RecordNotFound in AlbumsController#index
Couldn't find Album without an ID
Extracted source (around line #40):
38
39
40
41
42
43
def set_album
#album = Album.find(params[:id])
end
def album_params
Rails.root: /Users/melc/review_project
Application Trace | Framework Trace | Full Trace
app/controllers/albums_controller.rb:40:in `set_album'
Request
Parameters:
None
Here is my Albums controller:
class AlbumsController < ApplicationController
before_action :set_album, only: [:index, :show, :edit, :update]
def index
#albums = Album.all
#current_user
end
def show
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
Here is my albums#index view:
<h2>All Albums</h2>
<br>
<br>
<% 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>
<%= link_to "Upload a New Album", new_album_path %>
Here is the routes.rb 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
end
resources :users
root to: "albums#index"
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end
you need to change a couple things here:
on AlbumsController, you need to remove index from actions that "preload" an album
before_action :set_album, only: [:show, :edit, :update]
You need to pass the album object to the route in the view:
<%= link_to "Edit Album", edit_album_path(#album) %><br><br>
hope that helps
EDIT: about the avatar issue, looks like you're displaying the albums in the index, but you aren't iterating through them, something like:
<h2>All Albums</h2>
<% #albums.each do |album| %>
<br>
<br>
<% if album.avatar&.attached? %>
<image src="<%=(url_for(album.avatar))%>%" style="width:350px; height:350px;">
<% end %>
<br>
<%= album.artist %> - <%= album.title %>
<%= link_to "Edit Album", edit_album_path(album) %><br><br>
<br>
<% end %>
<%= link_to "Upload a New Album", new_album_path %>`
Not sure why comments created on _comment_form.html.erb is not being rendered on articles/:id
I'm referencing this youtube tutorial: https://www.youtube.com/watch?v=IUUThhcGtzc
Comments controller:
class CommentsController < ApplicationController
before_action :authenticate_user!
before_action :set_article
def create
#comment = #article.comments.create(params[:comment].permit(:content, :article_id, :user_id))
#comment.user = current_user
#comment.save
if #comment.save
redirect_to #article
else
redirect_to #article
end
end
private
def set_article
#article = Article.find(params[:article_id])
end
end
Articles controller:
class ArticlesController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
before_action :set_article, only: [:show, :edit, :update, :destroy]
def show
#comments = Comment.where(article_id: #article).order("created_at DESC")
end
private
def set_article
#article = Article.find(params[:id])
end
end
_comment.html.erb (from articles/show.html.erb)
<%= render 'comments/comment_form' %>
<% #comments.each do |comment| %>
<%= comment.content %>
<% end %>
_comment_form.html.erb
<% if user_signed_in? %>
<%= form_for ([#article, #article.comments.build]) do |f| %>
<div class="form-group">
<%= f.label :content %>
<%= f.text_area :content, class: 'form-control' %>
</div>
<%= f.submit 'Post Comment', class: 'btn btn-primary' %>
<% end %>
<% end %>
routes.rb
resources :articles do
resources :comments
end
Some considerations I would like to do:
#comment.save
if #comment.save
When you do that, you're saving twice. Calling #comment.save on if condition already saves the article and return true or false if it was successful. Also, replace .save for .save!, so it will raise an exception in case it doesn't save, so you can check the reason on rails server log.
Also, think it's unnecessary to do this:
#comments = Comment.where(article_id: #article).order("created_at DESC")
Since you already set the #article, you can access #article.comments, once you put has_many :comments on article model.
You can also check on rails console if the Article was created correctly. Create a new one, and get it like article = Article.last, then you can check article.comments.
Hope this helps!
I am a junior developer building an e-commerce web application using rails 4.2.4, Devise and a Pin scaffolding.
Right now, users can signup, sign in and then CREATE, READ UPDATE, DESTROY a pin on the index.html.erb.
Issue: I do not want users who sign in to be able to CRUD ( ONLY READ ).
I want to set this up so I as an ADMIN Can only create, update or destroy. Users and guests can only read.
I have been struggling with this for weeks, and would be grateful for help as to how I could achieve this please.
This is my PinsController
class PinsController < ApplicationController
before_action :set_pin, only: [:show, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update, :destroy]
before_action :authenticate_user!, except: [:index, :show]
def index
if params[:search].present? && !params[:search].nil?
#pins = Pin.where("description LIKE ?", "%#{params[:search]}%").paginate(:page => params[:page], :per_page => 15)
else
#pins = Pin.all.order("created_at DESC").paginate(:page => params[:page], :per_page => 15)
end
end
def show
end
def new
#pin = current_user.pins.build
authorize(#pin)
end
def edit
end
def create
#pin = current_user.pins.build(pin_params)
if #pin.save
redirect_to #pin, notice: 'Pin was successfully created.'
else
render :new
end
end
def update
if #pin.update(pin_params)
redirect_to #pin, notice: 'Pin was successfully updated.'
else
render :edit
end
end
def destroy
#pin.destroy
redirect_to pins_url
end
private
# Use callbacks to share common setup or constraints between actions.
def set_pin
#pin = Pin.find_by(id: params[:id])
end
def correct_user
#pin = current_user.pins.find_by(id: params[:id])
redirect_to pins_path, notice: "Not authorized to edit this pin" if #pin.nil?
end
# Never trust parameters from the scary internet, only allow the white list through.
def pin_params
params.require(:pin).permit(:description, :image)
end
end
This is my Index.html.erb
<h1>For Sale</h1>
<%= form_tag pins_path, method: :get do %>
<div class="field">
<%= label_tag :Description %>
<%= text_field_tag :search %>
<%= submit_tag "Search", name: nil, class: "btn btn-success btn-sm" %>
<%= link_to 'Clear', pins_path, class: 'btn btn-danger btn-sm' %>
<% end %>
<div id="pins" class="transitions-enabled">
<% #pins.each do |pin| %>
<div class="box panel panel-default">
<%= link_to image_tag(pin.image.url(:medium)), pin %>
<div class="panel-body">
<%= pin.description %>
<%= link_to 'Show', pin_path(pin) %>
<% if current_user && pin.user == current_user %>
<%= link_to 'Edit', edit_pin_path(pin) %>
<%= link_to 'Destroy', pin, method: :delete, data: { confirm: 'Are you sure?' } %>
<% end %>
</div>
</div>
<% end %>
</div>
<div class="center">
<%= will_paginate #pins, renderer: BootstrapPagination::Rails %>
</div>
<div class=text-right>
<% if user_signed_in? %>
<%= link_to 'Post a Free Ad', new_pin_path, class: "btn btn-warning btn-lg" %>
</div>
<% end %>
<br>
You want everybody (logged in users or just quests) to be able to access the index and the show method. All other methods are exclusively for administrators.
First step: We need a way to identify an administrator. Add a boolean admin attribute to your the User model. Run the following command on your command line to create a new migration:
$ rails g migration add_admin_to_users admin:boolean
Open the generated file and add default: false to it, it should look like this:
class AddAdminToUsers < ActiveRecord::Migration
def change
add_column :users, :admin, :boolean, default: false
end
end
Now run rake db:migrate to add that column to your database.
Next step it to grant your own user administration rights. Log into the rails console (with $ rails c). Find your user and update the admin flag on your user to true:
> user = User.find_by(email: 'your-eamiladdress#example.tld')
> user.admin = true
> user.save
> user.admin?
# => true
As you see Rails automatically adds a admin? method to the user. We use that method in the controller now:
before_action :find_pin, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, except: [:index, :show]
before_action :ensure_admin, except: [:index, :show]
With this private methods:
def find_pin
#pin = Pin.find(params[:id]) # renders 404 in production when pin isn't found
end
def ensure_admin
unless current_user.admin?
redirect_to(pins_path, notice: 'Not authorized to edit this pin')
end
end
Use the same admin? method in the view to hide edit and destroy links from non-admins:
<%= link_to 'Show', pin_path(pin) %>
<% if current_user && current_user.admin? %>
<%= link_to 'Edit', edit_pin_path(pin) %>
<%= link_to 'Destroy', pin, method: :delete, data: { confirm: 'Are you sure?' } %>
<% end %>
I am trying to access an "edit" link to edit an object, but I'm getting this error:
Param is missing or the value is empty: preview
Basically, I have 2 models that I linked through association:
Game model
Review model
I'm rendering reviews in the Game's show page. When I try to edit a review, it's saying I'm missing params or the value is empty in the Reviews controller.
The routes are also nested. How can I fix this?
Thanks in advance :)
routes.rb
Rails.application.routes.draw do
devise_for :users
root "games#index"
resources :games do
resources :news
resources :reviews, except: [:show, :index]
resources :previews, except: [:show, :index]
end
resources :platforms
resources :genres
end
show.html.erb (Linked to Games controller)
<% if #news.last.created_at > preview.updated_at %>
<p><%= link_to "edit", edit_game_preview_path(#game.id, preview.id) %></p>
<% end %>
<p><%= link_to "delete", game_preview_path(#game.id, preview.id), method: :delete %></p>
<% end %>
Reviews partial (Form)
<%= form_for [#game, #previews.new] do |r| %>
<h3 class="post_review">Preview this game</h3>
<p><%= flash[:notice_submit] %></p>
<p><%= r.text_field :title, placeholder: "Enter your tagline" %></p>
<p><%= r.text_area :content, placeholder: "Enter your review here" %></p>
<p><%= r.text_area :vote %></p>
<p><%= r.hidden_field :game_id, value: #game.id %></p>
<%= r.submit %>
<% end %>
Reviews controller
class PreviewsController < ApplicationController
before_action :authenticate_user!
before_action :set_preview, only: [:show, :edit, :update, :destroy]
before_action :set_game
def new
#preview = Preview.new
end
def create
#preview = Preview.new(preview_params)
#preview.user_id = current_user.id
#preview.game_id = #game.id
#preview.username = current_user.username
if #preview.save
redirect_to :back
flash[:notice_submit] = "Thanks for you comment!"
else
redirect_to :back
flash[:notice_submit] = "Either you've already voted, or you're not filling in all forms."
end
end
def edit
#preview.update(preview_params)
redirect_to #game
end
def destroy
#preview.destroy
redirect_to #game
end
private
def set_preview
#preview = Preview.find(params[:id])
end
def set_game
#game = Game.find(params[:game_id])
end
def set_user
#user = User.find(params[:user_id])
end
def preview_params
params.require(:preview).permit(:title, :content, :vote)
end
end
You are getting this error because in your preview_params you are requiring a preview object.
I think your controller logic for the edit action is invalid. For the edit action, you just need to set_preview and then render the edit template. The current logic in your edit action should go in an update action.
def edit
end
def update
#preview.update(preview_params)
redirect_to #game
end
Also the first line of your form should be:
<%= form_for [#game, #preview] do |r| %>
so I'm trying to create a videogame review website for practice. each game would have it's own page, and on each page, i'd like to have a form where you can input reviews, and have it show up on the same page when submitted.
I'm not sure how to handle this. but I managed to create two controllers -- one for creating games, and one for creating reviews.
I use devise for user logins/registration
I'll post the codes below: please let me know if you need to see other files.
routes.rb
Rails.application.routes.draw do
devise_for :users
resources :games
resources :reviews
root "games#index"
end
games_controller.rb -- to create new games
class GamesController < ApplicationController
before_action :set_game, only: [:show, :edit, :update, :destroy]
def index
#games = Game.all
end
def new
#game = Game.new
end
def create
#game = Game.create(game_params)
redirect_to #game
end
def edit
end
def update
#game.update(game_params)
redirect_to #game
end
def destroy
end
private
def game_params
params.require(:game).permit(:title, :image, :developer, :genre, :release_date, :platform)
end
def set_game
#game = Game.find(params[:id])
end
end
reviews_controller.rb -- to create reviews
class ReviewsController < ApplicationController
before_action :set_review, only: [:show, :edit, :update, :destroy]
def index
end
def show
end
def new
#review = Review.new
end
def create
#review = Review.create(review_params)
redirect_to #review
end
private
def review_params
params.require(:review).permit(:review)
end
def set_review
#review = Review.find(params[:id])
end
end
show.html.erb for games
<% if user_signed_in? %>
<p class=""><%= link_to "Update Details", edit_game_path %></p>
<p class=""><%= link_to "Update News", new_game_path %></p>
<% end %>
<%= flash[:notice] = "Successfully updated." %>
<hr>
<div class="game_summary">
<%= image_tag #game.image %>
<div class="game_details">
<h3 class="game_title"><%= #game.title %></h3>
<ul class="game_info">
<li>Developer: <%= #game.developer %></li>
<li>Release Date: <%= #game.release_date %></li>
<li>Genre: <%= #game.genre %></li>
<li>Platform(s): <%= #game.platform %></li>
</ul>
<button class="buttons" id="buy">Buy</button>
<button class="buttons" id="rent">Rent</button>
</div>
</div>
<div class="game_news">
<h2 class="game_news_title">News & Articles</h2>
</div>
<div class="game_reviews">
<%= link_to "Add Review", new_review_path %>
<h2>Reviews</h2>
<ul>
<li><%= %></li>
<li><%= %></li>
<li><%= %></li>
<li><%= %></li>
</ul>
</div>
In your games_controller.rb, you'd need a show method (which is created by default when you use scaffolding, but which you somehow missed). This show method would gather all the reviews pertaining to the game in question, assuming your Review model has a reference to its corresponding Game model (both as a foreign key specified in schema.rb and via belongs_to and has_many in their respective models).
The show method would look like this:
def show
#reviews = Review.where("game_id = ?", #game.id)
end
Then in your view, you'd have something like this:
<% #reviews.each do | review | %>
<%= review.title %>
<%= review.content %>
<% end %>
I have no idea what fields are contained in your Review object, but you can extrapolate from this example to get what you want.