remarkable_activerecord 3.0.8 → 3.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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