I am building an application that allows users to create a trip. However, for some reason I am not sure if I am truly utilizing the power of rails or am I being redundant in my code. In the following example you will see a trip controller where a trip can be created and then displayed. Everything works, I just want to make sure I am going about it in the most minimal fashion.
class TripsController < ApplicationController
def new
#user = User.find(session[:id])
#trip = Trip.new
end
def create
#trip = Trip.create(trip_params)
#user = User.find(session[:id])
redirect_to user_trip_path(#user.id, #trip.id)
end
def show
#trip = Trip.find(params[:id])
end
private
def trip_params
params.require(:trip).permit(:where, :when, :price_per_person)
end
end
To tighten it up, "scope the trip to the user".
class TripsController < ApplicationController
before_filter :find_user
def new
#trip = #user.trips.build #assuming a User has many trips
end
def create
#trip = #user.trips.create(trip_params) #you may want to add an if else here to catch bad trips
redirect_to user_trip_path(#user.id, #trip.id)
end
def show
#trip = #user.trips.find(params[:id])
end
private
def trip_params
params.require(:trip).permit(:where, :when, :price_per_person)
end
def find_user
#user = User.find(session[:id]) # or current_user if you are using popular authentication gems
end
end
It's about readability too, not just less lines.
Related
I don't know if I'm doing something wrong here but it seems like.
I use Pundit for authorization and I have set up a few models with it now.
Ive got a Category model which can only be created by admins. Also I don't want users to see the show/edit/destroy views either. I just want it to be accessed by admins. So far so good.
Will add some code below:
category_policy.rb
class CategoryPolicy < ApplicationPolicy
def index?
user.admin?
end
def create?
user.admin?
end
def show?
user.admin?
end
def new?
user.admin?
end
def update?
return true if user.admin?
end
def destroy?
return true if user.admin?
end
end
categories_controller.rb
class CategoriesController < ApplicationController
layout 'scaffold'
before_action :set_category, only: %i[show edit update destroy]
# GET /categories
def index
#category = Category.all
authorize #category
end
# GET /categories/1
def show
#category = Category.find(params[:id])
authorize #category
end
# GET /categories/new
def new
#category = Category.new
authorize #category
end
# GET /categories/1/edit
def edit
authorize #category
end
# POST /categories
def create
#category = Category.new(category_params)
authorize #category
if #category.save
redirect_to #category, notice: 'Category was successfully created.'
else
render :new
end
end
# PATCH/PUT /categories/1
def update
authorize #category
if #category.update(category_params)
redirect_to #category, notice: 'Category was successfully updated.'
else
render :edit
end
end
# DELETE /categories/1
def destroy
authorize #category
#category.destroy
redirect_to categories_url, notice: 'Category was successfully destroyed.'
end
private
# Use callbacks to share common setup or constraints between actions.
def set_category
#category = Category.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def category_params
params.require(:category).permit(:name)
end
end
application_policy.rb
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def index?
false
end
def create?
create?
end
def new?
create?
end
def update?
false
end
def edit?
update?
end
def destroy?
false
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
scope.all
end
end
end
Ive got Pundit included in my ApplicationController and rescue_from Pundit::NotAuthorizedError, with: :forbidden this set up there as well.
The authorization itself works, if I'm logged in with an admin account I have access to /categories/*. If I'm logged out I get the following: NoMethodError at /categories
undefined methodadmin?' for nil:NilClass`
While writing the question I think I found the problem- I guess Pundit looks for a User that is nil because I'm not logged in. What would the correct approach of solving this issue look like?
Best regards
The most common approach is to redirect users from pages that are not accessible by not logged in users. Just add a before action in your controller:
class CategoriesController < ApplicationController
before_action :redirect_if_not_logged_in
<...>
private
def redirect_if_not_logged_in
redirect_to :home unless current_user
end
end
(I assume here that you have current_user method which returns user instance or nil. Please change :home to wherever you want to redirect users)
There are multiple ways of achieving what you want.
The most obvious (but kind of dirty) and straightforward-looking way of doing that would be to add a check for user presence in every condition:
user && user.admin?
It won't fail with nil error as the second part of the condition won't get executed. But it doesn't look very nice, right? Especially if you have to copy this over to all methods you have in CategoryPolicy.
What you can do instead, is to make Pundit "think" that you passed a User, by creating a GuestUser class which responds with false to admin? method (https://en.wikipedia.org/wiki/Null_object_pattern):
In object-oriented computer programming, a null object is an object with no referenced value or with defined neutral ("null") behavior. The null object design pattern describes the uses of such objects and their behavior (or lack thereof)
And use it when user is nil. In practice, it will look like this:
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user || GuestUser.new
#record = record
end
# ...
end
class GuestUser
def admin?
false
end
end
This way you won't have to alter any of your other code, as the model you passed responds to the method which is expected by policy (admin?). You may want to define this GuestUser somewhere else (not in the policy file), depending if you want other parts of the app to reuse that behavior.
You can also proceed with the redirect approach from P. Boro answer. It's less flexible in some sense but can totally work fine if you don't need anything besides redirecting all non-logged in users.
So I've looked all through Stack Overflow trying to find a reason why I would be hitting a parsing error. This is a relatively new Rails app, but I don't think I've missed any configuration as I've been able to deploy on Heroku successfully.
From previous Stackoverflow posts it would seem that I have an extra end since Rails is complaining that it is finding an end when it is expecting the end of the file. I tried taking out the last end (which is the closer for the controller class itself) as well as commenting various parts of the code in and out and it just simply isn't working. Does anyone have any ideas/advice on how to circumvent/solve this issue?
Here is my controller file
Class WorkoutsController < ApplicationController
def show
id = params[:id] # retrieve workout ID from URI route
#workout = Workout.find(id) # look up workout by unique ID
# will render app/views/workouts/show.<extension> by default
end
def index
#all_workouts = Workout.all_workouts
#workouts = Workout.all
end
def new
# default: render 'new' template
end
def create
#workout = Workout.create!(params[:workout])
flash[:notice] = "#{#workout.title} was successfully created."
redirect_to workouts_path
end
def edit
#workout = Workout.find params[:id]
end
def update
#workout = Workout.find params[:id]
#workout.update_attributes!(params[:workout])
flash[:notice] = "#{#workout.title} was successfully updated."
redirect_to workouts_path(#workout)
end
def destroy
#workout = Workout.find(params[:id])
#workout.destroy
flash[:notice] = "Workout '#{#workout.title}' destroyed."
redirect_to workouts_path
end
end
This probably isn't needed, but here is my model file:
Class Workout < ActiveRecord::Base
attr_accessible :title, :time, :creator, :exercise_list
def self.all_exercises
%w(push-ups pull-ups crunches rest)
end
end
Replace
Class WorkoutsController < ApplicationController
with
class WorkoutsController < ApplicationController
Same for your model. class keyword should be used in lower register.
I assume you misprinted it, thats why parser sees extra end keyword
Experimenting with ruby on rails.. I put a new Post form on a users show page.(i.e. 0.0.0.0:3000/users/2) I'm trying to extract the user's id and insert it into a 'user_id' field in the Post table when you create a new post. So when the form is submitted from the user's page, I can link it to the user that wrote it.
models/post.rb
class Post < ActiveRecord::Base
belongs_to :user
before_save :create_user_id
def create_user_id
self.user_id = current_user
end
end
models/user.rb
class User < ActiveRecord::Base
has_many :posts
end
helpers/application_helper.rb
module ApplicationHelper
def current_user
#current_user ||= User.find(params[:id])
end
end
controllers/post_controller.rb
class PostsController < ApplicationController
def new
#post = Post.new
end
def show
#post = Post.find(params[:id])
#page_title = #post.title.capitalize
#author = User.find(#post.user_id)
#author_url = "/users/" + #post.user_id.to_s
end
def create
#post = Post.create(post_params)
if #post.save
redirect_to #post
else
render 'new'
end
end
# private
private
def post_params
params.require(:post).permit(:title, :body, :user_id)
end
end
The error I get:
Couldn't find User without an ID
Extracted source (around line #15):
#post = Post.find(params[:id])
#page_title = #post.title.capitalize
>>#author = User.find(#post.user_id)
#author_url = "/users/" + #post.user_id.to_s
end
If I test and change my application_helper.rb to this it works, and inserts 2 into the Post's user_id field. The current set up just returns nil
module ApplicationHelper
def current_user
#current_user = 2
end
end
First you want to get the current user, for now you can test using something like this:
#current_user ||= User.find(2)
Note that there will not be an :id param available on a create call, :id refers to a specific member of your resource so in this case if get http://localhost:3000/posts/1 posts would be the resource and 1 would be the param :id so this would not return the current_user you expected.
Then association should do all of the work for you and there is no need for the create_user_id method. All you would have to do is tweak your create method to
#post = current_user.posts.create(post_params)
how to make this code clean in rails?
profiles_controller.rb :
class ProfilesController < ApplicationController
before_action :find_profile, only: [:edit, :update]
def index
#profiles = Profile.all
end
def new
#profile = Profile.new
end
def create
profile, message = Profile.create_object(params["profile"], current_user)
flash[:notice] = message
redirect_to profile_url
end
def edit
end
def update
profile, message = #profile.update_object(params["profile"])
flash[:notice] = message
redirect_to profile_url
end
private
def find_profile
#profile = Profile.friendly.find(params["id"])
end
end
i look flash[:notice] and redirct_to profile_url is duplicate in my code, how to make the code to clean and dry?
How about moving the repetitive code to a separate method and call that method inside the actions.
def flash_redirect # you can come up with a better name
flash[:notice] = message
redirect_to profile_url
end
then in update action:
def update
profile, message = #profile.update_object(params["profile"])
flash_redirect
end
do the same thing for create action
UPDATE:
in case you are wondering about usingafter_action, you can't use it to redirect as the call-back is appended after the action runs out its course. see this answer
Take a look at Inherited Resources. It's based on the fact that many CRUD controllers in Rails have the exact same general structure. It does most of the work for you and is fully customisable in case things are done a little different in your controllers.
Using this gem, your code would look like this:
class ProfilesController < InheritedResources::Base
def create
redirect_to_profile(*Profile.create_object(params[:profile], current_user))
end
def update
redirect_to_profile(*#profile.update_object(params[:profile]))
end
private
def redirect_to_profile(profile, message)
redirect_to(profile_url, notice: message)
end
def resource
#profile ||= Profile.friendly.find(params[:id])
end
end
The create and update methods return multiple values, so I used the splat operator to DRY this up.
create_object and update_object don't follow the Rails default, so we need to implement those actions for Inherited Resources instead. Currently they don't seem to be handling validation errors. If you can, refactor them to use ActiveRecord's save and update, it would make everything even easier and DRYer.
First time poster, long time lurker here. I have a Users model and controller for a little video game application for Rails that I'm currently making. So I've read a couple of answers on here regarding this issue, but none of the answers really seem to have helped me. People have suggested adding a "user_id" column to my Users table, but my point of contention is, I thought the "user_id" was automatically made in Rails? Even if I use a user.inspect, I still see a user_id=7show up on the page. However, I still get the unknown attribute error when attempting to create a game and assign to the current user. Any help would be most appreciated in pinpointing the cause and solution to this. Thanks!
app/controllers/users_controller.rb:
class UsersController < ApplicationController
skip_before_filter :require_authentication, only: [:new, :create]
def index
#users = User.all
end
def show
end
def new
#user = User.new
end
def edit
#user = current_user
end
def create
#user = User.create!(user_params)
session[:user_id] = #user.id
redirect_to users_path, notice: "Hi #{#user.username}! Welcome to DuckGoose!"
end
def update
current_user.update_attributes!(user_params)
redirect_to users_path, notice: "Successfully updated profile."
end
def destroy
#user = User.find(params[:id])
#user.destroy
redirect_to users_url, notice: 'User was successfully destroyed.'
end
private
def user_params
params.require(:user).permit(:username, :firstname, :lastname, :email, :password, :password_confirmation)
end
end
app/config/routes.rb:
NkuProject::Application.routes.draw do
resources :users do
resources :games
end
resources :sessions
resources :games
get "sign_out", to: "sessions#destroy"
get "profile", to: "users#edit"
root to: "sessions#new"
end
app/controllers/games_controller.rb
class GamesController < ApplicationController
def new
#game = Game.new
end
def index
#games = Game.all
end
def destroy
#game = Game.find(params[:id])
#game.destroy
redirect_to games_url, notice: 'Game was successfully deleted.'
end
def create
#game = current_user.games.build(game_params)
if #game.save
redirect_to #game, notice: "Game successfully added"
else
render :new
end
end
def show
#game = Game.find(params[:id])
end
private
def game_params
params.require(:game).permit!
end
end
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_filter :require_authentication
def current_user
#current_user ||= User.find_by(id: session[:user_id]) if session[:user_id].present?
end
helper_method :current_user
def require_authentication
if current_user
true
else
redirect_to new_session_path
end
end
end
I'm sure I'm missing some code to put in for reference, but if I need anything else please let me know.
Looking at the way your controller actions are defined, I can safely say that User and Game have a 1-M relationship, i.e.,
class User < ActiveRecord::Base
has_many :games
end
class Game < ActiveRecord::Base
belongs_to :user
end
Now, based on that games table must have a field named user_id. Rails is not going to create it for you unless you specify it. You need to add field user_id in games table by creating a migration for the same. Right now, it doesn't seem like you have user_id foreign_key field in games table. Hence, the error while saving games record.