I have the following ActiveRecord classes:
class User < ActiveRecord::Base
cattr_accessor :current_user
has_many :batch_records
end
class BatchRecord < ActiveRecord::Base
belongs_to :user
named_scope :current_user, lambda {
{ :conditions => { :user_id => User.current_user && User.current_user.id } }
}
end
and I'm trying to test the named_scope :current_user using Shoulda but the following does not work.
class BatchRecordTest < ActiveSupport::TestCase
setup do
User.current_user = Factory(:user)
end
should_have_named_scope :current_user,
:conditions => { :assigned_to_id => User.current_user }
end
The reason it doesn't work is because the call to User.current_user in the should_have_named_scope method is being evaluated when the class is being defined and I'm change the value of current_user afterwards in the setup block when running the test.
Here is what I did come up with to test this named_scope:
class BatchRecordTest < ActiveSupport::TestCase
context "with User.current_user set" do
setup do
mock_user = flexmock('user', :id => 1)
flexmock(User).should_receive(:current_user).and_return(mock_user)
end
should_have_named_scope :current_user,
:conditions => { :assigned_to_id => 1 }
end
end
So how would you test this using Shoulda?
-
I think you are going about this the wrong way. Firstly, why do you need to use a named scope? Wont this just do?
class BatchRecord < ActiveRecord::Base belongs_to :user def current_user self.user.class.current_user end endIn which case it would be trivial to test. BUT! WTF are you defining
current_useras a class attribute? Now that Rails 2.2 is "threadsafe" what would happen if you were running your app in two seperate threads? One user would login, setting thecurrent_userfor ALLUserinstances. Now another user with admin privileges logs in andcurrent_useris switched to their instance. When the first user goes to the next page he/she will have access to the other persons account with their admin privileges! Shock! Horror!What I reccomend doing in this case is to either making a new controller method
current_userwhich returns the current user's User instance. You can also go one step further and create a wrapper model like:class CurrentUser attr_reader :user, :session def initialize(user, session) @user, @session = user, session end def authenticated? ... end def method_missing(*args) user.send(*args) if authenticated? end endOh, and by the way, now I look at your question again perhaps one of the reasons it isn't working is that the lineEDIT I'm an idiot.User.current_user && User.current_user.idwill return a boolean, rather than the Integer you want it to.Named scope is really the absolutely wrong way of doing this. Named scope is meant to return collections, rather than individual records (which is another reason this fails). It is also making an unnecessary call the the DB resulting in a query that you don't need.
Chuck : The Ruby logical operators (&&, ||) don't work the way you suggest. They return the last argument evaluated. So that current_user code will only return a boolean if either User.current_user or User.current_user.id is a boolean — which I assume is not the case.Chris Lloyd : Whoops, you're right! Early morning drinking wasn't helping with my Ruby skills. -
I just realized the answer is staring right at me. I should be working from the other side of the association which would be
current_user.batch_records. Then I simply test thenamed_scopeon theUsermodel and everything is fine.@Chris Lloyd - Regarding the thread safety issue, the
current_userattribute is being set by abefore_filterin myApplicationController, so it is modified per request. I understand that there is still the potential for disaster if I chose to run in a multi-threaded environment (which is currently not the case). That solution I suppose would be another topic entirely.Chris Lloyd : http://m.onkey.org/2008/10/23/thread-safety-for-your-rails#comment-2601Matt Haley : I was previously having to constantly pass around current_user, I suppose after reading that, I'll just have to live with it. Thanks for your comments.
0 comments:
Post a Comment