Rails 3.0.3
ruby 1.9.2p0
The Problem:
I have a Users table which has many items, the item(s) in turn therefore belongs to the Users.
In my model item.rb i attempt to save the item along with the value for the user.id so i have:
self.User_ID = #user.id
this however give me the error
Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id
this is causing some confusion that it can't find this as in the show.html.erb that 'wraps' this page <%= #user.id %> displays the correct ID on the page
Many thanks in advance
** EDIT **
The Shorten action is the action upon which i want to parameter to be passed
class ItemsController < ApplicationController
def redirect
#item = Item.find_by_shortened(params[:shortened])
if #item
#redirect_to #item.original
redirect_to #item.original
else
redirect_to :shorten
end
end
def shorten
#host = request.host_with_port
#user = current_user
You need to load the #user model in every action that will require access to it. Having it render properly in the show action will not guarantee it is loaded in the update action.
Usually you need to have something like this in your controller:
class UsersController < ApplicationController
before_filter :load_user, :except => [ :index, :new, :create ]
# ...
protected
def load_user
#user = User.find(params[:user_id] || params[:id])
rescue ActiveRecord::RecordNotFound
render(:text => 'Record not found')
end
end
Related
I'm working on my first rails api server.
I've got a controller for my User model that looks as such:
class UsersController < ApplicationController
def index
if current_user.admin?
#users = User.all
render json: #users
else
render json: { message: 'You do not have the appropriate permissions to access this resource' }, status: 401
end
end
def show
if User.exists?(#id)
#id = params[:id]
if current_user.id.to_s == #id || current_user.admin?
#user = User.find(#id)
render json: #user
else
render json: { message: 'You do not have the appropriate permissions to access this resource' }, status: 401
end
else
render json: { message: 'Requested resource not found' }, status: 404
end
end
end
What I want and currently have for these two controller methods is:
/users fetch all users only if the authenticated user making the request is of role admin
/users/:id fetch a user by id only if the authenticated user making the request has a matching id or is of role admin
The current implementation breaks the DRY philosophy. The reasoning is that the logic for handling whether or not the requesting user has the permissions to access the requested resource(s) is repeated across both controller methods. Furthermore, any model's controller method for show will repeat the logic for checking whether or not the requested resource exists. I also feel like this kind of implementation makes for fat controllers, where I'd rather them be skinny.
What I want to know from the community and from those that have solved this problem before; what is the best way to go about this in order to conform to the DRY philosophy and to keep controllers skinny.
Good to know: I'm using devise and devise-token-auth for authentication.
You need to use some kind of Authorization gem like cancancan. It is exactly what you need. Also it's else not elsif. elsif is followed by condition.
You can use github.com/varvet/pundit instead, for authorization.
It matches with the controller, instead of putting the authorization in the controller, you can use this to move out the authorization to another class.
I have used this across multiple Rails/Rails-API projects and didn't encounter a problem so far.
Instead of writing the code above. You can do this instead.
Also, prioritize early returns over nested ifs for readability.
In your controller.
class UsersController < ApplicationController
def index
authorize User # This will call the policy that matches this controller since this is UsersController it will call `UserPolicy`
#users = User.all
render :json => #users
end
def show
#user = User.find_by :id => params[:id] # Instead of using exists which query the data from db then finding it again, you can use find_by which will return nil if no records found.
if #user.blank?
return render :json => {:message => 'User not found.'}, :status => 404
end
authorize #user # This will call the policy that matches this controller since this is UsersController it will call `UserPolicy`
render :json => #user
end
end
In your Policy
class UserPolicy < ApplicationPolicy
def index?
#user.admin? # The policy is called in controller then this will check if the user is admin if not it will raise Pundit::NotAuthorizedError
end
def show?
#user.admin? || #record == #user # The policy is called in controller then this will check if the user is admin or the user is the same as the record he is accessing if not it will raise Pundit::NotAuthorizedError
end
end
In your ApplicationController
class ApplicationController < ActionController::API
include Pundit
rescue_from Pundit::NotAuthorizedError, :with => :show_forbidden
private
def show_forbidden exception
return render :json => {
:message => 'You are not authorized to perform this action.'
}, :status => 403
end
end
I'm wondering how can I print on the index of my project only the rooms with the :is_available column or the rooms table with the :true value (is boolean).
I can't figure out how to achieve this (Sorry but I'm new with Rails). Any advice will be very appreciate!
I've this error with my current code:
"ActiveRecord::RecordNotFound in RoomsController#home
Couldn't find Room without an ID"
Here is my rooms_controller code:
class RoomsController < ApplicationController
before_action :get_room, only: [:index, :home]
def index
end
def show
#room = Room.find(params[:id])
end
def home
if params[:set_locale]
redirect_to root_url(locale: params[:set_locale])
else
puts #rooms if Room.all(params[:is_available => :true])
end
end
def get_room
#rooms = Room.all
end
end
You already have got #rooms = Room.all, you just need to precise your query (from all to your is_available restriction).
def home
if params[:set_locale]
redirect_to root_url(locale: params[:set_locale])
else
puts #rooms.where(is_available: true)
end
end
Also, you should avoid using puts in your controller logic. Either pass variable to the view (you can change #rooms value or create new variable #available_rooms), respond_with it or log it using Rails.logger if you use puts as a debugging solution.
def index
end
def home
if params[:set_locale]
redirect_to root_url(locale: params[:set_locale])
elsif params[:is_available]
puts #rooms
end
end
def get_room
#rooms = Room.where(is_available: true)
end
Using puts in controller - not a good idea.Use view to show the data.
There are several issues you may have:
Routes
Your index method looks empty. I presume you're using "home" as a substitute
In this case, you have to know what type of action this is - a member or collection action? The reason this is important is that when you define your routes, you have to ensure you define the route in the right way. For your home route, I'd have done this:
#config/routes.rb
resources :rooms do
get "home", action: "home"
end
Scopes
You can use a scope to bring back all the values with :is_available present. This lives in the model like this:
#app/models/room.rb
Class Room < ActiveRecord::Base
scope :is_available?, -> { where(is_available: true) }
end
This will allow you to call
#room = Room.is_available?
Code
Although you've not given us any context of the error (when it happens, what you do to make it happen), this is what I would do to help fix it:
#app/controllers/rooms_controller.rb
def home
if params[:set_locale]
redirect_to root_url(locale: params[:set_locale])
else
puts Room.is_available?
end
end
This may change depending the params you send & how you send them
def home
if params[:set_locale]
redirect_to root_url(locale: params[:set_locale])
else
puts #rooms if params[:is_available] && Room.where(is_available: true)
end
end
should work.
I am trying to use a _before_filter_ in my ApplicationController to fetch a user object matching a user_id in a http parameter like:
before_filter :fetch_user
def fetch_user
if params[:user_id].present?
#user = User.find(params[:user_id])
end
rescue ActiveRecord::RecordNotFound
# user not found
end
This is working for me in all controllers which inherit from ApplicationController except the controller which is called UsersController in which #user seems to be set to nil in some way.
What could be the reason for this behavior? And is this the standard behavior? How to avoid/disable it?
UPDATE:
I always pass a parameter called user_id to the controller.
If I include the exact same before filter directly into the UsersController it is working!
there are no other before filters in the UsersController
UPDATE 2:
Route which is use to users controller
match ':user_id' => 'users#show'
User Controller code:
def show
if #user
render :text => "user not nil"
else
render :text => "user nil"
end
end
There is definitely a user with the id passed at the user_id parameter because
Thanks for your help!
point.1 in UsersController, you get params[:id] as user_id
point.2 use find_by_id to avoid rescue ActiveRecord::RecordNotFound, it safely returns nil
and so...
def fetch_user
user_id = controller_name=='users' ? params[:id] : params[:user_id]
#user ||= User.find_by_id user_id
end
I have a comment model. I am creating a new instance of that model by passing it params from my view to the comment controller. Here is the comment controller:
class CommentsController < ApplicationController
def create
session[:return_to] = request.referrer
#comment = Comment.create(:user_id => current_user.id,
:issue_id => params[:issue_id],
:content => params[:content])
redirect_to session[:return_to]
end
end
Here is how I am passing the params in my view:
<%= link_to "Test Comment", comments_path(:issue_id => #issue.id,
:content => "HeLLO"),
method: :create %>
my question is - is this secure? What prevents someone from changing the params[:issue_id] and commenting on another issue? Is there a better way of doing this?
yeah, there are better ways
at first we look to your controller. to store the referrer and redirect back to it makes no sense (at least you should NOT save this in a session) rails can do this with the key :back.
at second you dont need to make a varaible with the # because you dont use the created object. and also you dont need to save the restult. just do
class CommentsController < ApplicationController
def create
Comment.create(:user=>current_user, :issue_id=>params[:issue_id],:content=> params[:content])
redirect_to :back
end
end
++ edit
actually a better way would to to it like this:
class CommentsController < ApplicationController
def create
current_user.comments.create(issue_id: params[:issue_id], content: params[:content])
redirect_to :back
end
end
just use rails associations
-- edit
and as you think, YES we can change the issue_id and write comments to any issue i want. so if you want to protect from this you have do do a helper before you crate a comment (its just an example)
class CommentsController < ApplicationController
def create
issue = Issue.find(params[:issue_id]
if issue.is_locked? || current_user.cant_write_at_issue(issue)
return redirect_to :back, :notice=>"You dont have Privilegs"
end
issue.comments.create :user=>current_user, :content=>params[:content])
redirect_to :back :notice=>"Comment was created successfully"
end
end
is_locked and cant_write_at_issue you need to define in your models. this is just a way how to protect something.
so now we can change the issue ID but you look if the user has access for doing this :-)
I'm learning Rails by building a shop application and I'm having a bit of trouble with redirects. I have 3 roles in the application:
Buyer
Seller
Administrator
Depending on which type they are logged in as then I would like to redirect to a different page/action but still show the same URL for each (http://.../my-account).
I don't like having to render partials in the same view, it just seems messy, is there another way to achieve this?
The only way I can think of is to have multiple actions (e.g. buyer, seller, administrator) in the accounts controller but that means the paths will look like http://.../my-account/buyer or http://.../my-account/seller etc.
Many thanks,
Roger
I've put my code below:
models/user.rb
class User < ActiveRecord::Base
def buyer?
return type == 'buyer'
end
def seller?
return type == 'seller'
end
def administrator?
return type == 'administrator'
end
...
end
controllers/accounts_controller.rb
class AccountsController < ApplicationController
def show
end
end
controllers/user_sessions_controller.rb
class UserSessionsController < ApplicationController
def new
#user_session = UserSession.new
end
def create
#user_session = UserSession.new(params[:user_session])
if #user_session.save
if session[:return_to].nil?
# I'm not sure how to handle this part if I want the URL to be the same for each.
redirect_to(account_path)
else
redirect_to(session[:return_to])
end
else
#user_session.errors.clear # Give as little feedback as possible to improve security.
flash[:notice] = 'We didn\'t recognise the email address or password you entered, please try again.'
render(:action => :new)
end
end
def destroy
current_user_session.destroy
current_basket.destroy
redirect_to(root_url, :notice => 'Sign out successful!')
end
end
config/routes.rb
match 'my-account' => 'accounts#show'
Many thanks,
Roger
In UserSessionsController#create (i.e.: the login method) you could continue to redirect to the account path (assuming that goes to AccountsController#show) and then render different views according to the role. I.e.: something like this:
class AccountsController < ApplicationController
def show
if current_user.buyer?
render 'accounts/buyer'
elsif current_user.seller?
render 'accounts/seller'
elsif current_user.administrator?
render 'accounts/administrator
end
end
end
Better yet, you could do this by convention...
class AccountsController < ApplicationController
def show
render "accounts/#{current_user.type}"
end
end
If I understand you question correctly, then the solution is simple.
You can just call the method you want inside your controller. I do this in my project:
def create
create_or_update
end
def update
create_or_update
end
def create_or_update
...
end
In your case it should be:
def action
if administrator? then
admin_action
elsif buyer? then
buyer_action
elseif seller? then
seller_action
else
some_error_action
end
end
You should probably explicitly call "render" with an action name in each of those actions, though.