I've been trying to get the ancestry gem for rails to work but I seem to fail at implementing even the most simplest case. I'm new to rails so I could easily be making a rookie mistake.
What I was trying to implement was just a normal commenting system where replies to comments would be shown all on the same page using ancestry's child/parent functionality.
Below is my show html which has a reply link which is where I am trying to set up the parent child relationship:
<p id="notice"><%= notice %></p>
<%=#comment.children.nil?%>
<p>
<strong>Body:</strong>
<%= #comment.body %>
</p>
<p>
<%if !#children.nil?%>
<% #children.each do |c|%>
c.body
<%end%>
<%end%>
</p>
<p>
<strong>Author:</strong>
<%= #comment.author %>
</p>
<%= link_to 'Reply', new_comment_path, :parent_id => #comment %>
<%= link_to 'Edit', edit_comment_path(#comment) %> |
<%= link_to 'Back', comments_path %>
And here is some of the controller methods:
class CommentsController < ApplicationController
before_action :set_comment, only: [:show, :edit, :update, :destroy]
# GET /comments
# GET /comments.json
def index
#comments = Comment.all
end
# GET /comments/1
# GET /comments/1.json
def show
#children=#comment.children
end
# GET /comments/new
def new
#parent_id = params.delete(:parent_id)
#comment = Comment.new(:parent_id => #parent_id)
end
//omitted code
private
# Use callbacks to share common setup or constraints between actions.
def set_comment
#comment = Comment.find(params[:id])
end
I can not seem to get any parent/child relationship with the above code
ok I figured it out in the end. I was only passing the parent parameter to the "new" controller method, I should have been passing it again after this at the create stage. Was a silly mistake.
Related
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've got a User and Post models, which are related to each other in a classical way -- User has_many :posts and Post belongs_to :user. In my users#show, where I display a user's profile, I also have a list of all posts he has made. Also, I wanted to have links to edit and delete each post respectfully. So, I made up with this:
<% #user.posts.each do |post| %>
<h1><%= link_to post.title, post_path(post) %></h1>
<% if #user == current_user %>
<%= link_to 'Edit', edit_post_path(post) %>
<%= link_to 'Delete', post_path(post), method: :delete %>
<% end %>
<% end %>
But surely placing this logic into view results in a mess, so I decided to use Draper and write decorators for that. As we are going to check rights for posts#edit and posts#delete methods, I came up with a decorator for Post model and tried to use it in PostsController. Here it goes:
class PostDecorator << Draper::Decorator
delegate_all
def link_to_edit
if object.user == current_user
h.link_to 'Edit', h.edit_post_path(object)
end
end
def link_to_delete
if object.user == current.user
h.link_to 'Delete', h.post_path(object), method: :delete
end
end
end
Then, in my PostsController:
# ... class definition
before_action :set_post, only: [:show, :edit, :update, :destroy]
# ... other controller methods
def edit; end
def update
if #post.update(post_params)
#post.save
redirect_to post_path(#post)
else
render 'edit'
end
end
def destroy
#post.destroy
redirect_to feed_path
end
private
# Using FriendlyId gem to have neat slugs
def set_post
#post = Post.friendly.find(params[:id]).decorate
end
But every time I try to render my User profile with list of his posts, with the use of my new helpers <%= post.link_to_delete %> and <%= post.link_to_edit %> instead of that conditional mess, it just returns me the following error:
What am I doing wrong?
You probably figured this out in the meantime but here's an answer for others: You were calling #post = ....decorate in your controller but you are using #user.posts.each { |post| ... } in your view. The objects fed to that block are not decorated. Only #post is.
In your view you should have done something like #user.posts.each { |raw_post| post = raw_post.decorate } and so on. Obviously, with ERB syntax. Or #user.decorated_posts.each ... where
class User < ActiveRecord::Base
...
def decorated_posts
# this will load every post associated with the user.
# if there are a lot of them you might want to only load a limited scope of them
posts.map(&:decorate)
end
...
end
I created a simple rails app in rails using scaffolding method for restaurants.
This is the show and edit controller method for restaurants_controller.rb. Notice how they are just blank methods:
# GET /restaurants/1
# GET /restaurants/1.json
def show
end
# GET /restaurants/1/edit
def edit
end
This is restaurants/show.html.erb:
<p id="notice"><%= notice %></p>
<%= image_tag #restaurant.image_url %>
<p>
<strong>Name:</strong>
<%= #restaurant.name %>
</p>
<p>
<strong>Address:</strong>
<%= #restaurant.address %>
</p>
...
and restaurants/edit.html.erb:
<h1>Editing Restaurant</h1>
<%= render 'form', restaurant: #restaurant %>
<%= link_to 'Show', #restaurant, class: "btn btn-link" %> |
<%= link_to 'Back', restaurants_path, class: "btn btn-link" %>
Here is my question: my current understanding (could be wrong) is that we define the instance variable, in this case, #restaurant in restaurant_controllers.rb, and Rails automatically connects the variables defined in the controller to views. For example, index method in restaurant controller:
def index
#restaurants = Restaurant.all
end
when I call #restaurants in index.html.erb, Rails brings up #restaurants from index method to be used in views.
Where does rails get the #restaurant instance variable in show.html.erb and edit.html.erb from even though show and edit method in restaurants_controller.rb are empty methods? I am using Rails 5.
So rails acheives this by applying this to generated scaffolds
class TestsController < ApplicationController
before_action :set_test, only: [:show, :edit, :new, :update, :destroy]
private
def set_test
#test = Test.find(params[:id])
end
end
that dose the same as adding
#test = Test.find(params[:id]) to every action if you don't need to..
that then, sets the instance variable on the actions defined by the before_action
im no rails pro, so there may be a much better answer to this, but i have learned to not question "rails magic".
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 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