I am trying to create a scope on a model of mine limiting the available results to only those that are owned by the user's partner. When the user is an administrator, however, I want all models to be available.
This works, but looks stupid. What is the proper rails3 way of expressing this?
scope :accessible_by, proc { |user|
if user.admin?
where("1=1")
else
where(:owner_id => user.partner.id)
end
}
What I want to be able to do is select further and do e.g
#models = MyModel.
accessible_by(current_user).
other_scope.
where(:property => value).
order("another_property desc").
all
You might be able to use the all modifier.
scope :accessible_by, proc { |user|
if user.admin? == false
where(:owner_id => user.partner.id)
end
}
Related
I am learning Ruby on Rails and was looking into utilizing cancan to help restrict users access to actions that they shouldn't have and to pages depending on who they are. I currently understand how to restrict actions, but I was curious if someone could help with actually restricting certain pages and unique pages.
One example is I have a home page for admin users and one for regular users, how would I restrict the admin page from the normal user?
Thanks, and any pointers on if I am doing something wrong is greatly appreciated.
If you want to use cancan :
Admit you add in your user controller a method admin_home :
def admin_home
#user = current_user
authorize! :admin_home
end
You need to specify in ability.rb file you want to restrict access to admin_home for standard users :
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.admin?
#Authorize all actions
can :manage, User
else
#authorize only self modifications and restrict access to admin_home
can :manage, User, :id => user.id
cannot :admin_home, User
end
end
end
You can find great resources about cancan in official wiki like
https://github.com/ryanb/cancan/wiki/Defining-Abilities and
https://github.com/ryanb/cancan/wiki/Authorizing-controller-actions
Hope this help
Note: I am just giving you an example, you are not supposed to use it as it is, but you can have an Idea that how you will be able to put your logic.
class AdminsController < ApplicationController
before_filter :check_admin, :only => [:index, :show]
def index
#admins = //whatever your query for this action
end
def show
#admin = //whatever your query for this action
end
protected
def check_admin
if(my_condition to check if user type is admin)
{
return true // or anything u want for ur admin user
}
else
{
//anything here when user is not admin
1. you can redirect to users home page using redirect_to
2. you can redirect to a specific page which shows "You are not authorized to see this web page"
}
end
end
end
I have a User model. One of its attributes is a string called :access which can be either nil, "admin", or "active".
Now inside the User model I have the following methods:
def admin?
self.access == "admin"
end
def active?
self.access == "active"
end
They work fine. But if I add attr_accessor :access to the model something breaks. My admin? and active? methods no longer work. When I go into rails console and get a User out of the database I can see that user = User.find(7) shows access is set to "admin". But if I type user.access it returns nil. user.admin? returns false.
attr_accessor is overwriting the default methods of reading and writing an attribute from db. Why do you need attr_accessor if it is already a column in db and Rails provides you with the read and write methods. Are you confusing it with attr_accessible?
I'm trying to test a controller to ensure that only an authorized party can view the correct child object using RSpec. I cant figure out what I'm doing wrong as I'm getting this error:
ActiveRecord::RecordInvalid: Validation failed: Company can't be blank
I have a Plan object and a Company object. The Store can have many plans (think of a pest control Company). I want to test that given a known scenario I can retrieve the plan fo the Company (assuming there is only one).
The Plan looks like this:
class Plan < ActiveRecord::Base
before_save :default_values
# Validation
validates :amount, :presence => true
validates :company, :presence => true
# Plans belong to a particular company.
belongs_to :company, :autosave => true
scope :find_all_plans_for_company, lambda {
|company| where(:company_id => company.id)
}
# Other code ...
end
The Company looks like this:
class Company < ActiveRecord::Base
validates :name, :presence => true
validates :phone1, :presence => true
validates_format_of :phone1, :phone2,
:with => /^[\(\)0-9\- \+\.]{10,20}$/,
:message => "Invalid phone number, must be 10 digits. e.g. - 415-555-1212",
:allow_blank => true,
:allow_nil => true
has_many :users
has_many :plans
end
.. controller looks like this
def index
#plans = Plan.find_all_plans_for_company(current_user.company)
respond_to do |format|
format.html # index.html.erb
format.json { render json: #plans }
end
end
.. and my RSpec test looks like this (excuse me if its full of gimmickery, I'm just splunking around with it and cannot get it to work).
describe PlansController do
def valid_attributes
{
:company_id => 1,
:amount => 1000
}
end
describe "GET index" do
it "should return the Plans for which this users company has" do
#company = mock_model(Company, :id => 1, :name => "Test Company", :phone1 => "555-121-1212")
Company.stub(:find).with(#company.id).and_return(#company)
controller.stub_chain(:current_user, :company).and_return(#company)
plan = Plan.create! valid_attributes
get :index, {}
assigns(:plans).should eq([plan])
end
# Other tests ...
end
end
The problem is, when I try this (or any of the crazy other variants I've tried) I get this error:
ActiveRecord::RecordInvalid: Validation failed: Company can't be blank
I'm not sure why this is happening as I thought the Company.stub call would handle this for me. But apparently not.
What am I missing here and what am I doing wrong? How can I get this test to pass?
Let's peel back the layers on this spec, to make sure things make sense (and to make sure I understand what's going on). First, what are you testing?
it "should return the Plans for which this users company has" do
...
assigns(:plans).should eq([plan])
So you want to check that the plans associated with the company of the current user are assigned to #plans. We can stub or mock out everything else.
Looking at the controller code, we have:
def index
#plans = Plan.find_all_plans_for_company(current_user.company)
What do we need to get this to work, without hitting the database and without depending on the models?
First of all, we want to get a mock company out of current_user.company. This is what these two lines in your spec code do:
#company = mock_model(Company, :id => 1, :name => "Test Company", :phone1 => "555-121-1212")
controller.stub_chain(:current_user, :company).and_return(#company)
This will cause current_user.company to return the mock model #company. So far so good.
Now to the class method find_all_plans_for_company. This is where I'm a bit confused. In your spec, you stub the find method on Company to return #company for id = 1.
But really, wouldn't it suffice just to do something like this in your controller code?:
#plans = current_user.company.plans
If you did it this way, then in your test you could just mock a plan, and then return it as the plans association for your mock company:
#plan = mock_model(Plan)
#company = mock_model(Company, :plans => [ #plan ])
controller.stub_chain(:current_user, :company).and_return(#company)
Then the assignment should work, and you don't need to actually create any model or hit the database. You don't even need to give your mock company an id or any other attributes, which anyway are irrelevant to the spec.
Maybe I'm missing something here, if so please let me know.
Why do you need to mock?
My standard testing setup is to use Database Cleaner which clears out the database from any records created during tests. In this way, the tests are run with real database records which are consequently deleted from the test database after each test.
You might also like taking a look at Factory Girl for creating instances of your models during testing (makes it easy to create 10 company records, for example).
See:
http://rubygems.org/gems/database_cleaner
http://rubygems.org/gems/factory_girl
I have three thoughts coming up that could resolve your issue:
Try adding attr_accessible :company_id to Plan class.
Because mock_model does not actually save to the database when you create a Plan with company_id of 1 it fails validation since it is not present in the database.
Ensure before_save :default_values in Plan class does not mess with company_id attribute of the newly created instance.
I have this:
def valid_attributes
{ :email => "some_#{rand(9999)}#thing.com" }
end
For Rspec testing right? But I would like to do something like this:
def valid_attributes
static user_id = 0
user_id += 1
{ :email => "some_#{user_id}#thing.com" }
end
I don't want user_id to be accessible from anywhere but that method,
is this possible with Ruby?
This is a closure case. Try this
lambda {
user_id = 0
self.class.send(:define_method, :valid_attributes) do
user_id += 1
{ :email => "some_#{user_id}#thing.com" }
end
}.call
Wrapping everything in lambda allows the variables defined within lambda to only exist in the scope. You can add other methods also. Good luck!
This answer is a little larger in scope than your question, but I think it gets at the root of what you're trying to do, and will be the easiest and most maintainable.
I think what you're really looking for here is factories. Try using something like factory_girl, which will make a lot of testing much easier.
First, you'd set up a factory to create whatever type of object it is you're testing, and use a sequence for the email attribute:
FactoryGirl.define do
factory :model do
sequence(:email) {|n| "person#{n}#example.com" }
# include whatever else is required to make your model valid
end
end
Then, when you need valid attributes, you can use
Factory.attributes_for(:model)
You can also use Factory.create and Factory.build to create saved and unsaved instances of the model.
There's explanation of a lot more of the features in the getting started document, as well as instructions on how to add factories to your project.
You can use a closure:
def validator_factory
user_id = 0
lambda do
user_id += 1
{ :email => "some_#{user_id}#thing.com" }
end
end
valid_attributes = validator_factory
valid_attributes.call #=> {:email=>"some_1#thing.com"}
valid_attributes.call #=> {:email=>"some_2#thing.com"}
This way user_id won't be accessible outside.
I'd use an instance variable:
def valid_attributes
#user_id ||= 0
#user_id += 1
{ :email => "some_#{#user_id}#thing.com" }
end
The only variables Ruby has are local variables, instance variables, class variables and global variables. None of them fit what you're after.
What you probably need is a singleton that stores the user_id, and gives you a new ID number each time. Otherwise, your code won't be thread-safe.
I'm not sure this is even possible, but let's see if one of you comes up with a solution. This is more or less about code quality in terms of readability and not an actual problem because I already have a solution. I have a friendship model and a user model. The friendship model is used to model friendships between two users:
class Friendship
def self.requested(user)
where(:user_id => user).where(:status => 'requested')
end
def self.pending(user)
where(:user_id => user).where(:status => 'pending')
end
def self.accepted(user)
where(:user_id => user).where(:status => 'accepted')
end
# ...
end
class User
has_many :friendships
# ...
end
Is it somehow possible to call the requested, pending or accepted scope through
the user model without providing an argument?
a_user.friendships.pending # this does not work, is there a way to get it working?
a_user.friendships.pending(a_user) # works of course!
I think this should work if you take the argument off. Calling pending off of the user object like this should already scope friendships to the appropriate user. Define the method like this:
def self.pending
where(:status => 'pending')
end
And call:
a_user.friendships.pending
Check the logs for the generated query if you're not sure it's working.
If you still want to call it by passing an argument I'd name that method Friendship.pending_for(user).