remarkable_activerecord 3.1.2 → 3.1.3

Sign up to get free protection for your applications and to get access to all the features.
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: