remarkable_activerecord 3.0.8 → 3.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. data/CHANGELOG +49 -10
  2. data/lib/remarkable_activerecord.rb +2 -1
  3. data/lib/remarkable_activerecord/describe.rb +84 -0
  4. data/lib/remarkable_activerecord/human_names.rb +2 -2
  5. data/lib/remarkable_activerecord/matchers/accept_nested_attributes_for_matcher.rb +18 -9
  6. data/lib/remarkable_activerecord/matchers/allow_mass_assignment_of_matcher.rb +2 -2
  7. data/lib/remarkable_activerecord/matchers/allow_values_for_matcher.rb +3 -3
  8. data/lib/remarkable_activerecord/matchers/association_matcher.rb +8 -8
  9. data/lib/remarkable_activerecord/matchers/have_column_matcher.rb +2 -2
  10. data/lib/remarkable_activerecord/matchers/have_default_scope_matcher.rb +7 -5
  11. data/lib/remarkable_activerecord/matchers/have_index_matcher.rb +11 -3
  12. data/lib/remarkable_activerecord/matchers/have_readonly_attributes_matcher.rb +2 -2
  13. data/lib/remarkable_activerecord/matchers/have_scope_matcher.rb +10 -10
  14. data/lib/remarkable_activerecord/matchers/validate_acceptance_of_matcher.rb +2 -2
  15. data/lib/remarkable_activerecord/matchers/validate_associated_matcher.rb +9 -5
  16. data/lib/remarkable_activerecord/matchers/validate_confirmation_of_matcher.rb +2 -2
  17. data/lib/remarkable_activerecord/matchers/validate_exclusion_of_matcher.rb +2 -2
  18. data/lib/remarkable_activerecord/matchers/validate_inclusion_of_matcher.rb +2 -2
  19. data/lib/remarkable_activerecord/matchers/validate_length_of_matcher.rb +12 -7
  20. data/lib/remarkable_activerecord/matchers/validate_numericality_of_matcher.rb +10 -5
  21. data/lib/remarkable_activerecord/matchers/validate_presence_of_matcher.rb +2 -2
  22. data/lib/remarkable_activerecord/matchers/validate_uniqueness_of_matcher.rb +41 -8
  23. data/locale/en.yml +9 -1
  24. data/spec/accept_nested_attributes_for_matcher_spec.rb +11 -0
  25. data/spec/association_matcher_spec.rb +29 -4
  26. data/spec/describe_spec.rb +57 -0
  27. data/spec/have_column_matcher_spec.rb +6 -1
  28. data/spec/have_scope_matcher_spec.rb +9 -2
  29. data/spec/spec_helper.rb +1 -1
  30. data/spec/validate_associated_matcher_spec.rb +5 -1
  31. data/spec/validate_length_of_matcher_spec.rb +7 -2
  32. data/spec/validate_numericality_of_matcher_spec.rb +8 -2
  33. data/spec/validate_uniqueness_of_matcher_spec.rb +25 -16
  34. metadata +5 -3
data/CHANGELOG CHANGED
@@ -1,10 +1,50 @@
1
+ * Allow validate_uniqueness_of to work when scopes are not stringfiable values.
2
+ You can now give timestamps, datetime, date and boolean as scopes. [#60]
3
+
4
+ * Allow subjects to be overwriten quickly (quick subjects):
5
+
6
+ describe Post
7
+ should_validate_presente_of :title
8
+
9
+ describe :published => true do
10
+ should_validate_presence_of :published_at
11
+ end
12
+ end
13
+
14
+ Is the same as:
15
+
16
+ describe Post
17
+ should_validate_presente_of :title
18
+
19
+ describe "when published is true" do
20
+ subject { Post.new(:published => true) }
21
+ should_validate_presence_of :published_at
22
+ end
23
+ end
24
+
25
+ And the string can be also localized using I18n. [#57]
26
+
27
+ [COMPATIBILITY] validate_associated no longer accepts a block to confgure the
28
+ builder:
29
+
30
+ should_validate_associated(:tasks){ |p| p.tasks.build(:captcha => 'i_am_a_robot') }
31
+
32
+ The right way to do this is by giving an option called builder and a proc:
33
+
34
+ should_validate_associated :tasks, :builder => proc{ |p| p.tasks.build(:captcha => 'i_am_a_robot') }
35
+
36
+ * validate_uniqueness_of and accept_nested_attributes_for now use the new
37
+ interpolation option {{sentence}} [#58]
38
+
1
39
  * Added accept_nested_attributes_for matcher [#39]
2
40
 
3
41
  * Added have_default_scope matcher [#38]
4
42
 
5
- * Allow :include, :join, :group and :having as quick accessors to have_scope matcher
43
+ * Allow :conditions, :include, :joins, :limit, :offset, :order, :select, :readonly,
44
+ :group, :having, :from, :lock as quick accessors to have_scope matcher
6
45
 
7
- * Allow all objects to be sent to have_scope (thanks to Szymon Nowak and Nolan Eakins) [#53]
46
+ * Allow all kind of objects to be sent to have_scope (including datetimes, arrays,
47
+ booleans and nil) (thanks to Szymon Nowak and Nolan Eakins) [#53]
8
48
 
9
49
  * Added support to sql options in association_matcher: select, conditions, include,
10
50
  group, having, order, limit and offset, plus finder_sql and counter_sql. [#48]
@@ -17,8 +57,8 @@
17
57
 
18
58
  # v3.0
19
59
 
20
- [ENHANCEMENT] Added more options to associations matcher. Previously it was
21
- handling just :dependent and :through options. Now it deals with:
60
+ * Added more options to associations matcher. Previously it was handling just
61
+ :dependent and :through options. Now it deals with:
22
62
 
23
63
  :through, :class_name, :foreign_key, :dependent, :join_table, :uniq,
24
64
  :readonly, :validate, :autosave, :counter_cache, :polymorphic
@@ -55,10 +95,9 @@ subject definitions on rspec. This was also fixed.
55
95
 
56
96
  # v2.x
57
97
 
58
- [ENHANCEMENT] Added associations, allow_mass_assignment, allow_values_for,
59
- have_column, have_index, have_scope, have_readonly_attributes,
60
- validate_acceptance_of, validate_associate, validate_confirmation_of,
61
- validate_exclusion_of, validate_inclusion_of, validate_length_of,
62
- validate_numericality_of, validate_presence_of and validate_uniqueness_of
63
- matchers.
98
+ * Added associations, allow_mass_assignment, allow_values_for, have_column,
99
+ have_index, have_scope, have_readonly_attributes, validate_acceptance_of,
100
+ validate_associated, validate_confirmation_of, validate_exclusion_of,
101
+ validate_inclusion_of, validate_length_of, validate_numericality_of,
102
+ validate_presence_of and validate_uniqueness_of matchers.
64
103
 
@@ -11,7 +11,8 @@ end
11
11
 
12
12
  # Load Remarkable ActiveRecord files
13
13
  dir = File.dirname(__FILE__)
14
- require File.join(dir, 'remarkable_activerecord', 'base')
14
+ require File.join(dir, 'remarkable_activerecord', 'base')
15
+ require File.join(dir, 'remarkable_activerecord', 'describe')
15
16
  require File.join(dir, 'remarkable_activerecord', 'human_names')
16
17
 
17
18
  # Add locale
@@ -0,0 +1,84 @@
1
+ module Remarkable
2
+ module ActiveRecord
3
+
4
+ def self.after_include(target)
5
+ target.class_inheritable_reader :default_subject_attributes
6
+ target.extend Describe
7
+ end
8
+
9
+ module Describe
10
+
11
+ # Overwrites describe to provide quick way to configure your subject:
12
+ #
13
+ # describe Post
14
+ # should_validate_presente_of :title
15
+ #
16
+ # describe :published => true do
17
+ # should_validate_presence_of :published_at
18
+ # end
19
+ # end
20
+ #
21
+ # This is the same as:
22
+ #
23
+ # describe Post
24
+ # should_validate_presente_of :title
25
+ #
26
+ # describe "when published is true" do
27
+ # subject { Post.new(:published => true) }
28
+ # should_validate_presence_of :published_at
29
+ # end
30
+ # end
31
+ #
32
+ # The string can be localized using I18n. An example yml file is:
33
+ #
34
+ # locale:
35
+ # remarkable:
36
+ # active_record:
37
+ # describe:
38
+ # each: "{{key}} is {{value}}"
39
+ # prepend: "when "
40
+ # connector: " and "
41
+ #
42
+ def describe(*args, &block)
43
+ if described_class && args.first.is_a?(Hash)
44
+ attributes = args.shift
45
+
46
+ connector = Remarkable.t "remarkable.active_record.describe.connector", :default => " and "
47
+
48
+ description = if self.default_subject_attributes.blank?
49
+ Remarkable.t("remarkable.active_record.describe.prepend", :default => "when ")
50
+ else
51
+ connector.lstrip
52
+ end
53
+
54
+ pieces = []
55
+ attributes.each do |key, value|
56
+ translated_key = if described_class.respond_to?(:human_attribute_name)
57
+ described_class.human_attribute_name(key, :locale => Remarkable.locale)
58
+ else
59
+ key.to_s.humanize
60
+ end
61
+
62
+ pieces << Remarkable.t("remarkable.active_record.describe.each",
63
+ :default => "{{key}} is {{value}}",
64
+ :key => key, :value => value.inspect)
65
+ end
66
+
67
+ description << pieces.join(connector)
68
+ args.unshift(description)
69
+
70
+ # Creates an example group, send the method and eval the given block.
71
+ #
72
+ example_group = super(*args) do
73
+ write_inheritable_hash(:default_subject_attributes, attributes)
74
+ subject { self.class.described_class.new(self.class.default_subject_attributes) }
75
+ instance_eval(&block)
76
+ end
77
+ else
78
+ super(*args, &block)
79
+ end
80
+ end
81
+
82
+ end
83
+ end
84
+ end
@@ -7,7 +7,7 @@ if defined?(Spec)
7
7
  #
8
8
  def self.build_description_with_i18n(*args)
9
9
  args.inject("") do |description, arg|
10
- arg = if RAILS_I18N && arg.respond_to?(:human_name)
10
+ arg = if arg.respond_to?(:human_name)
11
11
  arg.human_name(:locale => Remarkable.locale)
12
12
  else
13
13
  arg.to_s
@@ -26,7 +26,7 @@ if defined?(Spec)
26
26
 
27
27
  # This is for rspec >= 1.2.0.
28
28
  #
29
- def build_description_from(*args)
29
+ def self.build_description_from(*args)
30
30
  text = ExampleGroupMethods.build_description_with_i18n(*args)
31
31
  text == "" ? nil : text
32
32
  end
@@ -96,24 +96,33 @@ module Remarkable
96
96
  #
97
97
  # class Projects < ActiveRecord::Base
98
98
  # has_many :tasks
99
- # accepts_nested_attributes_for :tasks, :reject_if => proc { |a| a[:name].blank? }
99
+ # accepts_nested_attributes_for :tasks, :reject_if => proc { |a| a[:title].blank? }
100
100
  # end
101
101
  #
102
102
  # You can have the following specs:
103
103
  #
104
- # should_accept_nested_attributes_for :tasks, :reject => { :name => '' } # Passes
105
- # should_accept_nested_attributes_for :tasks, :accept => { :name => 'My task' } # Passes
104
+ # should_accept_nested_attributes_for :tasks, :reject => { :title => '' } # Passes
105
+ # should_accept_nested_attributes_for :tasks, :accept => { :title => 'My task' } # Passes
106
106
  #
107
- # should_accept_nested_attributes_for :tasks, :accept => { :name => 'My task' },
108
- # :reject => { :name => '' } # Passes
107
+ # should_accept_nested_attributes_for :tasks, :accept => { :title => 'My task' },
108
+ # :reject => { :title => '' } # Passes
109
109
  #
110
- # should_accept_nested_attributes_for :tasks, :accept => { :name => '' } # Fail
111
- # should_accept_nested_attributes_for :tasks, :reject => { :name => 'My task' } # Fail
110
+ # should_accept_nested_attributes_for :tasks, :accept => { :title => '' } # Fail
111
+ # should_accept_nested_attributes_for :tasks, :reject => { :title => 'My task' } # Fail
112
112
  #
113
113
  # You can also give arrays to :accept and :reject to verify multiple attributes.
114
+ # In such cases the block syntax is more recommended for readability:
115
+ #
116
+ # should_accept_nested_attributes_for :tasks do
117
+ # m.allow_destroy(false)
118
+ # m.accept :title => 'My task'
119
+ # m.accept :title => 'Another task'
120
+ # m.reject :title => nil
121
+ # m.reject :title => ''
122
+ # end
114
123
  #
115
- def accept_nested_attributes_for(*args)
116
- AcceptNestedAttributesForMatcher.new(*args).spec(self)
124
+ def accept_nested_attributes_for(*args, &block)
125
+ AcceptNestedAttributesForMatcher.new(*args, &block).spec(self)
117
126
  end
118
127
 
119
128
  end
@@ -26,8 +26,8 @@ module Remarkable
26
26
  # should_allow_mass_assignment_of :email, :name
27
27
  # it { should allow_mass_assignment_of(:email, :name) }
28
28
  #
29
- def allow_mass_assignment_of(*attributes)
30
- AllowMassAssignmentOfMatcher.new(*attributes).spec(self)
29
+ def allow_mass_assignment_of(*attributes, &block)
30
+ AllowMassAssignmentOfMatcher.new(*attributes, &block).spec(self)
31
31
  end
32
32
  end
33
33
  end
@@ -53,7 +53,7 @@ module Remarkable
53
53
  options = if @in_range
54
54
  { :in => (@options[:in].first..@options[:in].last).inspect }
55
55
  elsif @options[:in].is_a?(Array)
56
- { :in => @options[:in].map(&:inspect).to_sentence }
56
+ { :in => @options[:in].map{|i| i.inspect}.to_sentence }
57
57
  else
58
58
  { :in => @options[:in].inspect }
59
59
  end
@@ -83,9 +83,9 @@ module Remarkable
83
83
  # it { should allow_values_for(:isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0") }
84
84
  # it { should_not allow_values_for(:isbn, "bad 1", "bad 2") }
85
85
  #
86
- def allow_values_for(attribute, *args)
86
+ def allow_values_for(attribute, *args, &block)
87
87
  options = args.extract_options!
88
- AllowValuesForMatcher.new(attribute, options.merge!(:in => args)).spec(self)
88
+ AllowValuesForMatcher.new(attribute, options.merge!(:in => args), &block).spec(self)
89
89
  end
90
90
  alias :validate_format_of :allow_values_for
91
91
 
@@ -150,8 +150,8 @@ module Remarkable
150
150
  # should_belong_to :parent, :polymorphic => true
151
151
  # it { should belong_to(:parent) }
152
152
  #
153
- def belong_to(*associations)
154
- AssociationMatcher.new(:belongs_to, *associations).spec(self)
153
+ def belong_to(*associations, &block)
154
+ AssociationMatcher.new(:belongs_to, *associations, &block).spec(self)
155
155
  end
156
156
 
157
157
  # Ensures that the has_and_belongs_to_many relationship exists, if the join
@@ -175,8 +175,8 @@ module Remarkable
175
175
  # should_have_and_belong_to_many :posts, :cars
176
176
  # it{ should have_and_belong_to_many :posts, :cars }
177
177
  #
178
- def have_and_belong_to_many(*associations)
179
- AssociationMatcher.new(:has_and_belongs_to_many, *associations).spec(self)
178
+ def have_and_belong_to_many(*associations, &block)
179
+ AssociationMatcher.new(:has_and_belongs_to_many, *associations, &block).spec(self)
180
180
  end
181
181
 
182
182
  # Ensures that the has_many relationship exists. Will also test that the
@@ -211,8 +211,8 @@ module Remarkable
211
211
  # it{ should have_many(:enemies, :through => :friends) }
212
212
  # it{ should have_many(:enemies, :dependent => :destroy) }
213
213
  #
214
- def have_many(*associations)
215
- AssociationMatcher.new(:has_many, *associations).spec(self)
214
+ def have_many(*associations, &block)
215
+ AssociationMatcher.new(:has_many, *associations, &block).spec(self)
216
216
  end
217
217
 
218
218
  # Ensures that the has_many relationship exists. Will also test that the
@@ -240,8 +240,8 @@ module Remarkable
240
240
  # should_have_one :universe
241
241
  # it{ should have_one(:universe) }
242
242
  #
243
- def have_one(*associations)
244
- AssociationMatcher.new(:has_one, *associations).spec(self)
243
+ def have_one(*associations, &block)
244
+ AssociationMatcher.new(:has_one, *associations, &block).spec(self)
245
245
  end
246
246
 
247
247
  end
@@ -56,8 +56,8 @@ module Remarkable
56
56
  # it { should have_column(:name, :type => :string) }
57
57
  # it { should have_column(:name).type(:string) }
58
58
  #
59
- def have_column(*args)
60
- HaveColumnMatcher.new(*args).spec(self)
59
+ def have_column(*args, &block)
60
+ HaveColumnMatcher.new(*args, &block).spec(self)
61
61
  end
62
62
  alias :have_columns :have_column
63
63
  alias :have_db_column :have_column
@@ -5,7 +5,8 @@ module Remarkable
5
5
  arguments
6
6
  assertions :options_match?
7
7
 
8
- optionals :select, :conditions, :join, :include, :group, :having, :order, :limit, :offset
8
+ optionals :conditions, :include, :joins, :limit, :offset, :order, :select,
9
+ :readonly, :group, :having, :from, :lock
9
10
 
10
11
  protected
11
12
 
@@ -32,8 +33,9 @@ module Remarkable
32
33
  #
33
34
  # == Options
34
35
  #
35
- # All options that the default scope would pass on to find: select, conditions,
36
- # join, include, group, having, order, limit e offset.
36
+ # All options that the default scope would pass on to find: :conditions,
37
+ # :include, :joins, :limit, :offset, :order, :select, :readonly, :group,
38
+ # :having, :from, :lock.
37
39
  #
38
40
  # == Examples
39
41
  #
@@ -58,8 +60,8 @@ module Remarkable
58
60
  # should_have_default_scope :conditions => { :published => true,
59
61
  # :visible => true } # Fails
60
62
  #
61
- def have_default_scope(*args)
62
- HaveDefaultScopeMatcher.new(*args).spec(self)
63
+ def have_default_scope(*args, &block)
64
+ HaveDefaultScopeMatcher.new(*args, &block).spec(self)
63
65
  end
64
66
 
65
67
  end
@@ -43,10 +43,18 @@ module Remarkable
43
43
  # == Examples
44
44
  #
45
45
  # it { should have_index(:ssn).unique(true) }
46
- # it { should have_index([:name, :email]).unique(true) }
46
+ # it { should have_index([:name, :email]).unique(true) }
47
+ #
48
+ # should_have_index :ssn, :unique => true, :limit => 9, :null => false
49
+ #
50
+ # should_have_index :ssn do |m|
51
+ # m.unique
52
+ # m.limit = 9
53
+ # m.null = false
54
+ # end
47
55
  #
48
- def have_index(*args)
49
- HaveIndexMatcher.new(*args).spec(self)
56
+ def have_index(*args, &block)
57
+ HaveIndexMatcher.new(*args, &block).spec(self)
50
58
  end
51
59
  alias :have_indices :have_index
52
60
  alias :have_db_index :have_index
@@ -20,8 +20,8 @@ module Remarkable
20
20
  #
21
21
  # it { should have_readonly_attributes(:password, :admin_flag) }
22
22
  #
23
- def have_readonly_attributes(*attributes)
24
- HaveReadonlyAttributesMatcher.new(*attributes).spec(self)
23
+ def have_readonly_attributes(*attributes, &block)
24
+ HaveReadonlyAttributesMatcher.new(*attributes, &block).spec(self)
25
25
  end
26
26
  alias :have_readonly_attribute :have_readonly_attributes
27
27
 
@@ -6,17 +6,15 @@ module Remarkable
6
6
  assertions :is_scope?, :options_match?
7
7
 
8
8
  optionals :with, :splat => true
9
- optionals :select, :conditions, :join, :include, :group, :having, :order, :limit, :offset
9
+ optionals :conditions, :include, :joins, :limit, :offset, :order, :select,
10
+ :readonly, :group, :having, :from, :lock
10
11
 
11
12
  protected
12
13
 
13
14
  def is_scope?
14
- @scope_object = if @options[:with]
15
- if @options[:with].is_a?(Array)
16
- subject_class.send(@scope_name, *@options[:with])
17
- else
18
- subject_class.send(@scope_name, @options[:with])
19
- end
15
+ @scope_object = if @options.key?(:with)
16
+ @options[:with] = [ @options[:with] ] unless Array === @options[:with]
17
+ subject_class.send(@scope_name, *@options[:with])
20
18
  else
21
19
  subject_class.send(@scope_name)
22
20
  end
@@ -43,7 +41,9 @@ module Remarkable
43
41
  #
44
42
  # * <tt>with</tt> - Options to be sent to the named scope
45
43
  #
46
- # And all other options that the named scope would pass on to find.
44
+ # All options that the named scope would pass on to find: :conditions,
45
+ # :include, :joins, :limit, :offset, :order, :select, :readonly, :group,
46
+ # :having, :from, :lock.
47
47
  #
48
48
  # == Examples
49
49
  #
@@ -75,8 +75,8 @@ module Remarkable
75
75
  # scoped(:limit => c)
76
76
  # end
77
77
  #
78
- def have_scope(*args)
79
- HaveScopeMatcher.new(*args).spec(self)
78
+ def have_scope(*args, &block)
79
+ HaveScopeMatcher.new(*args, &block).spec(self)
80
80
  end
81
81
  alias :have_named_scope :have_scope
82
82
 
@@ -41,8 +41,8 @@ module Remarkable
41
41
  # it { should validate_acceptance_of(:eula, :terms) }
42
42
  # it { should validate_acceptance_of(:eula, :terms, :accept => true) }
43
43
  #
44
- def validate_acceptance_of(*attributes)
45
- ValidateAcceptanceOfMatcher.new(*attributes).spec(self)
44
+ def validate_acceptance_of(*attributes, &block)
45
+ ValidateAcceptanceOfMatcher.new(*attributes, &block).spec(self)
46
46
  end
47
47
 
48
48
  end
@@ -2,8 +2,10 @@ module Remarkable
2
2
  module ActiveRecord
3
3
  module Matchers
4
4
  class ValidateAssociatedMatcher < Remarkable::ActiveRecord::Base #:nodoc:
5
- arguments :collection => :associations, :as => :association, :block => :block
6
- optional :message, :builder
5
+ arguments :collection => :associations, :as => :association, :block => true
6
+
7
+ optional :message
8
+ optional :builder, :block => true
7
9
 
8
10
  collection_assertions :find_association?, :is_valid?
9
11
  default_options :message => :invalid
@@ -84,11 +86,13 @@ module Remarkable
84
86
  # == Examples
85
87
  #
86
88
  # should_validate_associated :tasks
87
- # should_validate_associated(:tasks){ |p| p.tasks.build(:captcha => 'i_am_a_bot') }
88
- # should_validate_associated :tasks, :builder => proc{ |p| p.tasks.build(:captcha => 'i_am_a_bot') }
89
+ # should_validate_associated :tasks, :builder => proc{ |p| p.tasks.build(:captcha => 'i_am_a_bot') }
90
+ #
91
+ # should_validate_associated :tasks do |m|
92
+ # m.builder { |p| p.tasks.build(:captcha => 'i_am_a_bot') }
93
+ # end
89
94
  #
90
95
  # it { should validate_associated(:tasks) }
91
- # it { should validate_associated(:tasks){ |p| p.tasks.build(:captcha => 'i_am_a_bot') } }
92
96
  # it { should validate_associated(:tasks, :builder => proc{ |p| p.tasks.build(:captcha => 'i_am_a_bot') }) }
93
97
  #
94
98
  def validate_associated(*args, &block)
@@ -35,8 +35,8 @@ module Remarkable
35
35
  #
36
36
  # it { should validate_confirmation_of(:email, :password) }
37
37
  #
38
- def validate_confirmation_of(*attributes)
39
- ValidateConfirmationOfMatcher.new(*attributes).spec(self)
38
+ def validate_confirmation_of(*attributes, &block)
39
+ ValidateConfirmationOfMatcher.new(*attributes, &block).spec(self)
40
40
  end
41
41
 
42
42
  end
@@ -47,8 +47,8 @@ module Remarkable
47
47
  # should_validate_exclusion_of :username, :in => ["admin", "user"]
48
48
  # should_validate_exclusion_of :age, :in => 30..60
49
49
  #
50
- def validate_exclusion_of(*args)
51
- ValidateExclusionOfMatcher.new(*args).spec(self)
50
+ def validate_exclusion_of(*args, &block)
51
+ ValidateExclusionOfMatcher.new(*args, &block).spec(self)
52
52
  end
53
53
 
54
54
  end
@@ -47,8 +47,8 @@ module Remarkable
47
47
  # it { should validate_inclusion_of(:size, :in => ["S", "M", "L", "XL"]) }
48
48
  # it { should validate_inclusion_of(:age, :in => 18..100) }
49
49
  #
50
- def validate_inclusion_of(*args)
51
- ValidateInclusionOfMatcher.new(*args).spec(self)
50
+ def validate_inclusion_of(*args, &block)
51
+ ValidateInclusionOfMatcher.new(*args, &block).spec(self)
52
52
  end
53
53
 
54
54
  end
@@ -105,18 +105,23 @@ module Remarkable
105
105
  #
106
106
  # == Examples
107
107
  #
108
- # should_validate_length_of :password, :within => 6..20
109
- # should_validate_length_of :password, :maximum => 20
110
- # should_validate_length_of :password, :minimum => 6
111
- # should_validate_length_of :age, :is => 18
112
- #
113
108
  # it { should validate_length_of(:password).within(6..20) }
114
109
  # it { should validate_length_of(:password).maximum(20) }
115
110
  # it { should validate_length_of(:password).minimum(6) }
116
111
  # it { should validate_length_of(:age).is(18) }
112
+ #
113
+ # should_validate_length_of :password, :within => 6..20
114
+ # should_validate_length_of :password, :maximum => 20
115
+ # should_validate_length_of :password, :minimum => 6
116
+ # should_validate_length_of :age, :is => 18
117
+ #
118
+ # should_validate_length_of :password do |m|
119
+ # m.minimum 6
120
+ # m.maximum 20
121
+ # end
117
122
  #
118
- def validate_length_of(*attributes)
119
- ValidateLengthOfMatcher.new(*attributes).spec(self)
123
+ def validate_length_of(*attributes, &block)
124
+ ValidateLengthOfMatcher.new(*attributes, &block).spec(self)
120
125
  end
121
126
  end
122
127
  end
@@ -165,17 +165,22 @@ module Remarkable
165
165
  #
166
166
  # == Examples
167
167
  #
168
- # should_validate_numericality_of :age, :price
169
- # should_validate_numericality_of :price, :only_integer => false, :greater_than => 10
170
- #
171
168
  # it { should validate_numericality_of(:age).odd }
172
169
  # it { should validate_numericality_of(:age).even }
173
170
  # it { should validate_numericality_of(:age).only_integer }
174
171
  # it { should validate_numericality_of(:age, :odd => true) }
175
172
  # it { should validate_numericality_of(:age, :even => true) }
173
+ #
174
+ # should_validate_numericality_of :age, :price
175
+ # should_validate_numericality_of :price, :only_integer => false, :greater_than => 10
176
+ #
177
+ # should_validate_numericality_of :price do |m|
178
+ # m.only_integer = false
179
+ # m.greater_than = 10
180
+ # end
176
181
  #
177
- def validate_numericality_of(*attributes)
178
- ValidateNumericalityOfMatcher.new(*attributes).spec(self)
182
+ def validate_numericality_of(*attributes, &block)
183
+ ValidateNumericalityOfMatcher.new(*attributes, &block).spec(self)
179
184
  end
180
185
 
181
186
  end
@@ -56,8 +56,8 @@ module Remarkable
56
56
  # should_validate_presence_of :name, :phone_number
57
57
  # it { should validate_presence_of(:name, :phone_number) }
58
58
  #
59
- def validate_presence_of(*args)
60
- ValidatePresenceOfMatcher.new(*args).spec(self)
59
+ def validate_presence_of(*args, &block)
60
+ ValidatePresenceOfMatcher.new(*args, &block).spec(self)
61
61
  end
62
62
  end
63
63
  end
@@ -134,11 +134,36 @@ module Remarkable
134
134
 
135
135
  super
136
136
  end
137
-
138
- # Returns a value to be used as new scope. It does a range query in the
139
- # database and tries to return a new value which does not belong to it.
137
+
138
+ # Returns a value to be used as new scope. It deals with four different
139
+ # cases: date, time, boolean and stringfiable (everything that can be
140
+ # converted to a string and the next value makes sense)
141
+ #
142
+ def new_value_for_scope(scope)
143
+ column_type = if @existing.respond_to?(:column_for_attribute)
144
+ @existing.column_for_attribute(scope)
145
+ else
146
+ nil
147
+ end
148
+
149
+ case column_type.type
150
+ when :int, :integer, :float, :decimal
151
+ new_value_for_stringfiable_scope(scope)
152
+ when :datetime, :timestamp, :time
153
+ Time.now + 10000
154
+ when :date
155
+ Date.today + 100
156
+ when :boolean
157
+ !@existing.send(scope)
158
+ else
159
+ new_value_for_stringfiable_scope(scope)
160
+ end
161
+ end
162
+
163
+ # Returns a value to be used as scope by generating a range of values
164
+ # and searching for them in the database.
140
165
  #
141
- def new_value_for_scope(scope)
166
+ def new_value_for_stringfiable_scope(scope)
142
167
  values = [(@existing.send(scope) || 999).next.to_s]
143
168
 
144
169
  # Generate a range of values to search in the database
@@ -188,12 +213,20 @@ module Remarkable
188
213
  # == Examples
189
214
  #
190
215
  # it { should validate_uniqueness_of(:keyword, :username) }
191
- # it { should validate_uniqueness_of(:name, :message => "O NOES! SOMEONE STOELED YER NAME!") }
192
216
  # it { should validate_uniqueness_of(:email, :scope => :name, :case_sensitive => false) }
193
- # it { should validate_uniqueness_of(:address, :scope => [:first_name, :last_name]) }
217
+ # it { should validate_uniqueness_of(:address, :scope => [:first_name, :last_name]) }
218
+ #
219
+ # should_validate_uniqueness_of :keyword, :username
220
+ # should_validate_uniqueness_of :email, :scope => :name, :case_sensitive => false
221
+ # should_validate_uniqueness_of :address, :scope => [:first_name, :last_name]
222
+ #
223
+ # should_validate_uniqueness_of :email do |m|
224
+ # m.scope = name
225
+ # m.case_sensitive = false
226
+ # end
194
227
  #
195
- def validate_uniqueness_of(*attributes)
196
- ValidateUniquenessOfMatcher.new(*attributes).spec(self)
228
+ def validate_uniqueness_of(*attributes, &block)
229
+ ValidateUniquenessOfMatcher.new(*attributes, &block).spec(self)
197
230
  end
198
231
  end
199
232
  end
@@ -1,6 +1,10 @@
1
1
  en:
2
2
  remarkable:
3
3
  active_record:
4
+ describe:
5
+ each: "{{key}} is {{value}}"
6
+ prepend: "when "
7
+ connector: " and "
4
8
  expectations:
5
9
  allow_nil: "{{subject_name}} to {{not}}allow nil values for {{attribute}}"
6
10
  allow_blank: "{{subject_name}} to {{not}}allow blank values for {{attribute}}"
@@ -25,6 +29,10 @@ en:
25
29
  allow_destroy:
26
30
  positive: "allowing destroy"
27
31
  negative: "not allowing destroy"
32
+ accept:
33
+ positive: "accepting {{sentence}}"
34
+ reject:
35
+ positive: "rejecting {{sentence}}"
28
36
 
29
37
  allow_values_for:
30
38
  description: "allow {{in}} as values for {{attributes}}"
@@ -239,7 +247,7 @@ en:
239
247
  valid_with_new_scope: "{{subject_name}} to be valid when {{attribute}} scope ({{method}}) change"
240
248
  optionals:
241
249
  scope:
242
- positive: "scoped to {{inspect}}"
250
+ positive: "scoped to {{sentence}}"
243
251
  case_sensitive:
244
252
  positive: "case sensitive"
245
253
  negative: "case insensitive"
@@ -26,6 +26,10 @@ if RAILS_VERSION == '2.3.2'
26
26
 
27
27
  matcher.allow_destroy
28
28
  matcher.description.should == 'accept nested attributes for category allowing destroy'
29
+
30
+ matcher.accept(:name => 'jose')
31
+ matcher.accept(:name => 'maria')
32
+ matcher.description.should == 'accept nested attributes for category allowing destroy and accepting {:name=>"jose"} and {:name=>"maria"}'
29
33
  end
30
34
 
31
35
  it 'should set association_match? message' do
@@ -108,6 +112,13 @@ if RAILS_VERSION == '2.3.2'
108
112
  should_accept_nested_attributes_for :category, :accept => [ { :name => 'Jose' }, { :name => 'Maria' } ]
109
113
  should_accept_nested_attributes_for :category, :reject => [ { :name => '' } ]
110
114
 
115
+ should_accept_nested_attributes_for :category do |m|
116
+ m.allow_destroy
117
+ m.accept :name => "Jose"
118
+ m.accept :name => "Maria"
119
+ m.reject :name => ""
120
+ end
121
+
111
122
  should_not_accept_nested_attributes_for :nothing
112
123
  should_not_accept_nested_attributes_for :labels
113
124
  should_not_accept_nested_attributes_for :tags
@@ -149,7 +149,14 @@ describe 'association_matcher' do
149
149
  should_belong_to :company, :readonly => true
150
150
  should_belong_to :company, :validate => true
151
151
  should_belong_to :company, :class_name => "Company"
152
- should_belong_to :company, :foreign_key => :company_id
152
+ should_belong_to :company, :foreign_key => :company_id
153
+
154
+ should_belong_to :company do |m|
155
+ m.readonly
156
+ m.validate
157
+ m.class_name = "Company"
158
+ m.foreign_key = :company_id
159
+ end
153
160
 
154
161
  should_not_belong_to :project
155
162
  should_not_have_one :company
@@ -291,7 +298,14 @@ describe 'association_matcher' do
291
298
  should_have_and_belong_to_many :labels, :readonly => true
292
299
  should_have_and_belong_to_many :labels, :validate => true
293
300
  should_have_and_belong_to_many :labels, :class_name => "Label"
294
- should_have_and_belong_to_many :labels, :foreign_key => :project_id
301
+ should_have_and_belong_to_many :labels, :foreign_key => :project_id
302
+
303
+ should_have_and_belong_to_many :labels do |m|
304
+ m.readonly
305
+ m.validate
306
+ m.class_name "Label"
307
+ m.foreign_key :project_id
308
+ end
295
309
 
296
310
  should_not_have_and_belong_to_many :companies
297
311
  should_not_have_one :label
@@ -455,7 +469,13 @@ describe 'association_matcher' do
455
469
  should_have_many :tasks
456
470
  should_have_many :tasks, :readonly => true
457
471
  should_have_many :tasks, :validate => true
458
- should_have_many :tasks, :through => :project_tasks
472
+ should_have_many :tasks, :through => :project_tasks
473
+
474
+ should_have_many :tasks do |m|
475
+ m.readonly
476
+ m.validate
477
+ m.through = :project_tasks
478
+ end
459
479
 
460
480
  should_not_have_many :tasks, :readonly => false
461
481
  should_not_have_many :tasks, :validate => false
@@ -605,7 +625,12 @@ describe 'association_matcher' do
605
625
 
606
626
  should_have_one :manager
607
627
  should_have_one :manager, :validate => true
608
- should_have_one :manager, :through => :project_managers
628
+ should_have_one :manager, :through => :project_managers
629
+
630
+ should_have_one :manager do |m|
631
+ m.validate
632
+ m.through = :project_managers
633
+ end
609
634
 
610
635
  should_not_have_one :manager, :validate => false
611
636
  should_not_have_one :manager, :through => :another_thing
@@ -0,0 +1,57 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ RAILS_I18n = true
4
+
5
+ class Post
6
+ attr_accessor :title, :published, :public
7
+
8
+ def initialize(attributes={})
9
+ attributes.each do |key, value|
10
+ send(:"#{key}=", value)
11
+ end
12
+ end
13
+
14
+ def self.human_name(*args)
15
+ "MyPost"
16
+ end
17
+ end unless defined?(Post)
18
+
19
+ describe Post do
20
+ it "should use human name on description" do
21
+ self.class.description.should == "MyPost"
22
+ end
23
+
24
+ describe :published => true do
25
+ it "should set the subject with published equals to true" do
26
+ subject.published.should be_true
27
+ end
28
+
29
+ it "should generate a readable description" do
30
+ self.class.description.should == "MyPost when published is true"
31
+ end
32
+
33
+ describe :public => false do
34
+ it "should nest subject attributes" do
35
+ subject.published.should be_true
36
+ subject.public.should be_false
37
+ end
38
+
39
+ it "should nest descriptions" do
40
+ self.class.description.should == "MyPost when published is true and public is false"
41
+ end
42
+ end
43
+ end
44
+
45
+ describe :published => true, :public => false do
46
+ it "should set the subject with published equals to true and public equals to false" do
47
+ subject.published.should be_true
48
+ subject.public.should be_false
49
+ end
50
+
51
+ it "should include both published and public in descriptions" do
52
+ self.class.description.should match(/MyPost/)
53
+ self.class.description.should match(/public is false/)
54
+ self.class.description.should match(/published is true/)
55
+ end
56
+ end
57
+ end
@@ -61,7 +61,12 @@ describe 'have_column_matcher' do
61
61
  should_have_column :email, :limit => 255
62
62
  should_have_column :email, :default => 'jose.valim@gmail.com'
63
63
  should_have_column :price, :precision => 10
64
- should_have_column :price, :precision => 10, :scale => 2
64
+ should_have_column :price, :precision => 10, :scale => 2
65
+
66
+ should_have_column :price do |m|
67
+ m.scale = 2
68
+ m.precision = 10
69
+ end
65
70
 
66
71
  should_not_have_column :name, :null => false
67
72
  should_not_have_column :email, :limit => 400
@@ -49,7 +49,8 @@ describe 'have_scope' do
49
49
  it { should have_scope(:recent, :order => 'created_at DESC') }
50
50
 
51
51
  it { should have_scope(:latest).with(10).limit(10) }
52
- it { should have_scope(:beginning).with(10).offset(10) }
52
+ it { should have_scope(:beginning).with(10).offset(10) }
53
+ it { should have_scope(:since).with(false).conditions(["created_at > ?", false]) }
53
54
  it { should have_scope(:since).with(Time.at(0)).conditions(["created_at > ?", Time.at(0)]) }
54
55
  it { should have_scope(:between).with(2, 10).conditions(["created_at > ? and created_at < ?", 2, 10]) }
55
56
 
@@ -66,8 +67,14 @@ describe 'have_scope' do
66
67
 
67
68
  should_have_scope :latest, :with => 10, :limit => 10
68
69
  should_have_scope :beginning, :with => 10, :offset => 10
70
+ should_have_scope :since, :with => false, :conditions => ["created_at > ?", false]
69
71
  should_have_scope :since, :with => Time.at(0), :conditions => ["created_at > ?", Time.at(0)]
70
- should_have_scope :between, :with => [ 2, 10 ], :conditions => [ "created_at > ? and created_at < ?", 2, 10 ]
72
+ should_have_scope :between, :with => [ 2, 10 ], :conditions => [ "created_at > ? and created_at < ?", 2, 10 ]
73
+
74
+ should_have_scope :between do |m|
75
+ m.with(2, 10)
76
+ m.conditions([ "created_at > ? and created_at < ?", 2, 10 ])
77
+ end
71
78
 
72
79
  should_not_have_scope :null
73
80
  should_not_have_scope :latest, :with => 5, :limit => 10
@@ -1,7 +1,7 @@
1
1
  require 'rubygems'
2
2
  require 'ruby-debug'
3
3
 
4
- RAILS_VERSION = ENV['RAILS_VERSION'] || '=2.2.2'
4
+ RAILS_VERSION = ENV['RAILS_VERSION'] || '2.3.2'
5
5
 
6
6
  gem 'activesupport', RAILS_VERSION
7
7
  require 'active_support'
@@ -110,7 +110,11 @@ describe 'validate_associated' do
110
110
 
111
111
  describe 'has_many with builder' do
112
112
  before(:each){ define_and_validate(:has_many, :tasks, :with_builder => true) }
113
- should_validate_associated(:tasks){ |p| p.tasks.build(:name => true) }
113
+ should_validate_associated :tasks, :builder => proc{ |p| p.tasks.build(:name => true) }
114
+
115
+ should_validate_associated :tasks do |m|
116
+ m.builder { |p| p.tasks.build(:name => true) }
117
+ end
114
118
  end
115
119
 
116
120
  describe 'has_and_belongs_to_many with skip validation' do
@@ -155,11 +155,16 @@ describe 'validate_length_of' do
155
155
  before(:each) { define_and_validate }
156
156
 
157
157
  should_validate_length_of :size, :in => 3..5
158
- should_validate_length_of :size, :within => 3..5
158
+ should_validate_length_of :size, :within => 3..5
159
+
159
160
  should_not_validate_length_of :size, :within => 2..5
160
161
  should_not_validate_length_of :size, :within => 4..5
161
162
  should_not_validate_length_of :size, :within => 3..4
162
163
  should_not_validate_length_of :size, :within => 3..6
163
- should_not_validate_length_of :category, :in => 3..5
164
+ should_not_validate_length_of :category, :in => 3..5
165
+
166
+ should_validate_length_of :size do |m|
167
+ m.in = 3..5
168
+ end
164
169
  end
165
170
  end
@@ -172,9 +172,15 @@ describe 'validate_numericality_of' do
172
172
  should_validate_numericality_of :price
173
173
  should_validate_numericality_of :price, :less_than => 100000
174
174
  should_validate_numericality_of :price, :greater_than => 9999
175
- should_validate_numericality_of :price, :less_than => 100000, :greater_than => 999
175
+ should_validate_numericality_of :price, :less_than => 100000, :greater_than => 999
176
+
176
177
  should_not_validate_numericality_of :size
177
178
  should_not_validate_numericality_of :price, :less_than => 55555
178
- should_not_validate_numericality_of :price, :greater_than => 55555
179
+ should_not_validate_numericality_of :price, :greater_than => 55555
180
+
181
+ should_validate_numericality_of :price do |m|
182
+ m.less_than 100000
183
+ m.greater_than 999
184
+ end
179
185
  end
180
186
  end
@@ -5,12 +5,12 @@ describe 'validate_uniqueness_of' do
5
5
 
6
6
  # Defines a model, create a validation and returns a raw matcher
7
7
  def define_and_validate(options={})
8
- @model = define_model :user, :username => :string, :email => :integer, :access_code => :string do
8
+ @model = define_model :user, :username => :string, :email => :string, :public => :boolean, :deleted_at => :timestamp do
9
9
  validates_uniqueness_of :username, options
10
10
  end
11
11
 
12
12
  # Create a model
13
- User.create(:username => 'jose')
13
+ User.create(:username => 'jose', :deleted_at => 1.day.ago, :public => false)
14
14
 
15
15
  validate_uniqueness_of(:username)
16
16
  end
@@ -21,23 +21,25 @@ describe 'validate_uniqueness_of' do
21
21
  it 'should contain a description' do
22
22
  @matcher.description.should == 'require unique values for username'
23
23
 
24
- @matcher.scope(:email)
25
- @matcher.description.should == 'require unique values for username scoped to [:email]'
26
-
27
- @matcher.scope(:email, :access_code)
28
- @matcher.description.should == 'require unique values for username scoped to [:email, :access_code]'
29
-
30
24
  @matcher.case_sensitive
31
- @matcher.description.should == 'require unique values for username scoped to [:email, :access_code] and case sensitive'
25
+ @matcher.description.should == 'require unique values for username case sensitive'
32
26
 
33
27
  @matcher.case_sensitive(false)
34
- @matcher.description.should == 'require unique values for username scoped to [:email, :access_code] and case insensitive'
28
+ @matcher.description.should == 'require unique values for username case insensitive'
35
29
 
36
30
  @matcher.allow_nil
37
- @matcher.description.should == 'require unique values for username scoped to [:email, :access_code], case insensitive, and allowing nil values'
31
+ @matcher.description.should == 'require unique values for username case insensitive and allowing nil values'
38
32
 
39
33
  @matcher.allow_blank(false)
40
- @matcher.description.should == 'require unique values for username scoped to [:email, :access_code], case insensitive, allowing nil values, and not allowing blank values'
34
+ @matcher.description.should == 'require unique values for username case insensitive, allowing nil values, and not allowing blank values'
35
+
36
+ @matcher = validate_uniqueness_of(:username, :scope => :email)
37
+ @matcher.description.should == 'require unique values for username scoped to :email'
38
+
39
+ @matcher = validate_uniqueness_of(:username)
40
+ @matcher.scope(:email)
41
+ @matcher.scope(:public)
42
+ @matcher.description.should == 'require unique values for username scoped to :email and :public'
41
43
  end
42
44
 
43
45
  it 'should set responds_to_scope? message' do
@@ -72,10 +74,13 @@ describe 'validate_uniqueness_of' do
72
74
  end
73
75
 
74
76
  describe 'scoped to' do
75
- it { should define_and_validate(:scope => :email).scope(:email) }
76
- it { should define_and_validate(:scope => [:email, :access_code]).scope(:email, :access_code) }
77
+ it { should define_and_validate(:scope => :email).scope(:email) }
78
+ it { should define_and_validate(:scope => :public).scope(:public) }
79
+ it { should define_and_validate(:scope => :deleted_at).scope(:deleted_at) }
80
+ it { should define_and_validate(:scope => [:email, :public]).scope(:email, :public) }
81
+ it { should define_and_validate(:scope => [:email, :public, :deleted_at]).scope(:email, :public, :deleted_at) }
77
82
  it { should_not define_and_validate(:scope => :email).scope(:title) }
78
- it { should_not define_and_validate(:scope => :email).scope(:access_code) }
83
+ it { should_not define_and_validate(:scope => :email).scope(:public) }
79
84
  end
80
85
 
81
86
  create_message_specs(self)
@@ -169,6 +174,10 @@ describe 'validate_uniqueness_of' do
169
174
  should_validate_uniqueness_of :username
170
175
  should_validate_uniqueness_of :username, :scope => :email
171
176
  should_not_validate_uniqueness_of :email
172
- should_not_validate_uniqueness_of :username, :scope => :access_code
177
+ should_not_validate_uniqueness_of :username, :scope => :access_code
178
+
179
+ should_validate_uniqueness_of :username do |m|
180
+ m.scope :email
181
+ end
173
182
  end
174
183
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: remarkable_activerecord
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.8
4
+ version: 3.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carlos Brando
@@ -11,7 +11,7 @@ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
 
14
- date: 2009-04-24 00:00:00 +02:00
14
+ date: 2009-04-25 00:00:00 +02:00
15
15
  default_executable:
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
@@ -22,7 +22,7 @@ dependencies:
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: 3.0.8
25
+ version: 3.0.9
26
26
  version:
27
27
  description: "Remarkable ActiveRecord: collection of matchers and macros with I18n for ActiveRecord"
28
28
  email:
@@ -43,6 +43,7 @@ files:
43
43
  - CHANGELOG
44
44
  - lib/remarkable_activerecord/base.rb
45
45
  - lib/remarkable_activerecord/human_names.rb
46
+ - lib/remarkable_activerecord/describe.rb
46
47
  - lib/remarkable_activerecord/matchers/validate_confirmation_of_matcher.rb
47
48
  - lib/remarkable_activerecord/matchers/validate_length_of_matcher.rb
48
49
  - lib/remarkable_activerecord/matchers/have_default_scope_matcher.rb
@@ -98,6 +99,7 @@ test_files:
98
99
  - spec/spec_helper.rb
99
100
  - spec/validate_acceptance_of_matcher_spec.rb
100
101
  - spec/accept_nested_attributes_for_matcher_spec.rb
102
+ - spec/describe_spec.rb
101
103
  - spec/allow_mass_assignment_of_matcher_spec.rb
102
104
  - spec/have_scope_matcher_spec.rb
103
105
  - spec/validate_inclusion_of_matcher_spec.rb