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 Off (2)
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 Off (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 Off
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 Off (2)