Cannot find bids from post_id - ruby-on-rails

I'm having trouble finding my bids that are linked to specific posts via the index method. I have successfully linked my bids to posts (i.e. I have a post_id column within my bids table), but cannot seem to 'find' them. I currently get the error 'Couldn't find Bid with 'id'='
Bids controller:
class BidsController < ApplicationController
def index
#bid = Bid.find(params[:post_id])
end
end
View:
<ul>
<% #bid.each do |bid| %>
<li>$<%= bid.price.floor %></li>
<% end %>
</ul>
Models (post and bid):
class Bid < ActiveRecord::Base
belongs_to :post
end
class Post < ActiveRecord::Base
has_many :bids
end
Routes:
Rails.application.routes.draw do
root 'static_pages#home'
get 'add' => 'posts#new'
get 'posts' => 'posts#index'
get '/posts/:id/new_bid' => 'bids#new'
get '/posts/:id/bids' => 'bids#index'
resources :posts
resources :bids
end
Any help is appreciated. Thank you.

In your routes, you have
get '/posts/:id/bids' => 'bids#index'
Therefore, you will have a params[:id] for your controller. Then in your controller, you should use it:
class BidsController < ApplicationController
def index
#bids = Post.find(params[:id]).bids
end
end
You might consider using nested resources in routes:
resources :posts do
resources :bids
end
Then you will automatically have posts/:post_id/bids and you can use parmas[:post_id] in your controller.

Your index action should be doing used where to find all the bids with a particular post_id.
def index
#bid = Bid.where(post_id: params[:post_id])
end
else you can find the post first and then load the bids from the post like so.
def index
#bid = Post.find(params[:post_id]).bids
end

This error is the result of an empty string sent to Model.find(). You may reproduce this error in rails console ( rails c) with Bid.find('').
By the route file you provide, you should use :id instead of :post_id. And because you want all bids that matches the post_id, this should be #bid=Bid.where(post_id: params[:id])

Related

How to save data from a new object in a nested resource in rails?

I am looking to create a new record within a nested resource in rails as according to http://guides.rubyonrails.org/routing.html.
My model is:
class Entry < ApplicationRecord
belongs_to :user
belongs_to :event
class User < ApplicationRecord
has_many :entries, dependent: :destroy
class Event < ApplicationRecord
has_many :entries, dependent: :destroy
And I have declared my route as
Rails.application.routes.draw do
resources :users
resources :events do
resources :entries
end
What is the syntax for the entries controller for me to be able to create an entry on a link like events/2/entries/new. This is what I was trying:
class EntriesController < ApplicationController
def new
#entry = Entry.new
end
def create
#entry = Entry.new(params[:entry])
if #entry.save
redirect_to #user
flash.now[:info] = "Event Created"
else
render '/create'
flash.now[:danger] = "Somthing went wrong"
end
end
def entry_params
params.require(:event_id).permit(:siCard, :course)
end
end
On by new.html.erb I am using
<%= form_for(new_event_entry_path) do |f| %>.
But I cant get it to work as No route matches [POST] "/events/1/entries/new"
Many thanks
The route you've provided in form_for does not accept POSTs. You've provided the route that renders the new page, not the route that accepts new records, which is the create route. For that, you'll want to use event_entries_path(#event) route helper.
With nested resources, the form_for's first argument should be an array: <%= form_for([#event, #entry], .... Rails will intelligently choose the route you need based on whether the #entry is persisted or not.

How to set a like button in rails

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.

Creating "polymorphic" controller

Im working on adding voting to my app (like in Stackoveflow). I have two models Questions and Answers so i want to be able to vote for both of them. I see two ways to manage voting for different type of models:
Add concern for models, controllers and routes.
Add votes_controller that can handle voting for any model that has votable.
I`d like to use second way to solve my problem. But to use controller I will should pass two parameters to controller, like: votable_type: model-name, votable-id: object.id, and my route will look like: vote_up_vote_path, vote_down_vote_path.
Is there a way to use routes like: vote_up_path(answer); vote_down_path(question)?
And by passing object "vote_up_path (answer)" i want to be able to get it in controller
P.S. I`m not able to use gems. Gems provide logic for models, I'm already have this logic.
I found the solution. So at first we need to generate Votes controller.
$rg controller Votes
Than we add routes:
resource :vote, only: [:vote_up, :vote_down, :unvote] do
patch :vote_up, on: :member
patch :vote_down, on: :member
patch :unvote, on: :member
end
And add in votes_helper.rb:
module VotesHelper
def vote_up_path(votable)
{controller: "votes", action: "vote_up",
votable_id: votable.id, votable_type: votable.class}
end
def vote_down_path(votable)
{controller: "votes", action: "vote_down",
votable_id: votable.id, votable_type: votable.class}
end
def unvote_path(votable)
{controller: "votes", action: "unvote",
votable_id: votable.id, votable_type: votable.class}
end
end
Than we should add tests and complete our methods. In controller we can use this method to find our votable:
private
def set_votable
klass = params[:votable_type].to_s.capitalize.constantize
#votable = klass.find(params[:votable_id])
end
Highly recommend this gem to handle upvote/downvote:
Acts as votable
There are two ways to do this; the first is to use superclassed controllers, the second is to use an individual controller with a polymorphic model.
I'll detail both:
Superclassing
You can set a method in a "super" controller, which will be inherited by the answers and questions controllers respectively. This is kind of lame because it means you're sending requests to the answers or questions controllers (which is against the DRY (Don't Repeat Yourself) principle):
#config/routes.rb
resources :answers, :questions do
match :vote, via: [:post, :delete], on: :member
end
#app/controllers/votes_controller.rb
class VotesController < ApplicationController
def vote
if request.post?
# Upvote
elsif request.delete?
# Downvote
end
end
end
#app/controllers/answers_controller.rb
class AnswersController < VotesController
# Your controller as normal
end
... Even better ...
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def vote
...
end
end
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
....
end
This will allow you send the following:
<%= link_to "Upvote", answers_vote_path(#answer), method: :post %>
<%= link_to "Downvote", questions_vote_path(#question), method: :delete %>
-
The important factor here is that you need to ensure your vote logic is correct in the backend, which you'll then be able to use the vote method in the VotesController to get it working.
Polymorphic Model
Perhaps the simplest way will be to use a polymorphic model:
#app/models/vote.rb
class Vote < ActiveRecord::Base
belongs_to :voteable, polymorphic: true
end
#app/models/answer.rb
class Answer < ActiveRecord::Base
has_many :votes, as: :voteable
end
#app/models/vote.rb
class Vote < ActiveRecord::Base
has_many :votes, as: :voteable
end
This will allow you to do the following:
#config/routes.rb
resources vote, only: [] do
match ":voteable_type/:voteable_id", action: :vote, via: [:post,:delete], on: :collection #-> url.com/vote/answer/3
end
#app/controllers/votes_controller.rb
class VotesController < ApplicationController
def vote
if request.post?
#vote = Vote.create vote_params
elsif request.delete?
#vote = Vote.destroy vote_params
end
end
private
def vote_params
params.permit(:voteable_id, :voteable_type)
end
end
This would allow you to use the following:
<%= link_to "Vote", votes_path(:answer, #answer.id) %>

rails model associations recorded in controller

I have an application with 3 models user company and post.
I'm having trouble with figuring out the logic
A user can have many companies and a company can have many posts.
here are my models
class user < ActiveRecord::Base
has_many :companies
end
class company < ActiveRecord::Base
belongs_to :user
has_many :posts
end
class post < ActiveRecord::Base
belongs_to :company
end
Note: company has user_id, post has company_id in table
how do I make sure when creating a post the company_id is automatically recorded
extra information: for routes it will be resources :jobs and resources :companies
--update--
post controller
def new
Post.new(post_params)
end
def create
Post.new(post_params)
if #post.save
redirect_to #post
else
render 'new'
end
end
routes
resources :companies
resources :posts
I think I would probably nest posts within companies, so you would have available to you the company_id in the parameters.
Note your path names will change if you do this.
Routes.rb
resources :companies do
resources :posts
end
posts_controller.rb
def create
#company = Company.find(params[:company_id])
#post = #company.posts.build(post_params)
...
end
And since you will want to find the company on each of these actions in the controller, you can refactor it a bit.
class PostsController
before_action :set_company
def index
#post = #company.posts
end
def create
#post = #company.posts.build(post_params)
...
end
...
...
private
def set_company
#company = Company.find(params[:company_id])
end
end
2 options come to mind.
You can create a hidden company_id field in your posts#new view, so it would be sent automatically along with your params and hence be set in the model accordingly (provided that you permit it). But in that case you'll need to populate that field in the controller when creating the #post. Just like this:
#post = Post.new(company_id: some_company_id)
Use nested resources.First of all change your routes to this:
resources :companies do
resoures :posts
end
That way when you look at the rake routes output you'll see the :company_id param being set in the request url. So when you go on creating a post, you can do #post.company_id = params[:company_id] and you're done.

Route concern and polymorphic model: how to share controller and views?

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.

Resources