By: Matt Lins
November 14, 2008 at 08:11 AM · Posted under BDD, Rails, factory_girl, shoulda
Is anyone else using shoulda, factory_girl and webrat for Rails integration tests?
I did a quick search and found Daniel Wellman using it in integration tests minus shoulda and factory_girl. I really like adding shoulda to get the contexts and factory_girl is a great replacement for fixtures. When combined with webrat it really makes for some easy to read integration tests. By the way, I'm already using shoulda and factory_girl for my unit and functional tests.
Here is an example I whipped up last night. It's nothing fancy. I'll probably make some modifications in the future, but so far I'm happy with it. I'm loading webrat in my test_helper.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
require '../test_helper'
class AdminLoginTest < ActionController::IntegrationTest
context 'An employee' do
setup do
@employee = Factory(:employee)
end
should 'be able to login with a valid username and password' do
visit '/admin'
assert https?
assert_equal '/admin/sessions/new', path
fills_in 'Email', :with => @employee.user.email
fills_in 'Password', :with => @employee.user.password
clicks_button 'Log In'
assert_equal @employee.user.id, session[:user_id]
end
should 'be denied access with an invalid username or password' do
visit '/admin'
assert https?
assert_equal '/admin/sessions/new', path
fills_in 'Email', :with => @employee.user.email
fills_in 'Password', :with => 'wrongpassord'
clicks_button 'Log In'
assert_nil session[:user_id]
end
end
end
|
This is testing a simple login to the admin section of a project I'm working on. We're using restful_authentication. Each line of each test is essentially an assertion. If something doesn't respond with a success or a form field is missing, the test fails.
Comments (3)
By: Matt Lins
November 11, 2008 at 12:22 PM · Posted under BDD, Rails, Ruby, factory_girl
I just ran into a problem with factory_girl sequences. The problem was I defined my Factories like this:
1
2
3
4
5
6
7
8
9
10
11
12
|
# Sequences
Factory.sequence :name do |n|
'name' + n.to_s
end
# Manufacturers
Factory.define :manufacturer do |m|
m.name Factory.next(:name)
m.active true
end
|
Assume I have a validates_uniqueness_of validation on name in Manufacturer:
1
2
3
4
5
|
class Manufacturer < ActiveRecord::Base
validates_uniqueness_of :name
end
|
Now, lets say I created a couple of manufacturers in my test setup:
1
2
3
4
5
6
7
8
9
|
require File.dirname(__FILE__) + '/../test_helper'
class ProductTest < ActiveSupport::TestCase
setup do
2.times { Factory(:manufactuer) }
end
end
|
Ok, this setup block will fail because it's creating 2 manufacturers with the same name. But, I thought I was using a sequence? Well, the sequence is only sequenced during the definition of the factories. Meaning, in the above example Factory#next was only called once.
Luckily, factory_girl allows us to use something called lazy attributes. We can just wrap our sequence with some curly brackets, like so:
|
m.name { Factory.next(:name) } |
Now everytime I create a new Manufacturer the sequence will generate a unique name. Nice! Maybe if I'd taken some more time to RTFM, I wouldn't have wasted time on this problem. Regardless, I thought maybe I'd be able to help someone else out that runs into this.
More about factory_girl
Comments (1)
By: Matt Lins
August 21, 2007 at 02:16 AM · Posted under Rails, Ruby
The loading of the built-in validators
Well, it looks as if my posts will be irrespective of the actual chronology of events involved in validation. At least from a Rails start-to-finish call stack perspective. This post will venture back before those events discussed in Part I. The reason being, I forgot to mention how the Rails built-in validators are loaded and executed.
When your model's class definition is encountered(during initialization), all of the method calls to the built-in validators are ran. Let's say we have a model that looks like this:
1
2
3
|
class Supplier < ActiveRecord::Base
validates_uniqueness_of :name
end
|
When #validates_uniqueness_of is called, it jumps to ActiveRecord::Validations::ClassMethods, which was mixed in with ActiveRecord::Base during initilization earlier. We're not going to look at this method in whole because it's too long. The important part is that it makes use of #validates_each. I'd like to talk about #validates_each because it is the heart of almost all the built-in validators(some can't use it). Here it is:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
def validates_each(*attrs)
options = attrs.last.is_a?(Hash) ? attrs.pop.symbolize_keys : {}
attrs = attrs.flatten
# Declare the validation.
send(validation_method(options[:on] || :save)) do |record|
# Don't validate when there is an :if condition and that condition is false
unless options[:if] && !evaluate_condition(options[:if], record)
attrs.each do |attr|
value = record.send(attr)
next if value.nil? && options[:allow_nil]
yield record, attr, value
end
end
end
end |
After the first couple of lines you see this:
|
send(validation_method(options[:on] || :save)) do |record| |
This line of code is using the Ruby #send method to make a dynamic call to whatever #validation_method returns. Let's look at it:
1
2
3
4
5
6
7
|
def validation_method(on)
case on
when :save then :validate
when :create then :validate_on_create
when :update then :validate_on_update
end
end |
When you declare your validation in your model, :on is an optional parameter. So, if we only wanted to run our validation on only the creation of records, we could do this:
1
2
3
|
class Supplier > ActiveRecord::Base
validates_uniqueness_of :name, :on => :create
end |
The #validation_method method will tell the #send method back in #validates_each where to declare the validator. Assuming we pass don't pass anything for the :on parameter it'll be declared by #validate(both create and update). Don't get these methods confused with low-level #validate, #validate_on_create and #validate_on_update of which you can override in your models. These are defined outside of Validations::ClassMethods and they have a different amount of parameters. Lets look at Validations::ClassMethods#validate:
1
2
3
4
|
def validate(*methods, &block)
methods << block if block_given?
write_inheritable_set(:validate, methods)
end |
Validators can be methods, classes or blocks. This method will handle whatever you throw at it and toss it onto the array of validations to be ran whenever #valid? is called. It's using a method called #write_inheritable_set to store the array (read more about it in the inheritable_attributes.rb file).
Now, if you remember correctly from Part I the ActiveRecord::Base#save call is actually aliased to ActiveRecord::Validations#save_with_validation. This method makes a call to ActiveRecord::Validations#valid?. Let's dig into it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
def valid?
errors.clear
run_validations(:validate)
validate
if new_record?
run_validations(:validate_on_create)
validate_on_create
else
run_validations(:validate_on_update)
validate_on_update
end
errors.empty?
end |
First, it clears the errors object. The errors object is defined by the ActiveRecord::Errors class, which mixes in Enumerable. You can add errors with #add and clear them with #clear etc. Anytime a validator encounters a problem with your model it adds an error. Next, #valid? calls #run_validations with :validate as a parameter(validation_method). Remember, these are validations that are called on both create and update. Continuing, it determines whether the record is new, it calls #run_validations again with the appropriate parameter(validation_method). Lastly, it returns the result of the expression errors.empty?. So, if any problems occurred during #run_validations, then the validator would have added an error to the enumerable causing #valid? to return false. Let's look at #run_validations:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
def run_validations(validation_method)
validations = self.class.read_inheritable_attribute(validation_method.to_sym)
if validations.nil? then return end
validations.each do |validation|
if validation.is_a?(Symbol)
self.send(validation)
elsif validation.is_a?(String)
eval(validation, binding)
elsif validation_block?(validation)
validation.call(self)
elsif validation_class?(validation, validation_method)
validation.send(validation_method, self)
else
raise(
ActiveRecordError,
"Validations need to be either a symbol, string (to be eval'ed), proc/method, or " +
"class implementing a static validation method"
)
end
end
end |
It grabs all the validations based on the validation_method passed using the #read_inheritable_attribute (look in the inheritable_attributes.rb file to find out why this is used). Next, it iterates through each of the validations. It does a check to see if the validation is a method, class or a block and executes it appropriately. Remember, if these validations encounter problems with the model data, they'll add an error onto the @errors enumerable causing #valid? to return false. Let's tie it all together with a review of the #save_with_validation method:
1
2
3
4
5
6
7
|
def save_with_validation(perform_validation = true)
if perform_validation && valid? || !perform_validation
save_without_validation
else
false
end
end |
If #valid? returns true(and you didn't pass false to #save), then #save_with_validation makes a call to #save_without_validation, which is actually aliased back to ActiveRecord::Base#save thus completing our save. Remember, ActiveRecord::Base#save is actually aliased to #save_with_validation so that validations can happen. Confused? Well, this is done with #alias_method_chain. Also, you may need to review Part I.
That should do it for another part to this series. If I feel the need to write Part III, we'll probably dive into what each of the built-in validators is actually doing.
Comments (2)
By: Matt Lins
August 16, 2007 at 03:46 AM · Posted under Rails, Ruby
I've been digging through the core more and more. One thing that still throws me off once in a while are method declarations that I can't seem to find. Naturally, I first do a search for the method name in a bunch of files. When it doesn't show up, I become frustrated because even if it was aliased, it should still come up in search. Well, if that's the case, look no further than a call to #alias_method_chain(usually). Even though I was aware of this method(it's nothing new), it still throws me off. I just need to remember anytime I see "with" or "without" in the method name, I should probably search for a call to #alias_method_chain.
For those of you not aware of what it is, here is the code(aliasing.rb):
1
2
3
4
5
6
7
8
|
def alias_method_chain(target, feature)
# Strip out punctuation on predicates or bang methods since
# e.g. target?_without_feature is not a valid method name.
aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
yield(aliased_target, punctuation) if block_given?
alias_method "#{aliased_target}_without_#{feature}#{punctuation}", target
alias_method target, "#{aliased_target}_with_#{feature}#{punctuation}"
end |
It is used to DRY up the common aliasing pattern of:
1
2
|
alias_method :foo_without_feature, :foo
alias_method :foo, :foo_with_feature |
This is used a lot in the Rails core. It is nice, but when you're not aware of it, it can certainly wear on you when trying to understand some code. I know it's a method that has been talked about plenty, but it's one of those things that is hard to search for when you're actually trying to solve the mystery it created.
Comments
By: Matt Lins
August 13, 2007 at 04:11 PM · Posted under Rails, Ruby
The road from save to validation.
Rails validations seem simple on the outside, but have you ever taken the time to understand what's really going on? What actually happens when you do model.save? I ran into a problem one day and I decided to take a gander and figure out how it all works. It was a little difficult. The Rails core can be very daunting. With the mix-ins, aliases and the enormity of the ActiveRecord code base, one could spend hours trying to figure it out. Well, I did and I'm going to try to explain it.
To start, I have my Rails application froze to 1.2.3 and I can easily browse the code by looking in my vendors/rails/ directory(you could check it out of SVN if you desire, to follow along). All the files I mention will be relative to that path. I began my journey in the activerecord/validations.rb file. It contains a contains two ActiveRecord classes: ActiveRecord::Errors and ActiveRecord:RecordInvalid and a module: Validations, which contains another module: Validations::ClassMethods. Well, this file gave me a nice idea of how things like validates_presence_of works(which I'll explain later), but I wanted to know more. Specifically, during the save process, when do validations get called and from where?
Well, I scanned right over some clues in activerecord/validations.rb and hastily decided to go right into the heart of ActiveRecord: ActiveRecord::Base in activerecord/base.rb. I was puzzled when I found the #save and #save! methods.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# * No record exists: Creates a new record with values
# matching those of the object attributes.
# * A record does exist: Updates the record with
# values matching those of the object attributes.
def save
create_or_update
end
# Attempts to save the record, but instead of just returning false if it couldn't happen,
# it raises a RecordNotSaved exception
def save!
create_or_update || raise(RecordNotSaved)
end |
Naturally, I moved on to #create_or_update, to find:
1
2
3
4
5
|
def create_or_update
raise ReadOnlyRecord if readonly?
result = new_record? ? create : update
result != false
end |
Getting closer, but not quite what I'm looking for. This method checks and raises an exception if the record is read-only. Then it determines if the record is new and calls the appropriate method for a create or update.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
# Updates the associated record with values matching those of the instance attributes.
# Returns the number of affected rows.
def update
connection.update(
"UPDATE #{self.class.table_name} " +
"SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false))} " +
"WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quote_value(id)}",
"#{self.class.name} Update"
)
end
# Creates a record with values matching those of the instance attributes
# and returns its id.
def create
if self.id.nil? && connection.prefetch_primary_key?(self.class.table_name)
self.id = connection.next_sequence_value(self.class.sequence_name)
end
self.id = connection.insert(
"INSERT INTO #{self.class.table_name} " +
"(#{quoted_column_names.join(', ')}) " +
"VALUES(#{attributes_with_quotes.values.join(', ')})",
"#{self.class.name} Create",
self.class.primary_key, self.id, self.class.sequence_name
)
@new_record = false
id
end |
Where does validation come into play? We're already building SQL? Well this stumped me for about 10 seconds. Then I realized somewhere they must be mixing in the validation functionality. Logically, I headed back to activerecord/validations.rb to find what I overlooked:
1
2
3
4
5
6
7
8
|
def self.included(base) # :nodoc:
base.extend ClassMethods
base.class_eval do
alias_method_chain :save, :validation
alias_method_chain :save!, :validation
alias_method_chain :update_attribute, :validation_skipping
end
end |
This code is aliasing the Base#save method to Validations#save_with_validation, which is mixed in just prior. To do so, it uses a built-in rails convenience method: #alias_method_chain. This happens when the module is loaded by using self.included.
I really enjoy the design of ActiveRecord. It is totally usable outside of Rails. Not only that, if you don't need validation then you simply don't load activerecord/validation.rb(Rails does this by default during initialization). But, just because you load the Validations module, doesn't mean you actually need to validate on save. As seen in the overridden methods below, you can simply pass false as a parameter.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
# The validation process on save can be skipped by passing false. The regular Base#save method
# is replaced with this when the validations module is mixed in, which it is by default.
def save_with_validation(perform_validation = true)
if perform_validation && valid? || !perform_validation
save_without_validation
else
false
end
end
# Attempts to save the record just like Base#save but will raise a RecordInvalid exception
# instead of returning false if the record is not valid.
def save_with_validation!
if valid?
save_without_validation!
else
raise RecordInvalid.new(self)
end
end |
Well I think this is a good place to stop for Part I of this series. In the next post we will dig in to the actual validation process.
Comments (2)
By: Matt Lins
July 29, 2007 at 07:00 PM · Posted under BDD, Rails, rSpec
rSpec is all about writing easy to read, self-explanatory specs (tests to you TDDr's). It's tempting to want to use fixtures or even come up with a clever way to provide sample data for testing models. I'll demonstrate, what I consider to be the most beautiful way to write specs. Some of my influences have been the rSpec Mailing list and the PeepCode Screencast. I'll warn you brilliant ruby programmers: this is by no means efficient, DRY code. It is code that any stranger could come along and figure out exactly what the original developer was trying to accomplish.
Assume we have a simple Model like this:
1
2
3
4
5
6
|
class Post < ActiveRecord::Base
validates_presence_of :title
validates_length_of :title, :minimum => 2
end |
First we'll need the help of a couple of additional methods in Hash. Add this to your spec_helper.rb in the root of your /spec directory.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# Taken from
# http://wincent.com/knowledge-base/Fixtures_considered_harmful%3F
class Hash
# for excluding keys
def except(*exclusions)
self.reject { |key, value| exclusions.include? key.to_sym }
end
# for overriding keys
def with(overrides = {})
self.merge overrides
end
end |
These two methods with allow us to create a "valid_attributes" Hash and then tweak it later on. We're going to create a helper module at the top of the spec. This example is from one of my specs for a model: Post.
1
2
3
4
5
6
7
8
9
10
|
require File.dirname(__FILE__) + '/../spec_helper'
module PostSpecHelper
def valid_post_attributes
{
:title => "rSpec is Great"
:body => "rSpec is a fun way to write documentation
and test your application at the same time"
}
end
end |
Now we have some nice sample data. We can use this helper(don't forget to include it) in the before(setup) block of each of our describe blocks. Let start with a simple valid Post.
1
2
3
4
5
6
7
8
9
10
|
describe Post do
include PostSpecHelper
before(:each) do
@post = Post.new
@post.attributes = valid_post_attributes
end
it do
@post.should be_valid
end
end |
Great. Now let's use the add Hash#with to test one of our validations.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
describe Post, "with a title that is 1 character long" do
include PostSpecHelper
before(:each) do
@post = Post.new
@post.attributes = valid_post_attributes.with(:title => "r")
end
it do
@post.should_not be_valid
end
it "should have an error on title" do
@post.should have(1).error_on(:title)
end
end |
Now, we'll use the Hash#except to test another validation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
describe Post, "with a blank title" do
include PostSpecHelper
before(:each) do
@post = Post.new
@post.attributes = valid_post_attributes.except(:title)
end
it do
@post.should_not be_valid
end
it "should have an error on title" do
@post.should have(1).error_on(:title)
end
end |
There you have it, an easy to read spec. Any developer could come along and figure out exactly what I was testing. The sample data is provided in the top of every spec. I don't have to go searching through fixtures to figure out why this test failed.
Comments (10)
By: Matt Lins
May 16, 2007 at 06:01 PM · Posted under Rails
I've arrived in Portland, OR for RailsConf 2007. I'm pretty excited, it should be a great experience.
I came out early for some of the sessions(tutorials). I'm attending the jRuby and the Capistrano session. I'm really looking forward to the jRuby session, I've only read about it and have yet to use it. I really like the idea of being able to throw a rails app into a war file and deploy it in a web container.
I'm going to try to post many updates about the conference. I brought my new camera, but unfortunately I forgot the charger and I only have about a 1/8 bar left. We'll see how many I can snap.
I'll post tomorrow in detail about the sessions.
Comments