rails3 Pundit policy base on join table value - ruby-on-rails-3

User has_many constructusers, the latter being a join table for a has_many :through relationship to Construct. For the application purposes, the boolean roles are defined in the join table (constructusers.manager, constructusers.operator, etc.), while admin is a user attribute.
So when it comes time to define the policies on the actions the following throws a no method error for 'manager', while a relationship is recognised ActiveRecord::Relation:0x000001035c4370
def show?
user.admin? or user.constructusers.manager?
end
if the relationship (I assume the proper one) is correct, why is there no recognition of the boolean attribute?
As per comment below, for the simple reason that is plural. Thus filtering requires:
Constructuser.where(['construct_id = ? and user_id = ?', params[:id], current_user]).first
...which is running in the controller and impacts the view. Nonetheless, for proper Pundit handling, this needs to be factored out... still de application_controller in a before filter to set that attribute. However a before_filter :set_constructuser_manager with that find condition, with nil case handling, still has no impact when stating the policy
def show?
set_constructuser_manager?
end
Update: as per comment below. Pundit class private method
def contractorconstruct
#contructs = Construct.where(['constructusers.user_id = ?', user]).joins(:users).all
#contractorconstruct ||= Contractor.where(['construct_id IN (?)', #contructs]).first
end
and action rule
|| contractorconstruct?
returns no method error.

manager? will be a method on an instance of Constructuser, not on the relation. Think about what you are asking, "Is this constructusers a manager?" - it makes no sense. How would the computer know what constructuser you are talking about?
If a user has_many constructusers, in order to use manager? you need to find the instance you are concerned about. If this is in the ConstructPolicy, then you need to find the specific constructuser that links user to the construct that you are authorizing, then check if that single constructuser is manager?.
If you are in the Construct controller, you'll have something like
class ConstructsController
before_action :set_construct
def show
authorize #construct
# ...
end
# ...
end
In your policy then, user will be the current user and record will be #construct.
class ConstructPolicy
def show?
user.admin? || constructuser.manage?
end
private
def constructuser
#constructuser ||= Constructuser.find_by(user_id: user, construct_id: record)
end
end

Related

New to Rails 4 Testing - Need help getting started (rSpec and Devise)

I'm relatively new to testing and very new to Rails 4 and rSpec. I am trying to test a controller that uses Devise for authentication and I am stuck. All of the examples I can find are for Rails 3.
I'm using Rails 4.0.3, Devise 3.2.3, rSpec 2.14.1 and FactoryGirl 4.4.0.
class LessonPlansController < ApplicationController
before_action :authenticate_user!
# GET /lesson_plans
def index
#lesson_plans = current_user.lesson_plans.to_a
end
.
.
.
private
# Use callbacks to share common setup or constraints between actions.
def set_lesson_plan
#lesson_plan = LessonPlan.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def lesson_plan_params
params[:lesson_plan]
end
def lesson_plan_params
params.require(:lesson_plan).permit(:title, :synopsis)
end
end
Here are my factory definitions: (Maybe I don't need to define user_id in the lesson_plan factory?)
FactoryGirl.define do
factory :user do
sequence( :username ) { |n| "user#{n}" }
sequence( :email ) { |n| "foo#{n}#example.com" }
password 'foobarbaz'
password_confirmation 'foobarbaz'
created_at Time.now
updated_at Time.now
end
end
FactoryGirl.define do
factory :lesson_plan do
user_id 1
title "The French Revolution"
synopsis "Background and events leading up to the French Revolution"
end
end
And the test part is where I get stuck.
describe LessonPlansController do
let(:valid_attributes) { { } }
let(:valid_session) { {} }
# describe "GET index" do
it "assigns all lesson_plans as #lesson_plans" do
user=FactoryGirl.create(:user)
sign_in user
lesson_plan = LessonPlan.create! valid_attributes
get :index, {}, valid_session
assigns(:lesson_plans).should eq([lesson_plan])
end
end
I'm not sure what to put in valid_attributes and valid_session (or if I even need them). The test will get as far as signing in the user, but will fail on creation of the lesson_plan. Admittedly this is the default/generated test for rSpec, but I am not sure how to proceed.
Examples I have seen use a before block to set up the user. I haven't been able to find anything on the Devise wiki page covering how to write basic rSpec tests for a controller that requires the user to be logged in. Any pointers would be greatly appreciated!
"I'm not sure what to put in valid_attributes and valid_session (or if I even need them)."
Well that depends what you're testing for.. Say you're testing validations & want to ensure that a record not be created if x column is set to null... then you could try to specifically create a record with invalid attributes (e.g. column: nil) and expect the result to not return true; maybe you want to ensure that it IS created with valid attributes.
You can btw, use `attributes_for(:factory_name)`` since you're using FactoryGirl. And no you don't necessarily need to specify the user's id in your lesson plan factory; unless you always want it to reference user 1. You can simply reference user with no value. Check out http://everydayrails.com/2012/03/12/testing-series-intro.html and especially parts 3-5 for an introduction to testing with RSPec.. I found this a pretty easy to follow guide when I was getting started.

Rails 3: how to write DRYer scopes

I'm finding myself writing very similar code in two places, once to define a (virtual) boolean attribute on a model, and once to define a scope to find records that match that condition. In essence,
scope :something, where(some_complex_conditions)
def something?
some_complex_conditions
end
A simple example: I'm modelling a club membership; a Member pays a Fee, which is valid only in a certain year.
class Member < ActiveRecord::Base
has_many :payments
has_many :fees, :through => :payments
scope :current, joins(:fees).merge(Fee.current)
def current?
fees.current.exists?
end
end
class Fee < ActiveRecord::Base
has_many :payments
has_many :members, :through => :payments
scope :current, where(:year => Time.now.year)
def current?
year == Time.now.year
end
end
Is there a DRYer way to write a scopes that make use of virtual attributes (or, alternatively, to determine whether a model is matched by the conditions of a scope)?
I'm pretty new to Rails so please do point out if I'm doing something stupid!
This in not an answer to the question, but your code has a bug (in case you use something similar in production): Time.now.year will return the year the server was started. You want to run this scope in a lambda to have it behave as expected.
scope :current, lambda { where(:year => Time.now.year) }
No, there's no better way to do what you're trying to do (other than to take note of Geraud's comment). In your scope you're defining a class-level filter which will generate SQL to be used in restricting the results your finders return, in the attribute you're defining an instance-level test to be run on a specific instance of this class.
Yes, the code is similar, but it's performing different functions in different contexts.
Yes, you can use one or more parameters with a lambda in your scopes. Suppose that you have a set of items, and you want to get back those that are either 'Boot' or 'Helmet' :
scope :item_type, lambda { |item_type|
where("game_items.item_type = ?", item_type )
}
You can now do game_item.item_type('Boot') to get only the boots or game_item.item_type('Helmet') to get only the helmets. The same applies in your case. You can just have a parameter in your scope, in order to check one or more conditions on the same scope, in a DRYer way.

rspec testing association

I want to test that a staff member is associated with a company in my rspec controller tests.
I would like to end up with this in my create action of the staff controller:
staff.companies << current_company
Where current_company is collected from a session variable.
How do I write a test for this?
I've got these models
class Company < ActiveRecord::Base
has_many :employees
has_many :staff, :through => :employees
end
class Employee < ActiveRecord::Base
belongs_to :company
belongs_to :staff
end
class Staff < ActiveRecord::Base
has_many :employees
has_many :companies, :through => :employees
end
The following test is my attempt to spec the assocation and it fails when I enter in the association code:
it "should belong to the current_company" do
staff.should_receive(:companies)
post :create
end
If I enter the 'staff.companies << current_company' code in my controller I get this error when running that test:
Failure/Error: post :create
NoMethodError:
You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.<<
Staff controller create method:
def create
#staff = Staff.new(params[:staff])
if #staff.save
#staff.companies << current_company
redirect_to staff_index_path, :notice => "Staff created successfully!"
else
#company = #staff.firm || current_company
flash[:alert] = "Staff failed to create"
render "new"
end
end
I would use a different approach, since testing that the model should receive a certain message couples your tests too tightly to the implementation. Do you really care whether companies receives #<< or some other method?
Really, what you want to test is whether the user's company is recorded when they post to the page. It doesn't matter how it was recorded. So I'd do something like this:
it "should add the company to the user's list of companies" do
lambda do
post :create
end.should change(staff.companies, :count).from(0).to(1)
staff.companies.map(&:name).should include("Acme, Inc.")
end
This is testing behavior instead of implementation. The advantage is that your test wont fail when someone changes that << to the equivalent push. It also has the advantage of being clearer about your intention and therefore better documenting the code.
If you're in your controller spec, I would use stub_chain
staff.stub_chain(:company, :<<).once.and_return(true)
which will mock out the company call AND the << call AND expect it to be called once.
(At least, that .once should work with stub_chain...)
You can test it with :
staff.should have(1).company
Or if the staff already has other companies, get the count and test for have(count+1).companies.
The problem with the code is that once you stub out a method - it no longer exists on the model anymore.
You have stubbed out the "companies" method (when you set the expectation on it) and it now, no-longer calls the actual, real companies association on the model but the stub that you have created... which returns nil (because you didn't set a returns value on it).
Then, when you try to put a company into this new, null method using << it says it can't do that.
To get around it you can do what you did which is to set a returns value:
staff.should_receive(:companies).and_return([])
which will then make sure that:
#staff.companies << current_company
will not fail with the horrible nil error (because there's and actual, real array for the company to go into).
But really the best thing to do is as the previous people have suggested and test what you actually really need to test - which is that saving a staff with companies will cause a new company to get saved to the db.

How to access devise current_user from model

Just start to develop with devise for my app authentication, however running into trouble with accessing current_user from a model. I have no problem accessing this from a controller.
For my scenario I need to get the current user id to keep track who has edit the blog post, which I save the user id with before_save filter.
Appreciate your help!
Thanks
You can only pass it down in a parameter.
class SomeModel
def my_nifty_method(user)
end
end
in controller:
class SomeModelsController < ApplicationController
def some_method
sm = SomeModel.new
sm.my_nifty_method(current_user)
end
end
You should be able to do this in your model. 'c' here being the model object and setting it's user_id attribute before create, or save in your case.
before_create {|c| c.user_id = Authorization.current_user.id}
I'm somewhat new to RoR so be warned.
Why are you trying to access to that method in the model?
You can access to the ID attributes via self.id (within the model)

Where to put business logic that requires the current_user to be known? (Rails)

I have a model (say Car) in which a method needs access to the current_user to determine if the user is allowed to perform the things that the method does.
For example, a method might want to do these things:
Check that current_user owns this object
Check that the object status == 1 (Active)
Check that a related object exists and it's X field is not NULL
I need this business logic to be in the model, not in the controller, so that it's the one place where my business logic will be. The method might get called from places other than a controller.
I know that there are gems like cancan, declarative_authorization etc. but they seem to be overkill for what I need to do. And also, accessing current_user in a model is not considered the "right way".
Then, how do I make that check in the model but still feel "clean"?
I have experienced a situation where "current_user" needs be tightly connected to a model, but I handled it all in the Controller and it works pretty well. Here are some examples:
My model is "Photos". Photos are owned by users, and how people interact with photos is obviously tightly related to whether or not they own the photo.
In the show action I need to load either the existing rating a user has given to a photo (so they can edit it) or allow them to create a new one:
def show
#photo = Photo.find(params[:id])
if user_signed_in?
if #rating = current_user.ratings.find_by_photo_id(params[:id])
#rating
#current_user_rating = #rating.value
else
#rating = current_user.ratings.new
#current_user_rating = "n/a"
end
end
end
When people create photos I want them to be automatically assigned to the current user.
def new
#photo = Photo.new
end
def create
#photo = current_user.photos.create(params[:photo])
if #photo.save
redirect_to user_path(current_user), :notice => t('photo.notice.created')
else
render 'new'
end
end
Only the owners of a photo can change them:
def edit
#photo = Photo.find(params[:id])
if #photo.user == current_user
render 'edit'
else
redirect_to user_path(current_user), :alert => t('application.error.unauthorized')
end
end
def update
#photo = current_user.photos.find_by_id(params[:id])
#photo.update_attributes(params[:photo])
if #photo.save
redirect_to user_path(current_user), :notice => t('photo.notice.updated')
else
render 'edit'
end
end
This approach is based on the constraints that a "current_user" object is tied to the session, which only the controller knows about. So, in short, I have yet to find a good way to integrate "current_user" into a model, but I've been able to find (I think) pretty clean ways to tie the model and controller together so that this can be provided by the controller.
One fairly simple solution to most problems, if your controller is starting to get messy, would be to take a chunk of logic and define as a method in the model, but require one argument = a user object. Then you can just feed "current_user" to that method from your controller and the model handles the rest.
Good luck! Also, if anyone else has any better ideas for this, I'd love to hear them!
Handle auth'ing in a Controller.
Example: Putting auth logic in parent ApplicationController.
class ApplicationController < ActionController::Base
protect_from_forgery
protected
# Returns the currently logged in user or nil if there isn't one
def current_user
return unless session[:user_id]
#current_user ||= User.find_by_id(session[:user_id])
end
# Make current_user available in templates as a helper
helper_method :current_user
# Filter method to enforce a login requirement
# Apply as a before_filter on any controller you want to protect
def authenticate
logged_in? ? true : access_denied
end
# Predicate method to test for a logged in user
def logged_in?
current_user.is_a? User
end
# Make logged_in? available in templates as a helper
helper_method :logged_in?
def access_denied
redirect_to login_path, :notice => "Please log in to continue" and return false
end
end
Now that current_user is an accessor to the logged in user and you can access it in any controller, you can do your authorization logic in the appropriate controller before you do anything with the model.
Your right, though. Models don't care about authorization or who is accessing them.