Is there a Lint for ActiveRecord? - ruby-on-rails

Rubyists, particularly Railsers, know all about ActiveModel::Lint, which is a handy way to ensure that you haven't cocked up when writing your ActiveModel subclass, and saves hours of head-scratching. I use it all the time for table-less models in Rails projects.
But I spend a lot of time hunting down errors with my usage of ActiveRecord - particularly with double-sided associations. For example
# app/models/blog.rb
class Blog < ActiveRecord::Base
has_many :posts # so far so good...
end
# app/models/post.rb
class Post < ActiveRecord::Base
has_one :blog # incorrect - should be belongs_to
end
That example is somewhat contrived, as nobody's that silly. But what if Blog actually has a post_id column? Exceptions would not be raised where expected.
I'd like to be able to write a test for Blog like this
# test/unit/blog_test.rb
require 'test/test_helper'
class BlogTest < ActiveSupport::TestCase
include ActiveRecord::Lint
end
...and the test output should say something like Blog :has_many :posts, but Post does not :belong_to :blog!. It would need to reflect on the associations, dealing with table-names, foreign-keys, etc, and considering :through models on the way. I know that proper unit tests will detect these issues anyway, but generally as side-effects of other tests.
Does anyone know of a project which does this? (I'm only interested in ActiveRecord >= 3.1). All I could find was active_record_lint, which doesn't really do this at all.
Additionally, it would be great if all the existing fixtures could be tested automatically, to make sure that those pesky associations are set up. I usually just do
test "fixtures" do
Post.all.each { |p| assert p.valid? "Fixture is broken! #{p.inspect}" }
end
but there's surely a more elegant way.

A tool like this would be useful, but you should be testing these things explicitly anyway. This will uncover problems like this immediately. For instance, a typical unit test:
def test_defaults
blog = Blog.create(
:title => 'Example Blog'
)
assert blog.valid?
assert_equal [ ], blog.errors.full_messages
assert !blog.new_record?
assert_equal 0, blog.posts.count
end
For your post:
def test_defaults
blog = Blog.create(
:title => 'Example Blog'
)
post = Post.create(
:blog => blog,
:content => 'Blogging blog bloggers blogged blogs'
)
assert post.valid?
assert_equal [ ], post.errors.full_messages
assert !post.new_record?
assert_equal blog.id, post.blog_id
assert_equal 1, blog.posts.count
end
Those are the long form versions of some helper methods that I usually use.
If you don't exercise the relationships, you're not testing them and you've got a problem with your test coverage. If you exercise a bad relationship it will show up as an error.

Related

Using RSpec to test active record scope that uses the includes method

Given the following two classes:
class Location < ActiveRecord::Base
belongs_to :holiday_schedule
validates :name, :presence => true, :uniqueness => {:case_sensitive => false}
scope :with_holiday_schedule, includes(:holiday_schedule)
end
class HolidaySchedule < ActiveRecord::Base
validates_presence_of :name
has_many :locations
end
How would you spec the with_holiday_schedule scope to ensure that accessing location.holiday_schedule.name in a loop will not cause the N+1 Query problem?
After positing to the RSpec users mailing list and reading more about speccing in general, I ultimately came to the realization that this isn't worth a unit test. The :includes directive is well tested in rails and the overhead of testing that simple line is higher than the risk associated with it failing or being removed by another developer - at least in my case.
What I really care about is performance of the application. Speccing performance would be a lot more productive than jumping through hoops to unit test this line.
Have a look at Counting the number of queries performed.
This should work perfectly in your solution.
They've done this:
ActiveRecord::Base.count_queries do
Ticket.first
end
You can use it this way in your spec:
queries = ActiveRecord::Base.count_queries do
location.with_holiday_schedule.holiday_schedule.name
end
queries.should_be == 1
I hope this will work.

Testing has_many association with RSpec

I'm trying to test the Hour model with RSpec, namely the class method 'find_days_with_no_hours' that behaves like a scope. Business has_many Hours associated through STI.
find_days_with_no_hours needs to be called through a Business object and I can't figure out how to set this up in the RSpec test.
I want to be able to test something like:
bh = #business.hours.find_days_with_no_hours
bh.length.should == 2
I've tried various approaches, like creating a Business object (with, say, Business.create), then setting #business.hours << mock_model(BusinessHour, ..., ..., ...) but that doesn't work.
How is this normally done?
class Business < ActiveRecord::Base
has_many :hours, :as => :hourable
end
class Hour < ActiveRecord::Base
belongs_to :hourable, :polymorphic => true
def self.find_days_with_no_hours
where("start_time IS NULL")
end
end
You can't test an arel method by creating the object via mocks. Arel is going to go straight into the database, and not see any mocks or anything that you've created in memory. I would grab factory_girl and then define an hour factory for yourself:
Factory.define :hour do |f|
f.start_time {Time.now}
end
Factory.define :unstarted_day, :parent => :hour do |f|
f.start_time nil
end
And then in your test...
business = Factory.create(:business)
business.hours << Factory.create(:unstarted_day)
bh = business.hours.find_days_with_no_hours
bh.length.should == 1
However, factory_girl is just a personal preference for setting up known state, you can just as easily use create statements or fixtures, the problem for you was trying to use mock_model() (which prevents a database hit), and then using a method that queries the database.

Is validates_presence_of the preferred technique to require a has_many relationship

Basically: My model requires at least one instance of an associated model be present. Should I use validates_presence_of to assert this validation, or should I write some custom validation code?
Here are the essentials of my model:
class Event < ActiveRecord::Base
has_and_belongs_to_many :channels
validates_presence_of :channels, :message => "can't be empty"
end
(I assume things would be the same if I used has_many in place of has_and_belongs_to_many.)
Instead of the validates_presence_of line I could do this:
def validate
errors.add(:channels, "can't be empty") if channels.size < 1
end
I replaced the latter with the former in the Rails app I'm working on and am wondering if there might be any problems.
So to be more sure, I wrote the following rspec coverage, and both implementations respond the same:
describe Event do
before do
#net = Factory.create(:network)
#net_config = Factory.create(:network_config, :network => #net)
end
it "must have a channel" do
e = Factory.build(:event, :network => #net, :channels => [])
e.should have(1).error_on(:channels)
end
end
That is, if I remove the validation code, the above spec fails; if I put in either version of the validation code, the above spec passes.
So I might assume that my new implementation is ok. But I've read that validates_presence triggers a database load which, in turn, would wipe out any in-memory objects constructed from nested attributes. The proxy_target method, on the other hand, will return the in-memory objects without triggering a load. Some links on proxy_target: http://rubydoc.info/docs/rails/ActiveRecord/Associations/AssociationProxy http://withoutscope.com/2008/8/22/don-t-use-proxy_target-in-ar-association-extensions
In my particular case I'm not using ActiveRecord::Relation, but I wonder if I need to be cautious about this.

as_json not calling as_json on associations

I have a model with data that should never be included when it is rendered as json. So I implemented the class' as_json method to behave appropriately. The problem is when other models with associations with this model render json, my custom as_json is not being called.
class Owner < ActiveRecord::Base
has_one :dog
def as_json(options={})
puts "Owner::as_json"
super(options)
end
end
class Dog < ActiveRecord::Base
belongs_to :owner
def as_json(options={})
puts "Dog::as_json"
options[:except] = :secret
super(options)
end
end
Loading development environment (Rails 3.0.3)
ruby-1.9.2-p136 :001 > d = Dog.first
=> #<Dog id: 1, owner_id: 1, name: "Scooby", secret: "I enjoy crapping everwhere">
ruby-1.9.2-p136 :002 > d.as_json
Dog::as_json
=> {"dog"=>{"id"=>1, "name"=>"Scooby", "owner_id"=>1}}
ruby-1.9.2-p136 :004 > d.owner.as_json(:include => :dog)
Owner::as_json
=> {"owner"=>{"id"=>1, "name"=>"Shaggy", :dog=>{"id"=>1, "name"=>"Scooby", "owner_id"=>1, "secret"=>"I enjoy crapping everwhere"}}}
Thanks for the help
This is a known bug in Rails. (The issue is marked closed due to the migration to Github issues from the previous bug tracker, but it's still a problem as of Rails 3.1.)
As acknowledged above, this is an issue with the Rails base. The rails patch here is not yet applied and seems at least slightly controversial, so I'm hesitant to apply it locally. Even if applied as a monkey patch it could potentially complicate future rails upgrades.
I'm still considering RABL suggested above, it looks useful. For the moment, I'd rather not add another view templating language into my app. My current needs are very small.
So here's a workaround which doesn't require a patch and work for most simple cases. This works where the association's as_json method you'd like to have called looks like
def as_json(options={})
super( <... custom options ...> )
end
In my case I've got Schedule model which has many Events
class Event < ActiveRecord::Base
# define json options as constant, or you could return them from a method
EVENT_JSON_OPTS = { :include => { :locations => { :only => [:id], :methods => [:name] } } }
def as_json(options={})
super(EVENT_JSON_OPTS)
end
end
class Schedule < ActiveRecord::Base
has_many :events
def as_json(options={})
super(:include => { :events => { Event::EVENT_JSON_OPTS } })
end
end
If you followed the guideline that anytime you :include an association in your as_json() methods, you define any options you need as a constant in the model to be referenced, this would work for arbitrary levels of associations. NOTE I only needed the first level of association customized in the above example.
I've found that serializable_hash works just as you'd expect as_json to work, and is always called:
def serializable_hash(options = {})
result = super(options)
result[:url] = "http://.."
result
end
I ran into the same issue. I wanted this to work:
render :json => #favorites.as_json(:include => :location)
But it didn't so I ended up adding an index.json.erb with the following:
<% favs = #favorites.as_json.each do |fav| %>
<% fav["location"] = Location.find(fav["location_id"]).as_json %>
<% end %>
<%= favs.to_json.html_safe %>
Not a fix - just a work around. I imagine you did the same thing.
Update #John pointed out this is a known bug in Rails. A patch to fix it appears to be: at https://github.com/rails/rails/pull/2200. Nevertheless, you might try RABL, because its sweet.
I've always been frustrated with passing a complex set of options to create the JSON views I want. Your problem, which I experienced with Mongoid in Rails 3.0.9, prompted me to write JSON templates. But actually, if you're dealing with relations or custom api properties, it turns out that templates are way nicer.
Besides, dealing with different outputs seems like the View layer to me, so I settled on using RABL, the API templating language. It makes it super easy to build valid JSON and include any associations or fields.
Not a solution to the problem, but a better solution for the use case.
This was reported as a bug: http://ternarylabs.com/2010/09/07/migrating-to-rails-3-0-gotchas-as_json-bug/

Is it legal to stub the #class method of a Mock object when using RSpec in a Ruby on Rails application?

I would like to stub the #class method of a mock object:
describe Letter do
before(:each) do
#john = mock("John")
#john.stub!(:id).and_return(5)
#john.stub!(:class).and_return(Person) # is this ok?
#john.stub!(:name).and_return("John F.")
Person.stub!(:find).and_return(#john)
end
it.should "have a valid #to field" do
letter = Letter.create!(:to=>#john, :content => "Hello John")
letter.to_type.should == #john.class.name
letter.to_id.should == #john.id
end
[...]
end
On line 5 of this program, I stub the #class method, in order to allow things like #john.class.name. Is this the right way to go? Will there be any bad side effect?
Edit:
The Letter class looks like this:
class Letter < ActiveRecord::Base
belongs_to :to, :polymorphic => true
[...]
end
I wonder whether ActiveRecord gets the :to field's class name with to.class.name or by some other means. Maybe this is what the class_name method is ActiveRecord::Base is for?
I think you should be using mock_model for this particular case.
Your before(:each) would look like that:
before(:each) do
#john = mock_model(Person, :name => "John F.")
Person.stub!(:find).and_return(#john)
end
Then for your other question, you should not really care about how Rails works to test your behaviour. I don't think it's a good idea to test to_type and to_id fields yourself. This is Rails behaviour and as such should be tested in Rails, not in your project.
I have been using Remarkable for a while and it makes this really easy to specify:
describe Letter
should_belong_to :to, :polymorphic => true
end

Resources