remarkable_activerecord 3.1.2 → 3.1.3

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.
data/CHANGELOG CHANGED
@@ -1,3 +1,27 @@
1
+ * Deprecated validate_format_of. It does not have the same API as the respective
2
+ ActiveRecord macro, raising questions frequentely about its usage. [#76]
3
+
4
+ * allow_mass_assignment_of when called without arguments checks if any mass
5
+ assignment is possible [#80]
6
+
7
+ * Add :table_name option to have_index (thanks to Lawrence Pit) [#79]
8
+
9
+ * Allow default subject attributes to be given [#74]
10
+ You can even mix with a fixture replacement tool and still use quick subjects:
11
+
12
+ describe Post
13
+ # Fixjour example
14
+ subject_attributes { valid_post_attributes }
15
+
16
+ describe :published => true do
17
+ should_validate_presence_of :published_at
18
+ end
19
+ end
20
+
21
+ * Bug fix when a symbol is given has join table to habtm association [#75]
22
+
23
+ * Association matchers now searches in the right database for tables [#73]
24
+
1
25
  * validate_length_of accepts :with_kind_of to enable it to work with associations [#69]
2
26
  In your Post specs now you can write:
3
27
 
data/README CHANGED
@@ -3,8 +3,9 @@
3
3
  Remarkable ActiveRecord is a collection of matchers to ActiveRecord. Why use
4
4
  Remarkable?
5
5
 
6
- * The only one with matchers for all ActiveRecord validations, with support to
7
- all options (except :on and the option :with in validates_format_of);
6
+ * Matchers for all ActiveRecord validations, with support to all options. The only
7
+ exceptions are validate_format_of (which should be invoked as allow_values_for)
8
+ and the :on option;
8
9
 
9
10
  * Matchers for all ActiveRecord associations. The only one which supports all
10
11
  these options:
@@ -1,82 +1,190 @@
1
1
  module Remarkable
2
2
  module ActiveRecord
3
3
 
4
- def self.after_include(target)
5
- target.class_inheritable_reader :default_subject_attributes
6
- target.extend Describe
4
+ def self.after_include(target) #:nodoc:
5
+ target.class_inheritable_reader :describe_subject_attributes, :default_subject_attributes
6
+ target.send :include, Describe
7
7
  end
8
8
 
9
- module Describe
9
+ # Overwrites describe to provide quick way to configure your subject:
10
+ #
11
+ # describe Post
12
+ # should_validate_presente_of :title
13
+ #
14
+ # describe :published => true do
15
+ # should_validate_presence_of :published_at
16
+ # end
17
+ # end
18
+ #
19
+ # This is the same as:
20
+ #
21
+ # describe Post
22
+ # should_validate_presente_of :title
23
+ #
24
+ # describe "when published is true" do
25
+ # subject { Post.new(:published => true) }
26
+ # should_validate_presence_of :published_at
27
+ # end
28
+ # end
29
+ #
30
+ # The string can be localized using I18n. An example yml file is:
31
+ #
32
+ # locale:
33
+ # remarkable:
34
+ # active_record:
35
+ # describe:
36
+ # each: "{{key}} is {{value}}"
37
+ # prepend: "when "
38
+ # connector: " and "
39
+ #
40
+ # You can also call subject attributes to set the default attributes for a
41
+ # subject. You can even mix with a fixture replacement tool:
42
+ #
43
+ # describe Post
44
+ # # Fixjour example
45
+ # subject_attributes { valid_post_attributes }
46
+ #
47
+ # describe :published => true do
48
+ # should_validate_presence_of :published_at
49
+ # end
50
+ # end
51
+ #
52
+ # You can retrieve the merged result of all attributes given using the
53
+ # subject_attributes instance method:
54
+ #
55
+ # describe Post
56
+ # # Fixjour example
57
+ # subject_attributes { valid_post_attributes }
58
+ #
59
+ # describe :published => true do
60
+ # it "should have default subject attributes" do
61
+ # subject_attributes.should == { :title => 'My title', :published => true }
62
+ # end
63
+ # end
64
+ # end
65
+ #
66
+ module Describe
10
67
 
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
68
+ def self.included(base) #:nodoc:
69
+ base.extend ClassMethods
70
+ end
71
+
72
+ module ClassMethods
45
73
 
46
- connector = Remarkable.t "remarkable.active_record.describe.connector", :default => " and "
74
+ # Overwrites describe to provide quick way to configure your subject:
75
+ #
76
+ # describe Post
77
+ # should_validate_presente_of :title
78
+ #
79
+ # describe :published => true do
80
+ # should_validate_presence_of :published_at
81
+ # end
82
+ # end
83
+ #
84
+ # This is the same as:
85
+ #
86
+ # describe Post
87
+ # should_validate_presente_of :title
88
+ #
89
+ # describe "when published is true" do
90
+ # subject { Post.new(:published => true) }
91
+ # should_validate_presence_of :published_at
92
+ # end
93
+ # end
94
+ #
95
+ # The string can be localized using I18n. An example yml file is:
96
+ #
97
+ # locale:
98
+ # remarkable:
99
+ # active_record:
100
+ # describe:
101
+ # each: "{{key}} is {{value}}"
102
+ # prepend: "when "
103
+ # connector: " and "
104
+ #
105
+ # See also subject_attributes instance and class methods for more
106
+ # information.
107
+ #
108
+ def describe(*args, &block)
109
+ if described_class && args.first.is_a?(Hash)
110
+ attributes = args.shift
47
111
 
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
112
+ connector = Remarkable.t "remarkable.active_record.describe.connector", :default => " and "
53
113
 
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.to_s, :locale => Remarkable.locale)
114
+ description = if self.describe_subject_attributes.blank?
115
+ Remarkable.t("remarkable.active_record.describe.prepend", :default => "when ")
58
116
  else
59
- key.to_s.humanize
117
+ connector.lstrip
60
118
  end
61
119
 
62
- pieces << Remarkable.t("remarkable.active_record.describe.each",
63
- :default => "{{key}} is {{value}}",
64
- :key => translated_key.downcase, :value => value.inspect)
65
- end
120
+ pieces = []
121
+ attributes.each do |key, value|
122
+ translated_key = if described_class.respond_to?(:human_attribute_name)
123
+ described_class.human_attribute_name(key.to_s, :locale => Remarkable.locale)
124
+ else
125
+ key.to_s.humanize
126
+ end
66
127
 
67
- description << pieces.join(connector)
68
- args.unshift(description)
128
+ pieces << Remarkable.t("remarkable.active_record.describe.each",
129
+ :default => "{{key}} is {{value}}",
130
+ :key => translated_key.downcase, :value => value.inspect)
131
+ end
69
132
 
70
- # Creates an example group, set the subject 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)
133
+ description << pieces.join(connector)
134
+ args.unshift(description)
135
+
136
+ # Creates an example group, set the subject and eval the given block.
137
+ #
138
+ example_group = super(*args) do
139
+ write_inheritable_hash(:describe_subject_attributes, attributes)
140
+ subject { self.class.described_class.new(subject_attributes) }
141
+ instance_eval(&block)
142
+ end
143
+ else
144
+ super(*args, &block)
76
145
  end
77
- else
78
- super(*args, &block)
79
- end
146
+ end
147
+
148
+ # Sets default attributes for the subject. You can use this to set up
149
+ # your subject with valid attributes. You can even mix with a fixture
150
+ # replacement tool and still use quick subjects:
151
+ #
152
+ # describe Post
153
+ # # Fixjour example
154
+ # subject_attributes { valid_post_attributes }
155
+ #
156
+ # describe :published => true do
157
+ # should_validate_presence_of :published_at
158
+ # end
159
+ # end
160
+ #
161
+ def subject_attributes(options=nil, &block)
162
+ write_inheritable_attribute(:default_subject_attributes, options || block)
163
+ subject { self.class.described_class.new(subject_attributes) }
164
+ end
165
+
166
+ end
167
+
168
+ # Returns a hash with the subject attributes declared using the
169
+ # subject_attributes class method and the attributes given using the
170
+ # describe method.
171
+ #
172
+ # describe Post
173
+ # subject_attributes { valid_post_attributes }
174
+ #
175
+ # describe :published => true do
176
+ # it "should have default subject attributes" do
177
+ # subject_attributes.should == { :title => 'My title', :published => true }
178
+ # end
179
+ # end
180
+ # end
181
+ #
182
+ def subject_attributes
183
+ default = self.class.default_subject_attributes
184
+ default = self.instance_eval(&default) if default.is_a?(Proc)
185
+ default ||= {}
186
+
187
+ default.merge(self.class.describe_subject_attributes || {})
80
188
  end
81
189
 
82
190
  end
@@ -3,19 +3,44 @@ module Remarkable
3
3
  module Matchers
4
4
  class AllowMassAssignmentOfMatcher < Remarkable::ActiveRecord::Base #:nodoc:
5
5
  arguments :collection => :attributes, :as => :attribute
6
-
6
+
7
+ assertion :allows?
7
8
  collection_assertions :is_protected?, :is_accessible?
8
9
 
9
10
  protected
11
+
12
+ # If no attribute is given, check if no attribute is being protected,
13
+ # otherwise it fails.
14
+ #
15
+ def allows?
16
+ !@attributes.empty? || protected_attributes.empty?
17
+ end
10
18
 
11
19
  def is_protected?
12
- protected = subject_class.protected_attributes || []
13
- protected.empty? || !protected.include?(@attribute.to_s)
20
+ protected_attributes.empty? || !protected_attributes.include?(@attribute.to_s)
14
21
  end
15
22
 
16
23
  def is_accessible?
17
- accessible = subject_class.accessible_attributes || []
18
- accessible.empty? || accessible.include?(@attribute.to_s)
24
+ accessible_attributes.empty? || accessible_attributes.include?(@attribute.to_s)
25
+ end
26
+
27
+ def interpolation_options
28
+ if @subject
29
+ array = protected_attributes.to_a
30
+ { :protected_attributes => array.empty? ? "[]" : array_to_sentence(array) }
31
+ else
32
+ {}
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def accessible_attributes
39
+ @accessible_attributes ||= subject_class.accessible_attributes || []
40
+ end
41
+
42
+ def protected_attributes
43
+ @protected_attributes ||= subject_class.protected_attributes || []
19
44
  end
20
45
  end
21
46
 
@@ -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{|i| i.inspect}.to_sentence }
56
+ { :in => array_to_sentence(@options[:in].map{|i| i.inspect}) }
57
57
  else
58
58
  { :in => @options[:in].inspect }
59
59
  end
@@ -86,8 +86,18 @@ module Remarkable
86
86
  def allow_values_for(attribute, *args, &block)
87
87
  options = args.extract_options!
88
88
  AllowValuesForMatcher.new(attribute, options.merge!(:in => args), &block).spec(self)
89
+ end
90
+
91
+ # Deprecated. Use allow_values_for instead.
92
+ #
93
+ def validate_format_of(*args)
94
+ if caller[0] =~ /\macros.rb/
95
+ warn "[DEPRECATION] should_validate_format_of is deprecated, use should_allow_values_for instead."
96
+ else
97
+ warn "[DEPRECATION] validate_format_of is deprecated, use allow_values_for instead. Called from #{caller[0]}."
98
+ end
99
+ allow_values_for(*args)
89
100
  end
90
- alias :validate_format_of :allow_values_for
91
101
 
92
102
  end
93
103
  end
@@ -31,25 +31,28 @@ module Remarkable
31
31
  return true unless @options.key?(:through)
32
32
  reflection.source_reflection rescue false
33
33
  end
34
-
34
+
35
+ # has_and_belongs_to_many only works if the tables are in the same
36
+ # database, so we always look for the table in the subject connection.
37
+ #
35
38
  def join_table_exists?
36
39
  return true unless reflection.macro == :has_and_belongs_to_many
37
- ::ActiveRecord::Base.connection.tables.include?(reflection.options[:join_table])
40
+ subject_class.connection.tables.include?(reflection.options[:join_table].to_s)
38
41
  end
39
42
 
40
43
  def foreign_key_exists?
41
44
  return true unless foreign_key_table
42
- table_has_column?(foreign_key_table, reflection_foreign_key)
45
+ table_has_column?(foreign_key_table_class, foreign_key_table, reflection_foreign_key)
43
46
  end
44
47
 
45
48
  def polymorphic_exists?
46
49
  return true unless @options[:polymorphic]
47
- table_has_column?(subject_class.table_name, reflection_foreign_key.sub(/_id$/, '_type'))
50
+ klass_table_has_column?(subject_class, reflection_foreign_key.sub(/_id$/, '_type'))
48
51
  end
49
52
 
50
53
  def counter_cache_exists?
51
54
  return true unless @options[:counter_cache]
52
- table_has_column?(reflection.klass.table_name, reflection.counter_cache_column.to_s)
55
+ klass_table_has_column?(reflection.klass, reflection.counter_cache_column.to_s)
53
56
  end
54
57
 
55
58
  def options_match?
@@ -80,8 +83,12 @@ module Remarkable
80
83
  reflection.primary_key_name.to_s
81
84
  end
82
85
 
83
- def table_has_column?(table_name, column)
84
- ::ActiveRecord::Base.connection.columns(table_name, 'Remarkable column retrieval').any?{|c| c.name == column }
86
+ def table_has_column?(klass, table_name, column)
87
+ klass.connection.columns(table_name, 'Remarkable column retrieval').any?{|c| c.name == column }
88
+ end
89
+
90
+ def klass_table_has_column?(klass, column)
91
+ table_has_column?(klass, klass.table_name, column)
85
92
  end
86
93
 
87
94
  # In through we don't check the foreign_key, because it's spread
@@ -104,7 +111,18 @@ module Remarkable
104
111
  else
105
112
  reflection.klass.table_name
106
113
  end
107
- end
114
+ end
115
+
116
+ # Returns the foreign key table class to use the proper connection
117
+ # when searching for the table and foreign key.
118
+ #
119
+ def foreign_key_table_class
120
+ if [:belongs_to, :has_and_belongs_to_many].include?(reflection.macro)
121
+ subject_class
122
+ else
123
+ reflection.klass
124
+ end
125
+ end
108
126
 
109
127
  def interpolation_options
110
128
  options = { :macro => Remarkable.t(@macro, :scope => matcher_i18n_scope, :default => @macro.to_s), :options => @options.inspect }
@@ -4,6 +4,7 @@ module Remarkable
4
4
  class HaveIndexMatcher < Remarkable::ActiveRecord::Base #:nodoc:
5
5
  arguments :collection => :columns, :as => :column
6
6
 
7
+ optional :table_name
7
8
  optional :unique, :default => true
8
9
 
9
10
  collection_assertions :index_exists?, :is_unique?
@@ -25,11 +26,17 @@ module Remarkable
25
26
  end
26
27
 
27
28
  def indexes
28
- @indexes ||= ::ActiveRecord::Base.connection.indexes(subject_class.table_name)
29
+ @indexes ||= ::ActiveRecord::Base.connection.indexes(current_table_name)
29
30
  end
30
31
 
31
32
  def interpolation_options
32
- @subject ? { :table_name => subject_class.table_name } : {}
33
+ @subject ? { :table_name => current_table_name } : {}
34
+ end
35
+
36
+ private
37
+
38
+ def current_table_name
39
+ @options[:table_name] || subject_class.table_name
33
40
  end
34
41
 
35
42
  end
@@ -39,18 +46,19 @@ module Remarkable
39
46
  # == Options
40
47
  #
41
48
  # * <tt>unique</tt> - when supplied, tests if the index is unique or not
49
+ # * <tt>table_name</tt> - when supplied, tests if the index is defined for the given table
42
50
  #
43
51
  # == Examples
44
52
  #
45
53
  # it { should have_index(:ssn).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
+ # it { should have_index([:name, :email]).unique(true) }
55
+ #
56
+ # should_have_index :ssn, :unique => true, :limit => 9, :null => false
57
+ #
58
+ # should_have_index :ssn do |m|
59
+ # m.unique
60
+ # m.limit = 9
61
+ # m.null = false
54
62
  # end
55
63
  #
56
64
  def have_index(*args, &block)
@@ -21,28 +21,28 @@ module Remarkable
21
21
 
22
22
  # Tries to find an object in the database. If allow_nil and/or allow_blank
23
23
  # is given, we must find a record which is not nil or not blank.
24
- #
25
- # We should also ensure that the object retrieved from the database
26
- # is not the @subject.
24
+ #
25
+ # We should also ensure that the object retrieved from the database
26
+ # is not the @subject.
27
27
  #
28
28
  # If any of these attempts fail, an error is raised.
29
29
  #
30
- def find_first_object?
30
+ def find_first_object?
31
31
  conditions, message = if @options[:allow_nil]
32
32
  [ ["#{@attribute} IS NOT NULL"], " with #{@attribute} not nil" ]
33
33
  elsif @options[:allow_blank]
34
34
  [ ["#{@attribute} != ''"], " with #{@attribute} not blank" ]
35
35
  else
36
36
  [ [], "" ]
37
- end
38
-
39
- unless @subject.new_record?
40
- primary_key = subject_class.primary_key
41
-
42
- message << " which is different from the subject record (the object being validated is the same as the one in the database)"
43
- conditions << "#{subject_class.primary_key} != '#{@subject.send(primary_key)}'"
44
- end
45
-
37
+ end
38
+
39
+ unless @subject.new_record?
40
+ primary_key = subject_class.primary_key
41
+
42
+ message << " which is different from the subject record (the object being validated is the same as the one in the database)"
43
+ conditions << "#{subject_class.primary_key} != '#{@subject.send(primary_key)}'"
44
+ end
45
+
46
46
  options = conditions.empty? ? {} : { :conditions => conditions.join(' AND ') }
47
47
 
48
48
  return true if @existing = subject_class.find(:first, options)
@@ -53,9 +53,9 @@ module Remarkable
53
53
  #
54
54
  def responds_to_scope?
55
55
  (@options[:scope] || []).each do |scope|
56
- setter = :"#{scope}="
57
-
58
- return false, :method => setter unless @subject.respond_to?(setter)
56
+ setter = :"#{scope}="
57
+
58
+ return false, :method => setter unless @subject.respond_to?(setter)
59
59
  return false, :method => scope unless @existing.respond_to?(scope)
60
60
 
61
61
  @subject.send(setter, @existing.send(scope))
@@ -86,11 +86,11 @@ module Remarkable
86
86
  # Now test that the object is valid when changing the scoped attribute.
87
87
  #
88
88
  def valid_with_new_scope?
89
- (@options[:scope] || []).each do |scope|
90
- setter = :"#{scope}="
89
+ (@options[:scope] || []).each do |scope|
90
+ setter = :"#{scope}="
91
91
 
92
- previous_scope_value = @subject.send(scope)
93
- @subject.send(setter, new_value_for_scope(scope))
92
+ previous_scope_value = @subject.send(scope)
93
+ @subject.send(setter, new_value_for_scope(scope))
94
94
  return false, :method => scope unless good?(@value)
95
95
 
96
96
  @subject.send(setter, previous_scope_value)
@@ -106,13 +106,13 @@ module Remarkable
106
106
  #
107
107
  def allow_nil?
108
108
  return true unless @options.key?(:allow_nil)
109
-
109
+
110
110
  begin
111
111
  @existing.update_attribute(@attribute, nil)
112
112
  rescue ::ActiveRecord::StatementInvalid => e
113
113
  raise ScriptError, "You declared that #{@attribute} accepts nil values in validate_uniqueness_of, " <<
114
- "but I cannot save nil values in the database, got: #{e.message}" if @options[:allow_nil]
115
- return true
114
+ "but I cannot save nil values in the database, got: #{e.message}" if @options[:allow_nil]
115
+ return true
116
116
  end
117
117
 
118
118
  super
@@ -123,53 +123,53 @@ module Remarkable
123
123
  #
124
124
  def allow_blank?
125
125
  return true unless @options.key?(:allow_blank)
126
-
126
+
127
127
  begin
128
128
  @existing.update_attribute(@attribute, '')
129
129
  rescue ::ActiveRecord::StatementInvalid => e
130
130
  raise ScriptError, "You declared that #{@attribute} accepts blank values in validate_uniqueness_of, " <<
131
- "but I cannot save blank values in the database, got: #{e.message}" if @options[:allow_blank]
132
- return true
131
+ "but I cannot save blank values in the database, got: #{e.message}" if @options[:allow_blank]
132
+ return true
133
133
  end
134
134
 
135
135
  super
136
136
  end
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
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
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
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
164
  # and searching for them in the database.
165
165
  #
166
166
  def new_value_for_stringfiable_scope(scope)
167
167
  values = [(@existing.send(scope) || 999).next.to_s]
168
168
 
169
169
  # Generate a range of values to search in the database
170
- 100.times do
171
- values << values.last.next
172
- end
170
+ 100.times do
171
+ values << values.last.next
172
+ end
173
173
  conditions = { scope => values, @attribute => @value }
174
174
 
175
175
  # Get values from the database, get the scope attribute and map them to string.
@@ -190,15 +190,15 @@ module Remarkable
190
190
  #
191
191
  # Requires an existing record in the database. If you supply :allow_nil as
192
192
  # option, you need to have in the database a record which is not nil in the
193
- # given attributes. The same is required for allow_blank option.
194
- #
195
- # Notice that the record being validate should not be the same as in the
196
- # database. In other words, you can't do this:
197
- #
198
- # subject { Post.create!(@valid_attributes) }
199
- # should_validate_uniquness_of :title
200
- #
201
- # But don't worry, if you eventually do that, a helpful error message
193
+ # given attributes. The same is required for allow_blank option.
194
+ #
195
+ # Notice that the record being validate should not be the same as in the
196
+ # database. In other words, you can't do this:
197
+ #
198
+ # subject { Post.create!(@valid_attributes) }
199
+ # should_validate_uniqueness_of :title
200
+ #
201
+ # But don't worry, if you eventually do that, a helpful error message
202
202
  # will be raised.
203
203
  #
204
204
  # == Options
@@ -214,15 +214,15 @@ module Remarkable
214
214
  #
215
215
  # it { should validate_uniqueness_of(:keyword, :username) }
216
216
  # it { should validate_uniqueness_of(:email, :scope => :name, :case_sensitive => false) }
217
- # it { should validate_uniqueness_of(:address, :scope => [:first_name, :last_name]) }
218
- #
217
+ # it { should validate_uniqueness_of(:address, :scope => [:first_name, :last_name]) }
218
+ #
219
219
  # should_validate_uniqueness_of :keyword, :username
220
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
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
226
  # end
227
227
  #
228
228
  def validate_uniqueness_of(*attributes, &block)
data/locale/en.yml CHANGED
@@ -41,7 +41,8 @@ en:
41
41
 
42
42
  allow_mass_assignment_of:
43
43
  description: "allow mass assignment of {{attributes}}"
44
- expectations:
44
+ expectations:
45
+ allows: "{{subject_name}} to allow mass assignment ({{subject_name}} is protecting {{protected_attributes}})"
45
46
  is_protected: "{{subject_name}} to allow mass assignment of {{attribute}} ({{subject_name}} is protecting {{attribute}})"
46
47
  is_accessible: "{{subject_name}} to allow mass assignment of {{attribute}} ({{subject_name}} has not made {{attribute}} accessible)"
47
48
 
@@ -140,7 +141,9 @@ en:
140
141
  optionals:
141
142
  unique:
142
143
  positive: "with unique values"
143
- negative: "with non unique values"
144
+ negative: "with non unique values"
145
+ table_name:
146
+ positive: "on table {{value}}"
144
147
 
145
148
  have_readonly_attributes:
146
149
  description: "make {{attributes}} read-only"
@@ -20,7 +20,14 @@ describe 'allow_mass_assignment_of' do
20
20
  @matcher = allow_mass_assignment_of(:title, :category)
21
21
  @matcher.description.should == 'allow mass assignment of title and category'
22
22
  end
23
-
23
+
24
+ it 'should set allows? message' do
25
+ define_and_validate(:protected => true)
26
+ @matcher = allow_mass_assignment_of
27
+ @matcher.matches?(@model)
28
+ @matcher.failure_message.should == 'Expected Product to allow mass assignment (Product is protecting category and title)'
29
+ end
30
+
24
31
  it 'should set is_protected? message' do
25
32
  @matcher = define_and_validate(:protected => true)
26
33
  @matcher.matches?(@model)
@@ -40,7 +47,24 @@ describe 'allow_mass_assignment_of' do
40
47
  it { should define_and_validate(:accessible => true) }
41
48
 
42
49
  it { should_not define_and_validate(:protected => true) }
43
- it { should_not define_and_validate(:accessible => [:another]) }
50
+ it { should_not define_and_validate(:accessible => [:another]) }
51
+
52
+ describe 'with no argument' do
53
+ it 'should allow mass assignment if no attribute is accessible or protected' do
54
+ define_and_validate
55
+ should allow_mass_assignment_of
56
+ end
57
+
58
+ it 'should allow mass assignment if attributes are accessible' do
59
+ define_and_validate(:accessible => true)
60
+ should allow_mass_assignment_of
61
+ end
62
+
63
+ it 'should not allow mass assignment if attributes are protected' do
64
+ define_and_validate(:protected => true)
65
+ should_not allow_mass_assignment_of
66
+ end
67
+ end
44
68
  end
45
69
 
46
70
  describe 'macros' do
@@ -49,8 +49,13 @@ describe 'allow_values_for' do
49
49
  describe 'macros' do
50
50
  before(:each){ define_and_validate(:with => /X|Y|Z/) }
51
51
 
52
- should_allow_values_for :title, 'X'
53
- should_not_allow_values_for :title, 'A'
52
+ should_allow_values_for :title, 'X'
53
+ should_not_allow_values_for :title, 'A'
54
+
55
+ describe 'deprecation' do
56
+ it { should validate_format_of(:title, 'X') }
57
+ should_not_validate_format_of :title, 'A'
58
+ end
54
59
  end
55
60
  end
56
61
 
@@ -273,7 +273,10 @@ describe 'association_matcher' do
273
273
  describe 'with join table option' do
274
274
  it { should define_and_validate.join_table('labels_projects') }
275
275
  it { should define_and_validate(:join_table => 'my_table',
276
- :association_table => 'my_table').join_table('my_table') }
276
+ :association_table => 'my_table').join_table('my_table') }
277
+
278
+ it { should define_and_validate(:join_table => :my_table,
279
+ :association_table => :my_table).join_table(:my_table) }
277
280
 
278
281
  it { should_not define_and_validate.join_table('projects_labels') }
279
282
 
@@ -3,7 +3,7 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
3
3
  RAILS_I18n = true
4
4
 
5
5
  class Post
6
- attr_accessor :title, :published, :public
6
+ attr_accessor :published, :public, :deleted
7
7
 
8
8
  def initialize(attributes={})
9
9
  attributes.each do |key, value|
@@ -14,13 +14,41 @@ class Post
14
14
  def self.human_name(*args)
15
15
  "MyPost"
16
16
  end
17
- end unless defined?(Post)
17
+ end
18
18
 
19
19
  describe Post do
20
20
  it "should use human name on description" do
21
21
  self.class.description.should == "MyPost"
22
22
  end
23
23
 
24
+ describe "default attributes as a hash" do
25
+ subject_attributes :deleted => true
26
+
27
+ it "should set the subject with deleted equals to true" do
28
+ subject.deleted.should be_true
29
+ end
30
+
31
+ it "should not change the description" do
32
+ self.class.description.should == "MyPost default attributes as a hash"
33
+ end
34
+ end
35
+
36
+ describe "default attributes as a proc" do
37
+ subject_attributes { my_attributes }
38
+
39
+ it "should set the subject with deleted equals to true" do
40
+ subject.deleted.should be_true
41
+ end
42
+
43
+ it "should not change the description" do
44
+ self.class.description.should == "MyPost default attributes as a proc"
45
+ end
46
+
47
+ def my_attributes
48
+ { :deleted => true }
49
+ end
50
+ end
51
+
24
52
  describe :published => true do
25
53
  it "should set the subject with published equals to true" do
26
54
  subject.published.should be_true
@@ -46,6 +74,16 @@ describe Post do
46
74
  it "should nest descriptions" do
47
75
  self.class.description.should == "MyPost when published is true and public is false"
48
76
  end
77
+
78
+ describe "default attributes as a hash" do
79
+ subject_attributes :deleted => true
80
+
81
+ it "should merge describe attributes with subject attributes" do
82
+ subject.published.should be_true
83
+ subject.public.should be_false
84
+ subject.deleted.should be_true
85
+ end
86
+ end
49
87
  end
50
88
  end
51
89
 
@@ -9,25 +9,33 @@ describe 'have_index_matcher' do
9
9
  table.string :email, :limit => '255', :default => 'jose.valim@gmail.com'
10
10
  }
11
11
 
12
+ create_table "users_watchers" do |t|
13
+ t.integer :user_id
14
+ t.integer :watcher_id
15
+ end
16
+
12
17
  ActiveRecord::Base.connection.add_index :users, :name
13
18
  ActiveRecord::Base.connection.add_index :users, :email, :unique => true
14
19
  ActiveRecord::Base.connection.add_index :users, [:email, :name], :unique => true
20
+ ActiveRecord::Base.connection.add_index :users_watchers, :user_id
15
21
  end
16
22
 
17
23
  describe 'messages' do
18
-
19
24
  it 'should contain a description' do
20
25
  @matcher = have_index(:name)
21
26
  @matcher.description.should == 'have index for column(s) name'
22
27
 
23
28
  @matcher.unique
24
29
  @matcher.description.should == 'have index for column(s) name with unique values'
30
+
31
+ @matcher.table_name("another")
32
+ @matcher.description.should == 'have index for column(s) name on table another and with unique values'
25
33
  end
26
34
 
27
35
  it 'should set index_exists? message' do
28
- @matcher = have_index(:password)
36
+ @matcher = have_index(:password).table_name("special_users")
29
37
  @matcher.matches?(@model)
30
- @matcher.failure_message.should == 'Expected index password to exist on table users'
38
+ @matcher.failure_message.should == 'Expected index password to exist on table special_users'
31
39
  end
32
40
 
33
41
  it 'should set is_unique? message' do
@@ -41,28 +49,39 @@ describe 'have_index_matcher' do
41
49
  it { should have_index(:name) }
42
50
  it { should have_index(:email) }
43
51
  it { should have_index([:email, :name]) }
44
- it { should have_indices(:name, :email) }
52
+ it { should have_index(:name, :email) }
45
53
 
46
54
  it { should have_index(:name).unique(false) }
47
55
  it { should have_index(:email).unique }
56
+ it { should have_index(:user_id).table_name(:users_watchers) }
48
57
 
49
58
  it { should_not have_index(:password) }
50
59
  it { should_not have_index(:name).unique(true) }
51
60
  it { should_not have_index(:email).unique(false) }
61
+ it { should_not have_index(:watcher_id).table_name(:users_watchers) }
52
62
  end
53
63
 
54
64
  describe 'macros' do
55
65
  should_have_index :name
56
66
  should_have_index :email
57
67
  should_have_index [:email, :name]
58
- should_have_indices :name, :email
68
+ should_have_index :name, :email
59
69
 
60
70
  should_have_index :name, :unique => false
61
71
  should_have_index :email, :unique => true
72
+ should_have_index :user_id, :table_name => :users_watchers
62
73
 
63
74
  should_not_have_index :password
64
75
  should_not_have_index :name, :unique => true
65
76
  should_not_have_index :email, :unique => false
77
+ should_not_have_index :watcher_id, :table_name => :users_watchers
66
78
  end
79
+
80
+ describe "aliases" do
81
+ should_have_indices :name
82
+ should_have_db_index :name
83
+ should_have_db_indices :name
84
+ end
85
+
67
86
  end
68
87
 
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,5 @@
1
+ # encoding: utf-8
1
2
  require 'rubygems'
2
- require 'ruby-debug'
3
3
 
4
4
  RAILS_VERSION = ENV['RAILS_VERSION'] || '2.3.2'
5
5
 
@@ -143,29 +143,29 @@ describe 'validate_length_of' do
143
143
  it { should define_and_validate(:is => 3).is(3) }
144
144
  it { should_not define_and_validate(:is => 3).is(2) }
145
145
  it { should_not define_and_validate(:is => 3).is(4) }
146
- end
147
-
148
- describe "with with kind of" do
149
- def define_and_validate(options)
150
- define_model :variant, :product_id => :integer
146
+ end
147
+
148
+ describe "with with kind of" do
149
+ def define_and_validate(options)
150
+ define_model :variant, :product_id => :integer
151
151
 
152
- @model = define_model :product do
152
+ @model = define_model :product do
153
153
  has_many :variants
154
- validates_length_of :variants, options
154
+ validates_length_of :variants, options
155
155
  end
156
156
 
157
- validate_length_of(:variants)
158
- end
159
-
160
- it { should define_and_validate(:within => 3..6).within(3..6).with_kind_of(Variant) }
161
- it { should_not define_and_validate(:within => 2..6).within(3..6).with_kind_of(Variant) }
162
- it { should_not define_and_validate(:within => 3..7).within(3..6).with_kind_of(Variant) }
163
-
164
- it "should raise association type mismatch if with_kind_of is not supplied" do
165
- lambda {
166
- should_not define_and_validate(:within => 3..6).within(3..6)
167
- }.should raise_error(ActiveRecord::AssociationTypeMismatch)
168
- end
157
+ validate_length_of(:variants)
158
+ end
159
+
160
+ it { should define_and_validate(:within => 3..6).within(3..6).with_kind_of(Variant) }
161
+ it { should_not define_and_validate(:within => 2..6).within(3..6).with_kind_of(Variant) }
162
+ it { should_not define_and_validate(:within => 3..7).within(3..6).with_kind_of(Variant) }
163
+
164
+ it "should raise association type mismatch if with_kind_of does not match" do
165
+ lambda {
166
+ should_not define_and_validate(:within => 3..6).within(3..6).with_kind_of(Product)
167
+ }.should raise_error(ActiveRecord::AssociationTypeMismatch)
168
+ end
169
169
  end
170
170
 
171
171
  # Those are macros to test optionals which accept only boolean values
@@ -178,16 +178,16 @@ describe 'validate_length_of' do
178
178
  before(:each) { define_and_validate }
179
179
 
180
180
  should_validate_length_of :size, :in => 3..5
181
- should_validate_length_of :size, :within => 3..5
181
+ should_validate_length_of :size, :within => 3..5
182
182
 
183
183
  should_not_validate_length_of :size, :within => 2..5
184
184
  should_not_validate_length_of :size, :within => 4..5
185
185
  should_not_validate_length_of :size, :within => 3..4
186
186
  should_not_validate_length_of :size, :within => 3..6
187
- should_not_validate_length_of :category, :in => 3..5
188
-
189
- should_validate_length_of :size do |m|
190
- m.in = 3..5
187
+ should_not_validate_length_of :category, :in => 3..5
188
+
189
+ should_validate_length_of :size do |m|
190
+ m.in = 3..5
191
191
  end
192
192
  end
193
193
  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.1.2
4
+ version: 3.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carlos Brando
@@ -11,9 +11,19 @@ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
 
14
- date: 2009-05-15 00:00:00 +02:00
14
+ date: 2009-05-28 00:00:00 +02:00
15
15
  default_executable:
16
16
  dependencies:
17
+ - !ruby/object:Gem::Dependency
18
+ name: rspec
19
+ type: :runtime
20
+ version_requirement:
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 1.2.0
26
+ version:
17
27
  - !ruby/object:Gem::Dependency
18
28
  name: remarkable
19
29
  type: :runtime
@@ -22,7 +32,7 @@ dependencies:
22
32
  requirements:
23
33
  - - ">="
24
34
  - !ruby/object:Gem::Version
25
- version: 3.1.2
35
+ version: 3.1.3
26
36
  version:
27
37
  description: "Remarkable ActiveRecord: collection of matchers and macros with I18n for ActiveRecord"
28
38
  email: