So I'm pretty new to Rails; I have an assignment I'm trying to finish for my coursework and I'm stuck. I already have Topics and Posts for a reddit clone that the assignment is on. But now I'm supposed to add one summary for each post and after diving into that for awhile, I am stuck.
Here are the assignment requirements:
Create a Summary model which references posts.
Modify Post so that it has_one summary.
Create the necessary routes to create and show post summaries.
Create Summary views to create and show post summaries.
The error I'm hitting when I try to follow a 'Summary' link I've created to sit next to a post title:
ActionController::UrlGenerationError in Topics#show
No route matches {:action=>"show", :controller=>"summaries", :id=>nil, :post_id=>nil, :topic_id=>"1"} missing required keys: [:id, :post_id]
It's pointing me to the Topics#show method which looks like this:
def show
#topic = Topic.find(params[:id])
#posts = #topic.posts
authorize #topic
end
And my SummariesController looks like this:
class SummariesController < ApplicationController
def new
#topic = Topic.find(params[:topic_id])
#post = #topic.posts
#summary = Summary.new
authorize #summary
end
def create
#topic = Topic.find(params[:topic_id])
#post = #topic.posts
#summary = Summary.new(params.require(:summary).permit(:description))
authorize #summary
if #summary.save
flash[:notice] = "Your post summary was saved"
redirect_to [#topic, #post]
else
flash[:error] = "Sorry. There was an error saving your summary. Please try again"
render :new
end
end
def show
#summary = Summary.find(params[:id])
#post = Post.find(params[:post_id])
#topic = Topic.find(params[:topic_id])
authorize #summary
end
end
And here are my models:
Post:
class Post < ActiveRecord::Base
has_many :comments
has_one :summary
belongs_to :user
belongs_to :topic
default_scope { order('created_at DESC') }
end
Topic:
class Topic < ActiveRecord::Base
has_many :posts
end
Summary:
class Summary < ActiveRecord::Base
belongs_to :post
end
Lastly here is my routes file and the html.erb link:
Rails.application.routes.draw do
devise_for :users
resources :topics do
resources :posts, except: [:index] do
resources :summaries, except: [:index, :edit]
end
end
get 'about' => 'welcome#about'
root to: 'welcome#index'
end
html.erb summary Link:
<small>
<%= link_to "Summary", topic_post_summary_path(#topic, #post, #summary)%>
</small>
I'm not sure where I'm going wrong here.
Related
Let's say I have those two models:
class Post < ApplicationRecord
belongs_to :site
end
class Site < ApplicationRecord
has_many :posts
end
In order to create a post, I need to know the site id. Right now I have a route that points to PostsController#create:
post 'posts', to: 'posts#update'
Should I expect the user to send the site_id in the body of the request?
# config/routes.rb
resources :sites do
resources :posts
end
This creates nested routes. Run $ rails routes to see the routes created.
Should I expect the user to send the site_id in the body of the request?
No. A nested route describes the relationship between the two resources. Its very obvious by looking at the path that POST /sites/1/posts will create a post belonging to a site.
It would be ok to pass a site id in the params if you are using shallow nesting and the user can change which site a post belongs to when updating.
# app/controllers/posts_controller.rb
class PostsController
before_action :set_site
before_action :set_post, only: [:show, :edit, :update]
# GET /sites/1/posts/1
def show
end
# GET /sites/1/posts
def index
#posts = #site.posts
end
# GET /sites/1/posts/new
def new
#post = #site.posts.new
end
# POST /sites/1/posts
def create
#post = #site.posts.new(post_params)
if #post.save
redirect_to #post
else
render :new
end
end
# PATCH|PUT /sites/1/posts
def update
if #post.update(post_params)
redirect_to #post
else
render :edit
end
end
# GET /sites/1/posts/edit
def edit
end
private
def set_site
#site = Site.includes(:posts).find(params[:site_id])
end
def set_post
#post = #site.posts.find(params[:id])
end
def post_params
params.require(:post).permit(:title) # ...
end
end
# app/views/posts/_form.html.erb
<%= form_for [#site, #post] do |f| %>
# ...
<% end %>
# app/views/posts/new.html.erb
<%= render partial: 'form' %>
# app/views/posts/edit.html.erb
<%= render partial: 'form' %>
Hello I have an exercise app where a user should be able to Like some products.
I could find a way to display the product he liked, but I really can't figure how to create and make work the like button.
I am not using any gem, I wan't to understand how to do it from Scratch.
Here are my models:
class User < ApplicationRecord
has_many :likes
has_many :liked_products, through: :likes, source: :product
end
class Product < ApplicationRecord
has_many :likes
end
class Like < ApplicationRecord
belongs_to :user
belongs_to :product
end
In my view product show where I want the like button:
<h1><%= #product.name %></h1>
<%= link_to "Like", product_likes_path(#product), method: :put, remote: true %>
my routes:
Rails.application.routes.draw do
root to: 'visitors#index'
devise_for :users
resources :users
resources :products do
resource :likes
end
end
That's my products controller, I think things must come in here but I don't know HOW!
class ProductsController < ApplicationController
before_action :find_product, only: :show
def index
#products = Product.all
end
def show
##product.like => gives an error 404
end
private
def find_product
#product = Product.find(params[:id])
end
end
I had created a likes controller but it seems it is not useful.... So... I gave up there...
class LikesController < ApplicationController
def new
#like = Like.new(like_params)
end
def create
#like = Like.new(like_params)
end
private
def like_params
params.require(:likes).permit(:user_id, :product_id)
end
end
I would really enjoy some light on this please :)
Finally found out how to set the controller
class LikesController < ApplicationController
def create
#user = current_user.id
#product = params[:product_id]
likes = {user_id: #user, product_id: #product}
#like = Like.new(likes)
#like.save!
if #like.save
redirect_to user_path(#user)
else
redirect_to product_path
end
end
end
the buttton
<%= link_to "Like", product_likes_path(#product), method: :post %>
routes
Rails.application.routes.draw do
root to: 'products#index'
devise_for :users
resources :users
resources :users do
resources :products do
resources :likes
end
end
end
You could try something along these lines:
Routes:
Rails.application.routes.draw do
root to: 'visitors#index'
devise_for :users
resources :users do
resources :products do
resources :likes
end
end
resources :products do
resource :likes
end
end
Which will give you something like:
... other routes ...
user_product_likes GET /users/:user_id/products/:product_id/likes(.:format) likes#index
POST /users/:user_id/products/:product_id/likes(.:format) likes#create
new_user_product_like GET /users/:user_id/products/:product_id/likes/new(.:format) likes#new
edit_user_product_like GET /users/:user_id/products/:product_id/likes/:id/edit(.:format) likes#edit
user_product_like GET /users/:user_id/products/:product_id/likes/:id(.:format) likes#show
PATCH /users/:user_id/products/:product_id/likes/:id(.:format) likes#update
PUT /users/:user_id/products/:product_id/likes/:id(.:format) likes#update
DELETE /users/:user_id/products/:product_id/likes/:id(.:format) likes#destroy
... other routes ...
Then:
<%= link_to "Like", user_product_likes_path(#user, #product), method: :post, remote: true %>
And in your LikesController:
class LikesController < ApplicationController
def new
#like = Like.new(like_params)
end
def create
#like = Like.new(like_params)
if #like.save
... do something happy
else
... do something sad
end
end
private
def like_params
params.require(:likes).permit(:user_id, :product_id)
end
end
Untested, so buyer beware. You might need to fiddle with your like_params and other stuff.
I can't get a like system working for my Rails discussion forum:
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :comment
end
class Post < ActiveRecord::Base
belongs_to :user
has_many :comments
has_many :likes, through: :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :user
has_many :likes
end
routes.rb:
resources :posts do
resources :comments do
resources :likes do
put "/create", to: "likes#create"
end
end
end
likes_controller.rb:
class LikesController < ApplicationController
def create
#post = Post.find(params[:post_id])
#comment = Comment.find(params[:comment_id])
#like = Like.find(params[:like_id])
#user = current_user.id
Like.create(like_id: #like, post_id: #post, comment_id: #comment, user_id: #user)
respond_to do |format|
format.html { redirect_to post_path(#post) }
end
end
end
rake routes:
post_comment_like_create PUT /posts/:post_id/comments/:comment_id/likes/:like_id/create(.:format) likes#create
post_comment_likes GET /posts/:post_id/comments/:comment_id/likes(.:format) likes#index
POST /posts/:post_id/comments/:comment_id/likes(.:format) likes#create
etc
The problem I keep running into is that it's missing like_id:
No route matches {:action=>"create", :comment_id=>"218", :controller=>"likes", :like_id=>nil, :post_id=>"30"} missing required keys: [:like_id]
What am I doing wrong? I assume most of the code is correct, as the only error it's giving me is a missing ID, I just don't understand how or where to fetch that like_id.
EDIT:
This is the action I'm trying to use:
= link_to post_comment_like_create_path(#post, comment, #like)
The issue is partly in your routes, partly in your controller and partly in the view.
By using the line resources :likes, you are creating routes for the 7 CRUD actions automatically. This means you do not need to manually declare a create action in your routes (which should be a POST rather than PUT). I'd suggest reading the Rails Guide on Routing.
In your controller, you are attempting to create an object called #like by finding a Like based on an ID. But you are creating this for the first time, so there is nothing to find.
In your view, you shouldn't be using a link_to for anything that affects the database but rather a button_to and the path you are using is also part of the problem.
Make the following changes:
routes.rb
resources :posts do
resources :comments do
resources :likes
end
end
likes_controller.rb
class LikesController < ApplicationController
def create
#post = Post.find(params[:post_id])
#comment = Comment.find(params[:comment_id])
#user = current_user.id
Like.create(post_id: #post, comment_id: #comment, user_id: #user)
respond_to do |format|
format.html { redirect_to post_path(#post) }
end
end
end
view
= button_to post_comment_likes_path(#post, comment)
I would suggest you look at the guides around nesting of routes. Nesting this deep can easily become cumbersome.
Given the routes:
Example::Application.routes.draw do
concern :commentable do
resources :comments
end
resources :articles, concerns: :commentable
resources :forums do
resources :forum_topics, concerns: :commentable
end
end
And the model:
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
end
When I edit or add a comment, I need to go back to the "commentable" object. I have the following issues, though:
1) The redirect_to in the comments_controller.rb would be different depending on the parent object
2) The references on the views would differ as well
= simple_form_for comment do |form|
Is there a practical way to share views and controllers for this comment resource?
In Rails 4 you can pass options to concerns. So if you do this:
# routes.rb
concern :commentable do |options|
resources :comments, options
end
resources :articles do
concerns :commentable, commentable_type: 'Article'
end
Then when you rake routes, you will see you get a route like
POST /articles/:id/comments, {commentable_type: 'Article'}
That will override anything the request tries to set to keep it secure. Then in your CommentsController:
# comments_controller.rb
class CommentsController < ApplicationController
before_filter :set_commentable, only: [:index, :create]
def create
#comment = Comment.create!(commentable: #commentable)
respond_with #comment
end
private
def set_commentable
commentable_id = params["#{params[:commentable_type].underscore}_id"]
#commentable = params[:commentable_type].constantize.find(commentable_id)
end
end
One way to test such a controller with rspec is:
require 'rails_helper'
describe CommentsController do
let(:article) { create(:article) }
[:article].each do |commentable|
it "creates comments for #{commentable.to_s.pluralize} " do
obj = send(commentable)
options = {}
options["#{commentable.to_s}_id"] = obj.id
options["commentable_type".to_sym] = commentable.to_s.camelize
options[:comment] = attributes_for(:comment)
post :create, options
expect(obj.comments).to eq [Comment.all.last]
end
end
end
You can find the parent in a before filter like this:
comments_controller.rb
before_filter: find_parent
def find_parent
params.each do |name, value|
if name =~ /(.+)_id$/
#parent = $1.classify.constantize.find(value)
end
end
end
Now you can redirect or do whatever you please depending on the parent type.
For example in a view:
= simple_form_for [#parent, comment] do |form|
Or in a controller
comments_controller.rb
redirect_to #parent # redirect to the show page of the commentable.
I have has_many :posts, :dependent => :destroy in my user.rb and belongs_to :user in my post.rb, in my post migration i have t.references :user and add_index :posts, :user_id and then in my routes.rb i have:
resources :users do
resources :posts
end
How do I make it so that when i'm logged in as a user and I make a post the i can user user.posts and access those posts?
respond_to :html
def index
#posts = current_user.posts
end
def new
#post = current_user.posts.new
end
def edit
#post = current_user.posts.find params[:id]
end
def create
#post = current_user.posts.new params[:post]
#post.save
respond_with #post
end
def update
#post = current_user.posts.find params[:id]
#post.update_attributes params[:post]
respond_with #post
end
def destroy
#post = current_user.posts.find params[:id]
#post.destroy
respond_with #post
end
An alternative is to do:
def create
if current_user.posts.create!(params[:post])
# success
else
# validation errors
end
end
The bottom line is, you want the post to have a foreign key called user_id that ties it to a user object. By doing current_user.posts... it automatically associates the two.