| Module | ThoughtBot::Shoulda::ActiveRecord |
| In: |
lib/shoulda/active_record_helpers.rb
|
These helpers will test most of the validations and associations for your ActiveRecord models.
class UserTest < Test::Unit::TestCase
should_require_attributes :name, :phone_number
should_not_allow_values_for :phone_number, "abcd", "1234"
should_allow_values_for :phone_number, "(123) 456-7890"
should_protect_attributes :password
should_have_one :profile
should_have_many :dogs
should_have_many :messes, :through => :dogs
should_belong_to :lover
end
For all of these helpers, the last parameter may be a hash of options.
Ensures that the attribute can be set to the given values. Requires an existing record
Example:
should_allow_values_for :isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0"
# File lib/shoulda/active_record_helpers.rb, line 167
167: def should_allow_values_for(attribute, *good_values)
168: get_options!(good_values)
169: klass = model_class
170: good_values.each do |v|
171: should "allow #{attribute} to be set to #{v.inspect}" do
172: assert_good_value(klass, attribute, v)
173: end
174: end
175: end
Ensure that the belongs_to relationship exists.
should_belong_to :parent
# File lib/shoulda/active_record_helpers.rb, line 467
467: def should_belong_to(*associations)
468: get_options!(associations)
469: klass = model_class
470: associations.each do |association|
471: should "belong_to #{association}" do
472: reflection = klass.reflect_on_association(association)
473: assert reflection, "#{klass.name} does not have any relationship to #{association}"
474: assert_equal :belongs_to, reflection.macro
475:
476: unless reflection.options[:polymorphic]
477: associated_klass = (reflection.options[:class_name] || association.to_s.classify).constantize
478: fk = reflection.options[:foreign_key] || reflection.primary_key_name
479: assert klass.column_names.include?(fk.to_s), "#{klass.name} does not have a #{fk} foreign key."
480: end
481: end
482: end
483: end
Ensures that the length of the attribute is at least a certain length Requires an existing record
Options:
Example:
should_ensure_length_at_least :name, 3
# File lib/shoulda/active_record_helpers.rb, line 236
236: def should_ensure_length_at_least(attribute, min_length, opts = {})
237: short_message = get_options!([opts], :short_message)
238: short_message ||= /short/
239:
240: klass = model_class
241:
242: if min_length > 0
243: min_value = "x" * (min_length - 1)
244: should "not allow #{attribute} to be less than #{min_length} chars long" do
245: assert_bad_value(klass, attribute, min_value, short_message)
246: end
247: end
248: should "allow #{attribute} to be at least #{min_length} chars long" do
249: valid_value = "x" * (min_length)
250: assert_good_value(klass, attribute, valid_value, short_message)
251: end
252: end
Ensures that the length of the attribute is in the given range Requires an existing record
Options:
Example:
should_ensure_length_in_range :password, (6..20)
# File lib/shoulda/active_record_helpers.rb, line 189
189: def should_ensure_length_in_range(attribute, range, opts = {})
190: short_message, long_message = get_options!([opts], :short_message, :long_message)
191: short_message ||= /short/
192: long_message ||= /long/
193:
194: klass = model_class
195: min_length = range.first
196: max_length = range.last
197: same_length = (min_length == max_length)
198:
199: if min_length > 0
200: should "not allow #{attribute} to be less than #{min_length} chars long" do
201: min_value = "x" * (min_length - 1)
202: assert_bad_value(klass, attribute, min_value, short_message)
203: end
204: end
205:
206: if min_length > 0
207: should "allow #{attribute} to be exactly #{min_length} chars long" do
208: min_value = "x" * min_length
209: assert_good_value(klass, attribute, min_value, short_message)
210: end
211: end
212:
213: should "not allow #{attribute} to be more than #{max_length} chars long" do
214: max_value = "x" * (max_length + 1)
215: assert_bad_value(klass, attribute, max_value, long_message)
216: end
217:
218: unless same_length
219: should "allow #{attribute} to be exactly #{max_length} chars long" do
220: max_value = "x" * max_length
221: assert_good_value(klass, attribute, max_value, long_message)
222: end
223: end
224: end
Ensures that the length of the attribute is exactly a certain length Requires an existing record
Options:
Example:
should_ensure_length_is :ssn, 9
# File lib/shoulda/active_record_helpers.rb, line 264
264: def should_ensure_length_is(attribute, length, opts = {})
265: message = get_options!([opts], :message)
266: message ||= /wrong length/
267:
268: klass = model_class
269:
270: should "not allow #{attribute} to be less than #{length} chars long" do
271: min_value = "x" * (length - 1)
272: assert_bad_value(klass, attribute, min_value, message)
273: end
274:
275: should "not allow #{attribute} to be greater than #{length} chars long" do
276: max_value = "x" * (length + 1)
277: assert_bad_value(klass, attribute, max_value, message)
278: end
279:
280: should "allow #{attribute} to be #{length} chars long" do
281: valid_value = "x" * (length)
282: assert_good_value(klass, attribute, valid_value, message)
283: end
284: end
Ensure that the attribute is in the range specified Requires an existing record
Options:
Example:
should_ensure_value_in_range :age, (0..100)
# File lib/shoulda/active_record_helpers.rb, line 298
298: def should_ensure_value_in_range(attribute, range, opts = {})
299: low_message, high_message = get_options!([opts], :low_message, :high_message)
300: low_message ||= /included/
301: high_message ||= /included/
302:
303: klass = model_class
304: min = range.first
305: max = range.last
306:
307: should "not allow #{attribute} to be less than #{min}" do
308: v = min - 1
309: assert_bad_value(klass, attribute, v, low_message)
310: end
311:
312: should "allow #{attribute} to be #{min}" do
313: v = min
314: assert_good_value(klass, attribute, v, low_message)
315: end
316:
317: should "not allow #{attribute} to be more than #{max}" do
318: v = max + 1
319: assert_bad_value(klass, attribute, v, high_message)
320: end
321:
322: should "allow #{attribute} to be #{max}" do
323: v = max
324: assert_good_value(klass, attribute, v, high_message)
325: end
326: end
Ensures that the has_and_belongs_to_many relationship exists, and that the join table is in place.
should_have_and_belong_to_many :posts, :cars
# File lib/shoulda/active_record_helpers.rb, line 448
448: def should_have_and_belong_to_many(*associations)
449: get_options!(associations)
450: klass = model_class
451:
452: associations.each do |association|
453: should "should have and belong to many #{association}" do
454: reflection = klass.reflect_on_association(association)
455: assert reflection, "#{klass.name} does not have any relationship to #{association}"
456: assert_equal :has_and_belongs_to_many, reflection.macro
457: table = reflection.options[:join_table]
458: assert ::ActiveRecord::Base.connection.tables.include?(table), "table #{table} doesn't exist"
459: end
460: end
461: end
Ensure that the given class methods are defined on the model.
should_have_class_methods :find, :destroy
# File lib/shoulda/active_record_helpers.rb, line 489
489: def should_have_class_methods(*methods)
490: get_options!(methods)
491: klass = model_class
492: methods.each do |method|
493: should "respond to class method ##{method}" do
494: assert_respond_to klass, method, "#{klass.name} does not have class method #{method}"
495: end
496: end
497: end
Ensure that the given column is defined on the models backing SQL table. The options are the same as the instance variables defined on the column definition: :precision, :limit, :default, :null, :primary, :type, :scale, and :sql_type.
should_have_db_column :email, :type => "string", :default => nil, :precision => nil, :limit => 255,
:null => true, :primary => false, :scale => nil, :sql_type => 'varchar(255)'
# File lib/shoulda/active_record_helpers.rb, line 537
537: def should_have_db_column(name, opts = {})
538: klass = model_class
539: test_name = "have column named :#{name}"
540: test_name += " with options " + opts.inspect unless opts.empty?
541: should test_name do
542: column = klass.columns.detect {|c| c.name == name.to_s }
543: assert column, "#{klass.name} does not have column #{name}"
544: opts.each do |k, v|
545: assert_equal column.instance_variable_get("@#{k}").to_s, v.to_s, ":#{name} column on table for #{klass} does not match option :#{k}"
546: end
547: end
548: end
Ensure that the given columns are defined on the models backing SQL table.
should_have_db_columns :id, :email, :name, :created_at
# File lib/shoulda/active_record_helpers.rb, line 517
517: def should_have_db_columns(*columns)
518: column_type = get_options!(columns, :type)
519: klass = model_class
520: columns.each do |name|
521: test_name = "have column #{name}"
522: test_name += " of type #{column_type}" if column_type
523: should test_name do
524: column = klass.columns.detect {|c| c.name == name.to_s }
525: assert column, "#{klass.name} does not have column #{name}"
526: end
527: end
528: end
Ensures that there are DB indices on the given columns or tuples of columns. Also aliased to should_have_index for readability
should_have_indices :email, :name, [:commentable_type, :commentable_id] should_have_index :age
# File lib/shoulda/active_record_helpers.rb, line 556
556: def should_have_indices(*columns)
557: table = model_class.name.tableize
558: indices = ::ActiveRecord::Base.connection.indexes(table).map(&:columns)
559:
560: columns.each do |column|
561: should "have index on #{table} for #{column.inspect}" do
562: columns = [column].flatten.map(&:to_s)
563: assert_contains(indices, columns)
564: end
565: end
566: end
Ensure that the given instance methods are defined on the model.
should_have_instance_methods :email, :name, :name=
# File lib/shoulda/active_record_helpers.rb, line 503
503: def should_have_instance_methods(*methods)
504: get_options!(methods)
505: klass = model_class
506: methods.each do |method|
507: should "respond to instance method ##{method}" do
508: assert_respond_to klass.new, method, "#{klass.name} does not have instance method #{method}"
509: end
510: end
511: end
Ensures that the has_many relationship exists. Will also test that the associated table has the required columns. Works with polymorphic associations.
Options:
Example:
should_have_many :friends should_have_many :enemies, :through => :friends should_have_many :enemies, :dependent => :destroy
# File lib/shoulda/active_record_helpers.rb, line 363
363: def should_have_many(*associations)
364: through, dependent = get_options!(associations, :through, :dependent)
365: klass = model_class
366: associations.each do |association|
367: name = "have many #{association}"
368: name += " through #{through}" if through
369: name += " dependent => #{dependent}" if dependent
370: should name do
371: reflection = klass.reflect_on_association(association)
372: assert reflection, "#{klass.name} does not have any relationship to #{association}"
373: assert_equal :has_many, reflection.macro
374:
375: associated_klass_name = reflection.options[:class_name]
376: associated_klass_name = reflection.options[:source].to_s.classify if associated_klass_name.blank?
377: associated_klass_name = association.to_s.classify if associated_klass_name.blank?
378: associated_klass = associated_klass_name.constantize
379:
380: if through
381: through_reflection = klass.reflect_on_association(through)
382: assert through_reflection, "#{klass.name} does not have any relationship to #{through}"
383: assert_equal(through, reflection.options[:through])
384: end
385:
386: if dependent
387: assert_equal dependent.to_s,
388: reflection.options[:dependent].to_s,
389: "#{associated_klass.name} should have #{dependent} dependency"
390: end
391:
392: # Check for the existence of the foreign key on the other table
393: unless reflection.options[:through]
394: if reflection.options[:foreign_key]
395: fk = reflection.options[:foreign_key]
396: elsif reflection.options[:as]
397: fk = reflection.options[:as].to_s.foreign_key
398: else
399: fk = reflection.primary_key_name
400: end
401:
402: assert associated_klass.column_names.include?(fk.to_s),
403: "#{associated_klass.name} does not have a #{fk} foreign key."
404: end
405: end
406: end
407: end
Ensures that the model has a method named scope_name that returns a NamedScope object with the proxy options set to the options you supply. scope_name can be either a symbol, or a method call which will be evaled against the model. The eval‘d method call has access to all the same instance variables that a should statement would.
Options: Any of the options that the named scope would pass on to find.
Example:
should_have_named_scope :visible, :conditions => {:visible => true}
Passes for
named_scope :visible, :conditions => {:visible => true}
Or for
def self.visible
scoped(:conditions => {:visible => true})
end
You can test lambdas or methods that return ActiveRecord#scoped calls:
should_have_named_scope 'recent(5)', :limit => 5 should_have_named_scope 'recent(1)', :limit => 1
Passes for
named_scope :recent, lambda {|c| {:limit => c}}
Or for
def self.recent(c)
scoped(:limit => c)
end
# File lib/shoulda/active_record_helpers.rb, line 626
626: def should_have_named_scope(scope_call, *args)
627: klass = model_class
628: scope_opts = args.extract_options!
629: scope_call = scope_call.to_s
630:
631: context scope_call do
632: setup do
633: @scope = eval("#{klass}.#{scope_call}")
634: end
635:
636: should "return a scope object" do
637: assert_equal ::ActiveRecord::NamedScope::Scope, @scope.class
638: end
639:
640: unless scope_opts.empty?
641: should "scope itself to #{scope_opts.inspect}" do
642: assert_equal scope_opts, @scope.proxy_options
643: end
644: end
645: end
646: end
Ensure that the has_one relationship exists. Will also test that the associated table has the required columns. Works with polymorphic associations.
Example:
should_have_one :god # unless hindu
# File lib/shoulda/active_record_helpers.rb, line 416
416: def should_have_one(*associations)
417: get_options!(associations)
418: klass = model_class
419: associations.each do |association|
420: should "have one #{association}" do
421: reflection = klass.reflect_on_association(association)
422: assert reflection, "#{klass.name} does not have any relationship to #{association}"
423: assert_equal :has_one, reflection.macro
424:
425: associated_klass = (reflection.options[:class_name] || association.to_s.camelize).constantize
426:
427: if reflection.options[:foreign_key]
428: fk = reflection.options[:foreign_key]
429: elsif reflection.options[:as]
430: fk = reflection.options[:as].to_s.foreign_key
431: fk_type = fk.gsub(/_id$/, '_type')
432: assert associated_klass.column_names.include?(fk_type),
433: "#{associated_klass.name} does not have a #{fk_type} column."
434: else
435: fk = klass.name.foreign_key
436: end
437: assert associated_klass.column_names.include?(fk.to_s),
438: "#{associated_klass.name} does not have a #{fk} foreign key."
439: end
440: end
441: end
Ensures that the attribute cannot be changed once the record has been created. Requires an existing record.
should_have_readonly_attributes :password, :admin_flag
# File lib/shoulda/active_record_helpers.rb, line 123
123: def should_have_readonly_attributes(*attributes)
124: get_options!(attributes)
125: klass = model_class
126:
127: attributes.each do |attribute|
128: attribute = attribute.to_sym
129: should "make #{attribute} read-only" do
130: readonly = klass.readonly_attributes || []
131:
132: assert readonly.include?(attribute.to_s),
133: (readonly.empty? ?
134: "#{klass} attribute #{attribute} is not read-only" :
135: "#{klass} is making #{readonly.to_a.to_sentence} read-only, but not #{attribute}.")
136: end
137: end
138: end
Ensures that the attribute cannot be set to the given values Requires an existing record
Options:
Example:
should_not_allow_values_for :isbn, "bad 1", "bad 2"
# File lib/shoulda/active_record_helpers.rb, line 150
150: def should_not_allow_values_for(attribute, *bad_values)
151: message = get_options!(bad_values, :message)
152: message ||= /invalid/
153: klass = model_class
154: bad_values.each do |v|
155: should "not allow #{attribute} to be set to #{v.inspect}" do
156: assert_bad_value(klass, attribute, v, message)
157: end
158: end
159: end
Ensure that the attribute is numeric Requires an existing record
Options:
Example:
should_only_allow_numeric_values_for :age
# File lib/shoulda/active_record_helpers.rb, line 338
338: def should_only_allow_numeric_values_for(*attributes)
339: message = get_options!(attributes, :message)
340: message ||= /number/
341: klass = model_class
342: attributes.each do |attribute|
343: attribute = attribute.to_sym
344: should "only allow numeric values for #{attribute}" do
345: assert_bad_value(klass, attribute, "abcd", message)
346: end
347: end
348: end
Ensures that the attribute cannot be set on mass update. Requires an existing record.
should_protect_attributes :password, :admin_flag
# File lib/shoulda/active_record_helpers.rb, line 100
100: def should_protect_attributes(*attributes)
101: get_options!(attributes)
102: klass = model_class
103:
104: attributes.each do |attribute|
105: attribute = attribute.to_sym
106: should "protect #{attribute} from mass updates" do
107: protected = klass.protected_attributes || []
108: accessible = klass.accessible_attributes || []
109:
110: assert protected.include?(attribute.to_s) || !accessible.include?(attribute.to_s),
111: (accessible.empty? ?
112: "#{klass} is protecting #{protected.to_a.to_sentence}, but not #{attribute}." :
113: "#{klass} has made #{attribute} accessible")
114: end
115: end
116: end
Ensures that the model cannot be saved if one of the attributes listed is not accepted.
Options:
Example:
should_require_acceptance_of :eula
# File lib/shoulda/active_record_helpers.rb, line 579
579: def should_require_acceptance_of(*attributes)
580: message = get_options!(attributes, :message)
581: message ||= /must be accepted/
582: klass = model_class
583:
584: attributes.each do |attribute|
585: should "require #{attribute} to be accepted" do
586: assert_bad_value(klass, attribute, false, message)
587: end
588: end
589: end
Ensures that the model cannot be saved if one of the attributes listed is not present.
Options:
Example:
should_require_attributes :name, :phone_number
# File lib/shoulda/active_record_helpers.rb, line 32
32: def should_require_attributes(*attributes)
33: message = get_options!(attributes, :message)
34: message ||= /blank/
35: klass = model_class
36:
37: attributes.each do |attribute|
38: should "require #{attribute} to be set" do
39: assert_bad_value(klass, attribute, nil, message)
40: end
41: end
42: end
Ensures that the model cannot be saved if one of the attributes listed is not unique. Requires an existing record
Options:
Examples:
should_require_unique_attributes :keyword, :username should_require_unique_attributes :name, :message => "O NOES! SOMEONE STOELED YER NAME!" should_require_unique_attributes :email, :scoped_to => :name should_require_unique_attributes :address, :scoped_to => [:first_name, :last_name]
# File lib/shoulda/active_record_helpers.rb, line 58
58: def should_require_unique_attributes(*attributes)
59: message, scope = get_options!(attributes, :message, :scoped_to)
60: scope = [*scope].compact
61: message ||= /taken/
62:
63: klass = model_class
64: attributes.each do |attribute|
65: attribute = attribute.to_sym
66: should "require unique value for #{attribute}#{" scoped to #{scope.join(', ')}" unless scope.blank?}" do
67: assert existing = klass.find(:first), "Can't find first #{klass}"
68: object = klass.new
69: existing_value = existing.send(attribute)
70:
71: if !scope.blank?
72: scope.each do |s|
73: assert_respond_to object, "#{s}=""#{s}=", "#{klass.name} doesn't seem to have a #{s} attribute."
74: object.send("#{s}=", existing.send(s))
75: end
76: end
77: assert_bad_value(object, attribute, existing_value, message)
78:
79: # Now test that the object is valid when changing the scoped attribute
80: # TODO: There is a chance that we could change the scoped field
81: # to a value that's already taken. An alternative implementation
82: # could actually find all values for scope and create a unique
83: # one.
84: if !scope.blank?
85: scope.each do |s|
86: # Assume the scope is a foreign key if the field is nil
87: object.send("#{s}=", existing.send(s).nil? ? 1 : existing.send(s).next)
88: assert_good_value(object, attribute, existing_value, message)
89: end
90: end
91: end
92: end
93: end