I'm making a website for my sports team where players can make blog posts.
class Player < ApplicationRecord
has_many :blogs, dependent: :destroy
end
class Blog < ApplicationRecord
belongs_to :player
end
My question is:
What is the difference between setting up the route/controller action with the following two params? They are both giving me the exact same thing.
1)
route:
get "players/:player_id/posts" => "blogs#show", as: "player_posts"
controller action:
class BlogsController < ApplicationController
def show
#player = Player.find(params[:player_id])
#blogs = #player.blogs
end
end
2)
route:
get "players/:id/posts" => "blogs#show", as: "player_posts"
controller action:
class BlogsController < ApplicationController
def show
#player = Player.find(params[:id])
#blogs = #player.blogs
end
end
What is the difference between setting up the route/controller action
with the following two params?
There is no difference between those two named routes, except you are using different keys(:player_id & :id). I suggest the first route since you fetching players instance in the blogs controller.
get "players/:player_id/posts" => "blogs#show", as: "player_posts"
#controller action:
class BlogsController < ApplicationController
def show
#player = Player.find(params[:player_id]) #players instance
#blogs = #player.blogs
end
end
Related
More specifically, if I have two options: do a check in the controller or override an association method in the model, which one should I prefer?
Edit:
class Book < ApplicationRecord
belongs_to :author
def author
super || build_author
end
end
Is the code above ok or should I prefer another solution like the one below?
class BooksController < ApplicationController
def update
set_author
if #book.update_attributes(params[:book])
#redirect
else
#render show page - unprocessable entity
end
end
def set_author
a = Author.where(fields)
#book.author = a || #book.build_author
end
end
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) %>
How to configure the rails controller so I can have a user post a submission in no matter what contest. When they post their user id and the contest id should be automatically appended to the submission.
I know I can do:
User.first.contests.create => let the user create a contest
Contest.first.submissions.create => create a submission in a contest (not linked to a user)
User.first.submissions.create => create a submission linked to a user but not to a contest
I cannot do User.first.Contest.last.submissions.create => I want to link a submission to a contest and to a submission.
Is there an elegant way to fix this?
The submission controller looks like this:
class SubmissionsController < ApplicationController
before_action :set_submission, only: [:show, :edit, :update, :destroy]
# the current user can only edit, update or destroy if the id of the pin matches the id the user is linked with.
before_action :correct_user, only: [:edit, :update, :destroy]
# the user has to authenticate for every action except index and show.
before_action :authenticate_user!, except: [:index, :show]
respond_to :html
def index
#title = t('submissions.index.title')
#submissions = Submission.all
respond_with(#submissions)
end
def show
#title = t('submissions.show.title')
respond_with(#submission)
end
def new
#title = t('submissions.new.title')
#submission = Submission.new
respond_with(#submission)
end
def edit
#title = t('submissions.edit.title')
end
def create
#title = t('submissions.create.title')
#submission = Submission.new(submission_params)
#submission.save
respond_with(#submission)
end
def update
#title = t('submissions.update.title')
#submission.update(submission_params)
respond_with(#submission)
end
def destroy
#title = t('submissions.destroy.title')
#submission.destroy
respond_with(#submission)
end
private
def set_submission
#submission = Submission.find(params[:id])
end
def submission_params
arams.require(:submission).permit(:reps, :weight, :user_id)
end
def correct_user
#submission = current_user.submissions.find_by(id: params[:id])
redirect_to submissions_path, notice: t('submissions.controller.correct_user') if #submission.nil?
end
end
I have following models:
class Contest < ActiveRecord::Base
has_many :submissions
has_many :users, through: :submissions
class Submission < ActiveRecord::Base
belongs_to :user
belongs_to :contest
class User < ActiveRecord::Base
has_many :submissions
has_many :contests, through: :submissions
I think you're making this a bit complicated.
Submission is POSTED within Contest, Submission needs to know the user_id.
<%= simple_form_for :submission, url: contest_submissions_path(contest) do |f| %>
...
<%= f.submit 'Submit', class: "button" %>
<% end %>
And on your submissions CREATE method
class SubmissionsController < ApplicationController
def create
#contest = Contest.find(params[:contest_id])
#submission = #contest.submissions.new(submission_params)
#submissions.user = current_user
.....
end
The magic happens at #submissions.user = current_user If you are using Devise, it is easy to pass in the current_user.id ANYWHERE in the controller, as I just did in the submissions controller.
Are you able to use #submission = current_user.submissions.new(submission_params) and #contest = Contest.find(params[:contest_id]) in your SubmissionsController#create
EDIT: I've added some details on adding a reference to contest_id in the submissions table.
The best way I've found to tie related things together in Rails (and indeed, any relational database) is to add a reference in the child table to the parent's id. You can do this with a migration in Rails.
rails g migration AddContestToSubmission contest:references
And modify the migration file generated in your db/migrate/<datetime>_add_contest_to_submission to look similar to:
class AddContestToSubmission < ActiveRecord::Migration
def change
add_reference :submissions, :contest, index: true
end
end
Then go ahead and look at your submissions table in your schema.rb. You should notice something like t.integer "contest_id" You should probably also add the user_id in your migration is you want a submission to be tied to one user.
In my rails app I have a User Model where I have an attribute: points
class User < ActiveRecord::Base
attr_accessible :points
has_many :answers
end
Is there a way to make the attribute updated only from specific controller actions?
class AnswersController < ApplicationController
....
def create
#user = current_user
#answer = #user.answers.build(params[:answer])
if #answer.save
#user.points += 10 # <-- I want to make points updatable only from this action
#user.save
else
...
end
...
end
If I have a nested resource like so:
resources :users
resources :posts
end
and a user has_many posts, it is possible to have Rails start numbering based on the parent association in the URL? For example, currently, nesting resources just grabs the ID:
#user.posts.find(params[:id])
This correctly namespaces the posts, only allowing posts from #user... however, is there a way such that the post_id is independent? I.E. I want each user's posts to start at 1, where:
/users/1/posts/1
/users/2/posts/1
Actually refer to two different posts?
It can be quite a bit of work, but basically you can do it with these steps:
Create a migration to add a new attribute to store the specific user-post count. (I used user_post_id)
Override Post's to_param method to use the new value you just created. (It has to be a string.)
to_param is the method that the url and path helpers use.
Create a before_save filter that will actually increment the user_post_id value for each new post.
Change all your controller methods to find on user_post_id
#user = User.find(params[:user_id])
#post = #user.posts.where(:user_post_id => (params[:id])).first
Change all your Views that might not work now
You can see the source here: Custom Nested Resource URL example
Code
migration:
class AddUserPostIdToPosts < ActiveRecord::Migration
def change
add_column :posts, :user_post_id, :integer
end
end
post.rb:
class Post < ActiveRecord::Base
before_save :set_next_user_post_id
belongs_to :user
validates :user_post_id, :uniqueness => {:scope => :user_id}
def to_param
self.user_post_id.to_s
end
private
def set_next_user_post_id
self.user_post_id ||= get_new_user_post_id
end
def get_new_user_post_id
user = self.user
max = user.posts.maximum('user_post_id') || 0
max + 1
end
end
A couple controller methods
posts_controller.rb:
class PostsController < ApplicationController
respond_to :html, :xml
before_filter :find_user
def index
#posts = #user.posts.all
respond_with #posts
end
def show
#post = #user.posts.where(:user_post_id => (params[:id])).first
respond_with [#user, #post]
end
...
end