carlosbrando-remarkable 0.0.99 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. data/History.txt +5 -5
  2. data/Manifest.txt +53 -35
  3. data/PostInstall.txt +1 -6
  4. data/README.rdoc +109 -15
  5. data/Rakefile +29 -29
  6. data/lib/remarkable/active_record/README.markdown +378 -0
  7. data/lib/remarkable/active_record/active_record.rb +12 -11
  8. data/lib/remarkable/active_record/helpers.rb +215 -5
  9. data/lib/remarkable/active_record/macros/associations/association_matcher.rb +242 -0
  10. data/lib/remarkable/active_record/macros/callbacks/callback_matcher.rb +46 -0
  11. data/lib/remarkable/active_record/macros/database/column_matcher.rb +122 -0
  12. data/lib/remarkable/active_record/macros/database/index_matcher.rb +103 -0
  13. data/lib/remarkable/active_record/macros/validations/allow_mass_assignment_of_matcher.rb +52 -0
  14. data/lib/remarkable/active_record/macros/validations/ensure_value_in_list_matcher.rb +83 -0
  15. data/lib/remarkable/active_record/macros/validations/ensure_value_in_range_matcher.rb +172 -0
  16. data/lib/remarkable/active_record/macros/validations/have_class_methods_matcher.rb +54 -0
  17. data/lib/remarkable/active_record/macros/validations/have_instance_methods_matcher.rb +54 -0
  18. data/lib/remarkable/active_record/macros/validations/have_named_scope_matcher.rb +94 -0
  19. data/lib/remarkable/active_record/macros/validations/have_readonly_attributes_matcher.rb +48 -0
  20. data/lib/remarkable/active_record/macros/validations/protect_attributes_matcher.rb +51 -0
  21. data/lib/remarkable/active_record/macros/validations/validate_acceptance_of_matcher.rb +79 -0
  22. data/lib/remarkable/active_record/macros/validations/validate_associated_matcher.rb +177 -0
  23. data/lib/remarkable/active_record/macros/validations/validate_confirmation_of_matcher.rb +74 -0
  24. data/lib/remarkable/active_record/macros/validations/validate_exclusion_of_matcher.rb +38 -0
  25. data/lib/remarkable/active_record/macros/validations/validate_format_of_matcher.rb +33 -0
  26. data/lib/remarkable/active_record/macros/validations/validate_inclusion_of_matcher.rb +45 -0
  27. data/lib/remarkable/active_record/macros/validations/validate_length_of_matcher.rb +248 -0
  28. data/lib/remarkable/active_record/macros/validations/validate_numericality_of_matcher.rb +206 -0
  29. data/lib/remarkable/active_record/macros/validations/validate_presence_of_matcher.rb +72 -0
  30. data/lib/remarkable/active_record/macros/validations/validate_uniqueness_of_matcher.rb +222 -0
  31. data/lib/remarkable/active_record/macros.rb +52 -0
  32. data/lib/remarkable/assertions.rb +29 -0
  33. data/lib/remarkable/controller/README.markdown +147 -0
  34. data/lib/remarkable/controller/controller.rb +11 -6
  35. data/lib/remarkable/controller/helpers.rb +4 -38
  36. data/lib/remarkable/controller/macros/assign_matcher.rb +85 -0
  37. data/lib/remarkable/controller/macros/filter_params_matcher.rb +63 -0
  38. data/lib/remarkable/controller/macros/metadata_matcher.rb +63 -0
  39. data/lib/remarkable/controller/macros/render_with_layout_matcher.rb +75 -0
  40. data/lib/remarkable/controller/macros/respond_with_content_type_matcher.rb +60 -0
  41. data/lib/remarkable/controller/macros/respond_with_matcher.rb +62 -0
  42. data/lib/remarkable/controller/macros/return_from_session_matcher.rb +58 -0
  43. data/lib/remarkable/controller/macros/route_matcher.rb +75 -0
  44. data/lib/remarkable/controller/macros/set_the_flash_to_matcher.rb +60 -0
  45. data/lib/remarkable/controller/macros.rb +78 -0
  46. data/lib/remarkable/dsl.rb +239 -0
  47. data/lib/remarkable/example/example_methods.rb +27 -7
  48. data/lib/remarkable/helpers.rb +28 -0
  49. data/lib/remarkable/matcher_base.rb +64 -0
  50. data/lib/remarkable/private_helpers.rb +10 -115
  51. data/lib/remarkable/rails.rb +27 -0
  52. data/lib/remarkable.rb +13 -5
  53. data/remarkable.gemspec +43 -0
  54. data/spec/controllers/posts_controller_spec.rb +58 -4
  55. data/spec/controllers/users_controller_spec.rb +1 -0
  56. data/spec/fixtures/fleas.yml +10 -0
  57. data/spec/fixtures/users.yml +7 -0
  58. data/spec/models/address_spec.rb +44 -0
  59. data/spec/models/dog_spec.rb +64 -3
  60. data/spec/models/flea_spec.rb +30 -0
  61. data/spec/models/post_spec.rb +36 -2
  62. data/spec/models/product_spec.rb +73 -8
  63. data/spec/models/tag_spec.rb +2 -2
  64. data/spec/models/tagging_spec.rb +24 -0
  65. data/spec/models/user_spec.rb +206 -21
  66. data/spec/other/custom_macros_spec.rb +27 -0
  67. data/spec/other/my_own_matcher_spec.rb +11 -0
  68. data/spec/other/private_helpers_spec.rb +31 -0
  69. data/spec/rails_root/app/controllers/posts_controller.rb +2 -0
  70. data/spec/rails_root/app/models/address.rb +2 -2
  71. data/spec/rails_root/app/models/flea.rb +4 -0
  72. data/spec/rails_root/app/models/pets/dog.rb +12 -0
  73. data/spec/rails_root/app/models/product.rb +7 -5
  74. data/spec/rails_root/app/models/tagging.rb +2 -0
  75. data/spec/rails_root/app/models/user.rb +20 -5
  76. data/spec/rails_root/app/views/layouts/posts.rhtml +8 -6
  77. data/spec/rails_root/config/database.yml +1 -2
  78. data/spec/rails_root/config/environment.rb +3 -1
  79. data/spec/rails_root/config/environments/{sqlite3.rb → test.rb} +0 -0
  80. data/spec/rails_root/config/locales/en.yml +8 -0
  81. data/spec/rails_root/db/migrate/001_create_users.rb +2 -2
  82. data/spec/rails_root/db/migrate/005_create_dogs.rb +1 -0
  83. data/spec/rails_root/db/migrate/011_add_fleas_color.rb +10 -0
  84. data/spec/rails_root/db/migrate/012_add_fleas_address.rb +10 -0
  85. data/spec/rails_root/spec/remarkable_macros/.keep +0 -0
  86. data/spec/rails_root/vendor/plugins/my_plugin/remarkable_macros/.keep +0 -0
  87. data/spec/spec_helper.rb +0 -2
  88. metadata +63 -43
  89. data/lib/remarkable/active_record/macros/associations/belong_to.rb +0 -81
  90. data/lib/remarkable/active_record/macros/associations/have_and_belong_to_many.rb +0 -77
  91. data/lib/remarkable/active_record/macros/associations/have_many.rb +0 -160
  92. data/lib/remarkable/active_record/macros/associations/have_one.rb +0 -133
  93. data/lib/remarkable/active_record/macros/database/have_db_column.rb +0 -81
  94. data/lib/remarkable/active_record/macros/database/have_db_columns.rb +0 -73
  95. data/lib/remarkable/active_record/macros/database/have_indices.rb +0 -75
  96. data/lib/remarkable/active_record/macros/validations/allow_values_for.rb +0 -103
  97. data/lib/remarkable/active_record/macros/validations/ensure_length_at_least.rb +0 -97
  98. data/lib/remarkable/active_record/macros/validations/ensure_length_in_range.rb +0 -134
  99. data/lib/remarkable/active_record/macros/validations/ensure_length_is.rb +0 -106
  100. data/lib/remarkable/active_record/macros/validations/ensure_value_in_range.rb +0 -117
  101. data/lib/remarkable/active_record/macros/validations/have_class_methods.rb +0 -74
  102. data/lib/remarkable/active_record/macros/validations/have_instance_methods.rb +0 -74
  103. data/lib/remarkable/active_record/macros/validations/have_named_scope.rb +0 -148
  104. data/lib/remarkable/active_record/macros/validations/have_readonly_attributes.rb +0 -81
  105. data/lib/remarkable/active_record/macros/validations/only_allow_numeric_values_for.rb +0 -89
  106. data/lib/remarkable/active_record/macros/validations/protect_attributes.rb +0 -89
  107. data/lib/remarkable/active_record/macros/validations/require_acceptance_of.rb +0 -94
  108. data/lib/remarkable/active_record/macros/validations/require_attributes.rb +0 -94
  109. data/lib/remarkable/active_record/macros/validations/require_unique_attributes.rb +0 -146
  110. data/lib/remarkable/controller/macros/assign_to.rb +0 -110
  111. data/lib/remarkable/controller/macros/filter_params.rb +0 -52
  112. data/lib/remarkable/controller/macros/redirect_to.rb +0 -24
  113. data/lib/remarkable/controller/macros/render_a_form.rb +0 -23
  114. data/lib/remarkable/controller/macros/render_template.rb +0 -18
  115. data/lib/remarkable/controller/macros/render_with_layout.rb +0 -61
  116. data/lib/remarkable/controller/macros/respond_with.rb +0 -86
  117. data/lib/remarkable/controller/macros/respond_with_content_type.rb +0 -45
  118. data/lib/remarkable/controller/macros/return_from_session.rb +0 -45
  119. data/lib/remarkable/controller/macros/route.rb +0 -91
  120. data/lib/remarkable/controller/macros/set_the_flash_to.rb +0 -58
  121. data/spec/rails_root/app/models/dog.rb +0 -5
@@ -0,0 +1,242 @@
1
+ module Remarkable # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers # :nodoc:
4
+
5
+ class AssociationMatcher < Remarkable::Matcher::Base
6
+ def initialize(macro, *associations)
7
+ @options = associations.extract_options!
8
+ @macro = macro
9
+ @associations = associations
10
+ end
11
+
12
+ def through(through)
13
+ @options[:through] = through
14
+ self
15
+ end
16
+
17
+ def dependent(dependent)
18
+ @options[:dependent] = dependent
19
+ self
20
+ end
21
+
22
+ def matches?(subject)
23
+ @subject = subject
24
+
25
+ assert_matcher_for(@associations) do |association|
26
+ association_correct?(association)
27
+ end
28
+ end
29
+
30
+ def failure_message
31
+ "Expected #{expectation} (#{@missing})"
32
+ end
33
+
34
+ def negative_failure_message
35
+ "Did not expect #{expectation}"
36
+ end
37
+
38
+ def description
39
+ description = "#{macro_description} #{@associations.to_sentence}"
40
+ description += " through #{@options[:through]}" if @options[:through]
41
+ description += " dependent => #{@options[:dependent]}" if @options[:dependent]
42
+ description
43
+ end
44
+
45
+ protected
46
+
47
+ def association_correct?(association)
48
+ @name = association
49
+
50
+ association_exists? &&
51
+ macro_correct? &&
52
+ foreign_key_exists? &&
53
+ through_association_valid? &&
54
+ dependent_correct? &&
55
+ join_table_exists?
56
+ end
57
+
58
+ def association_exists?
59
+ if reflection.nil?
60
+ @missing = "no association called #{@name}"
61
+ false
62
+ else
63
+ true
64
+ end
65
+ end
66
+
67
+ def macro_correct?
68
+ if reflection.macro == @macro
69
+ true
70
+ else
71
+ @missing = "actual association type was #{reflection.macro}"
72
+ false
73
+ end
74
+ end
75
+
76
+ def foreign_key_exists?
77
+ !(belongs_foreign_key_missing? || has_foreign_key_missing?)
78
+ end
79
+
80
+ def belongs_foreign_key_missing?
81
+ @macro == :belongs_to && !class_has_foreign_key?(subject_class)
82
+ end
83
+
84
+ def has_foreign_key_missing?
85
+ [:has_many, :has_one].include?(@macro) &&
86
+ !through? &&
87
+ !class_has_foreign_key?(associated_class)
88
+ end
89
+
90
+ def through_association_valid?
91
+ @options[:through].nil? || (through_association_exists? && through_association_correct?)
92
+ end
93
+
94
+ def through_association_exists?
95
+ if through_reflection.nil?
96
+ @missing = "#{subject_name} does not have any relationship to #{@options[:through]}"
97
+ false
98
+ else
99
+ true
100
+ end
101
+ end
102
+
103
+ def through_association_correct?
104
+ if @options[:through] == reflection.options[:through]
105
+ @missing = "Expected #{subject_name} to have #{@name} through #{@options[:through]}, " <<
106
+ " but got it through #{reflection.options[:through]}"
107
+ true
108
+ else
109
+ false
110
+ end
111
+ end
112
+
113
+ def dependent_correct?
114
+ if @options[:dependent].nil? || @options[:dependent].to_s == reflection.options[:dependent].to_s
115
+ true
116
+ else
117
+ @missing = "#{@name} should have #{@options[:dependent]} dependency"
118
+ false
119
+ end
120
+ end
121
+
122
+ def join_table_exists?
123
+ if @macro != :has_and_belongs_to_many ||
124
+ ::ActiveRecord::Base.connection.tables.include?(join_table.to_s)
125
+ true
126
+ else
127
+ @missing = "join table #{join_table} doesn't exist"
128
+ false
129
+ end
130
+ end
131
+
132
+ def class_has_foreign_key?(klass)
133
+ if klass.column_names.include?(foreign_key.to_s)
134
+ true
135
+ else
136
+ @missing = "#{klass} does not have a #{foreign_key} foreign key."
137
+ false
138
+ end
139
+ end
140
+
141
+ def join_table
142
+ reflection.options[:join_table]
143
+ end
144
+
145
+ def associated_class
146
+ reflection.klass
147
+ end
148
+
149
+ def foreign_key
150
+ reflection.primary_key_name
151
+ end
152
+
153
+ def through?
154
+ reflection.options[:through]
155
+ end
156
+
157
+ def reflection
158
+ subject_class.reflect_on_association(@name)
159
+ end
160
+
161
+ def through_reflection
162
+ subject_class.reflect_on_association(@options[:through])
163
+ end
164
+
165
+ def expectation
166
+ "#{subject_name} to have a #{@macro} association called #{@name}"
167
+ end
168
+
169
+ def macro_description
170
+ case @macro.to_s
171
+ when 'belongs_to' then 'belong to'
172
+ when 'has_many' then 'have many'
173
+ when 'has_one' then 'have one'
174
+ when 'has_and_belongs_to_many' then
175
+ 'have and belong to many'
176
+ end
177
+ end
178
+ end
179
+
180
+
181
+ # Ensure that the belongs_to relationship exists.
182
+ #
183
+ # Examples:
184
+ #
185
+ # should_belong_to :parent
186
+ # it { should belong_to(:parent) }
187
+ #
188
+ def belong_to(*associations)
189
+ AssociationMatcher.new(:belongs_to, *associations)
190
+ end
191
+
192
+ # Ensures that the has_many relationship exists. Will also test that the associated table has the required columns. Works with polymorphic associations.
193
+ #
194
+ # Options:
195
+ #
196
+ # * <tt>:through</tt> - association name for has_many :through
197
+ # * <tt>:dependent</tt> - tests that the association makes use of the dependent option.
198
+ #
199
+ # Examples:
200
+ #
201
+ # should_have_many :friends
202
+ # should_have_many :enemies, :through => :friends
203
+ # should_have_many :enemies, :dependent => :destroy
204
+ #
205
+ # it{ should have_many(:friends) }
206
+ # it{ should have_many(:enemies, :through => :friends) }
207
+ # it{ should have_many(:enemies, :dependent => :destroy) }
208
+ #
209
+ #
210
+ def have_many(*associations)
211
+ AssociationMatcher.new(:has_many, *associations)
212
+ end
213
+
214
+ # Ensure that the has_one relationship exists. Will also test that the associated table has the required columns. Works with polymorphic associations.
215
+ #
216
+ # Options:
217
+ #
218
+ # * <tt>:dependent</tt> - tests that the association makes use of the dependent option.
219
+ #
220
+ # Examples:
221
+ #
222
+ # should_have_one :god
223
+ # it{ should have_one(:god) }
224
+ #
225
+ def have_one(*associations)
226
+ AssociationMatcher.new(:has_one, *associations)
227
+ end
228
+
229
+ # Ensures that the has_and_belongs_to_many relationship exists, and that the join table is in place.
230
+ #
231
+ # Examples:
232
+ #
233
+ # should_have_and_belong_to_many :posts, :cars
234
+ # it{ should have_and_belong_to_many :posts, :cars }
235
+ #
236
+ def have_and_belong_to_many(*associations)
237
+ AssociationMatcher.new(:has_and_belongs_to_many, *associations)
238
+ end
239
+
240
+ end
241
+ end
242
+ end
@@ -0,0 +1,46 @@
1
+ module Remarkable # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers # :nodoc:
4
+
5
+ ::ActiveRecord::Callbacks::CALLBACKS.each do |callback|
6
+ define_method("have_#{callback}_callback") do |method|
7
+ CallbackMatcher.new(callback, method)
8
+ end
9
+ end
10
+
11
+ class CallbackMatcher < Remarkable::Matcher::Base
12
+ def initialize(callback, method)
13
+ @callback = callback
14
+ @method = method
15
+ end
16
+
17
+ def matches?(subject)
18
+ @subject = subject
19
+ assert_matcher { has_callback? }
20
+ end
21
+
22
+ def description
23
+ "have a #{@callback} callback named #{@method}"
24
+ end
25
+
26
+ private
27
+
28
+ def has_callback?
29
+ return true if callbacks_for(@callback).include?(@method)
30
+
31
+ @missing = "#{subject_name} does not have a #{@callback} callback named #{@method}"
32
+ return false
33
+ end
34
+
35
+ def callbacks_for(callback)
36
+ subject_class.send("#{callback}_callback_chain").collect(&:method)
37
+ end
38
+
39
+ def expectation
40
+ "#{subject_name} have a #{@callback} callback named #{@method}"
41
+ end
42
+
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,122 @@
1
+ module Remarkable # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers # :nodoc:
4
+
5
+ class ColumnMatcher < Remarkable::Matcher::Base
6
+ def initialize(*columns)
7
+ @options = columns.extract_options!
8
+ @columns = columns
9
+ end
10
+
11
+ def type(type)
12
+ @options[:type] = type
13
+ self
14
+ end
15
+
16
+ def primary(value = true)
17
+ @options[:primary] = value
18
+ self
19
+ end
20
+
21
+ def default(default)
22
+ @options[:default] = default
23
+ self
24
+ end
25
+
26
+ def precision(precision)
27
+ @options[:precision] = precision
28
+ self
29
+ end
30
+
31
+ def limit(limit)
32
+ @options[:limit] = limit
33
+ self
34
+ end
35
+
36
+ def null(value = true)
37
+ @options[:null] = value
38
+ self
39
+ end
40
+
41
+ def scale(scale)
42
+ @options[:scale] = scale
43
+ self
44
+ end
45
+
46
+ def sql_type(sql_type)
47
+ @options[:sql_type] = sql_type
48
+ self
49
+ end
50
+
51
+ def matches?(subject)
52
+ @subject = subject
53
+
54
+ assert_matcher_for(@columns) do |column|
55
+ @column = column
56
+ has_column? && all_options_correct?
57
+ end
58
+ end
59
+
60
+ def failure_message
61
+ "Expected #{expectation} (#{@missing})"
62
+ end
63
+
64
+ def negative_failure_message
65
+ "Did not expect #{expectation}"
66
+ end
67
+
68
+ def description
69
+ description = if @columns.size == 1
70
+ "have column named :#{@columns[0]}"
71
+ else
72
+ "have columns #{@columns.to_sentence}"
73
+ end
74
+ description << " with options " + @options.inspect unless @options.empty?
75
+ description
76
+ end
77
+
78
+ protected
79
+
80
+ def column_type
81
+ subject_class.columns.detect {|c| c.name == @column.to_s }
82
+ end
83
+
84
+ def has_column?
85
+ return true if column_type
86
+ @missing = "#{subject_name} does not have column #{@column}"
87
+ false
88
+ end
89
+
90
+ def all_options_correct?
91
+ @options.each do |option, value|
92
+ return false unless option_correct?(option, value)
93
+ end
94
+ end
95
+
96
+ def option_correct?(option, expected_value)
97
+ found_value = column_type.instance_variable_get("@#{option.to_s}").to_s
98
+
99
+ if found_value == expected_value.to_s
100
+ true
101
+ else
102
+ @missing = ":#{@column} column on table for #{subject_class} does not match option :#{option}, found '#{found_value}' but expected '#{expected_value}'"
103
+ false
104
+ end
105
+ end
106
+
107
+ def expectation
108
+ "#{subject_name} to have a column named #{@column}"
109
+ end
110
+ end
111
+
112
+ def have_db_column(column, options = {})
113
+ ColumnMatcher.new(column, options)
114
+ end
115
+
116
+ def have_db_columns(*columns)
117
+ ColumnMatcher.new(*columns)
118
+ end
119
+
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,103 @@
1
+ module Remarkable # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers # :nodoc:
4
+
5
+ # Ensures the database column has specified index.
6
+ #
7
+ # Options:
8
+ # * <tt>unique</tt> -
9
+ #
10
+ # Example:
11
+ # it { should have_index(:ssn).unique(true) }
12
+ #
13
+ def have_indices(*columns)
14
+ IndexMatcher.new(*columns)
15
+ end
16
+ alias_method :have_index, :have_indices
17
+
18
+ class IndexMatcher < Remarkable::Matcher::Base
19
+ INDEX_TYPES = { true => "unique", false => "non-unique" }
20
+
21
+ def initialize(*columns)
22
+ load_options(columns)
23
+ @columns = columns
24
+ end
25
+
26
+ def unique(value = true)
27
+ @options[:unique] = value
28
+ self
29
+ end
30
+
31
+ def table_name=(table_name)
32
+ @table_name = table_name
33
+ end
34
+
35
+ def matches?(subject)
36
+ @subject = subject
37
+ @expected_uniqueness = @options[:unique] ? 'unique' : 'non-unique'
38
+
39
+ assert_matcher_for(@columns) do |column|
40
+ @column = column
41
+ index_exists? && correct_unique?
42
+ end
43
+ end
44
+
45
+ def failure_message
46
+ "Expected to #{expectation} (#{@missing})"
47
+ end
48
+
49
+ def negative_failure_message
50
+ "Did not expect to #{expectation}"
51
+ end
52
+
53
+ def description
54
+ "have #{index_type} index on #{table_name} for #{@columns.inspect}"
55
+ end
56
+
57
+ protected
58
+
59
+ def index_exists?
60
+ return true if matched_index
61
+
62
+ @missing = "#{table_name} does not have an index for #{@column}"
63
+ return false
64
+ end
65
+
66
+ def correct_unique?
67
+ return true unless [true, false].include?(@options[:unique])
68
+ return true if @options[:unique] == matched_index.unique
69
+
70
+ @missing = "Expected #{index_type} index but was #{INDEX_TYPES[!@options[:unique]]}."
71
+ return false
72
+ end
73
+
74
+ def matched_index
75
+ columns = [@column].flatten.map(&:to_s)
76
+ indexes.detect { |ind| ind.columns == columns }
77
+ end
78
+
79
+ def index_type
80
+ INDEX_TYPES[@options[:unique]] || "an"
81
+ end
82
+
83
+ def table_name
84
+ @table_name ||= subject_class.table_name
85
+ end
86
+
87
+ def indexes
88
+ @indexes ||= ::ActiveRecord::Base.connection.indexes(table_name)
89
+ end
90
+
91
+ def expectation
92
+ "have #{index_type} index on #{table_name} for #{@column.inspect}"
93
+ end
94
+
95
+ def load_options(options)
96
+ @options = {
97
+ :unique => nil
98
+ }.merge(options.extract_options!)
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,52 @@
1
+ module Remarkable # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers # :nodoc:
4
+ class AllowMassAssignmentOfMatcher < Remarkable::Matcher::Base
5
+ def initialize(*attributes)
6
+ attributes.extract_options!
7
+ @attributes = attributes
8
+ end
9
+
10
+ def matches?(subject)
11
+ @subject = subject
12
+
13
+ assert_matcher_for(@attributes) do |attribute|
14
+ @attribute = attribute
15
+ allowed_to_mass_update?
16
+ end
17
+ end
18
+
19
+ def description
20
+ "allow mass assignment of #{@attributes.to_sentence}"
21
+ end
22
+
23
+ private
24
+
25
+ def allowed_to_mass_update?
26
+ attribute = @attribute.to_sym
27
+ protected = subject_class.protected_attributes || []
28
+ accessible = subject_class.accessible_attributes || []
29
+
30
+ return true unless protected.include?(attribute.to_s)
31
+ return true unless accessible.empty? || accessible.include?(attribute.to_s)
32
+
33
+ @missing = accessible.empty? ? "#{subject_class} is protecting #{protected.to_a.to_sentence}" :
34
+ "#{subject_class} has not made #{attribute} accessible"
35
+ return false
36
+ end
37
+
38
+ def expectation
39
+ "to allow mass assignment of #{@attribute}"
40
+ end
41
+ end
42
+
43
+ # Ensures that the attribute can be set on mass update.
44
+ #
45
+ # it { should allow_mass_assignment_of(:email, :name) }
46
+ #
47
+ def allow_mass_assignment_of(*attributes)
48
+ AllowMassAssignmentOfMatcher.new(*attributes)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,83 @@
1
+ module Remarkable # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers # :nodoc:
4
+ class EnsureValueInListMatcher < Remarkable::Matcher::Base
5
+ include Remarkable::ActiveRecord::Helpers
6
+
7
+ def initialize(attribute, behavior, *good_values)
8
+ @behavior = behavior
9
+ load_options(good_values.extract_options!)
10
+
11
+ @attribute = attribute
12
+ @good_values = good_values
13
+ end
14
+
15
+ def matches?(subject)
16
+ @subject = subject
17
+
18
+ assert_matcher_for(@good_values) do |good_value|
19
+ @good_value = good_value
20
+ value_valid?
21
+ end &&
22
+ assert_matcher do
23
+ @good_value = 'nil'
24
+ allow_nil?
25
+ end &&
26
+ assert_matcher do
27
+ @good_value = 'blank'
28
+ allow_blank?
29
+ end
30
+ end
31
+
32
+ def description
33
+ values = @good_values.dup
34
+ values << 'nil' if @options[:allow_nil]
35
+ values << 'blank' if @options[:allow_blank]
36
+
37
+ if @behavior == :invalid
38
+ "allow #{@attribute} to be set to #{values.to_sentence}"
39
+ else
40
+ "ensure #{@behavior} of #{values.to_sentence} in #{@attribute}"
41
+ end
42
+ end
43
+
44
+ def failure_message
45
+ "Expected to #{expectation} (#{@missing})"
46
+ end
47
+
48
+ def negative_failure_message
49
+ "Did not expect to #{expectation}"
50
+ end
51
+
52
+ private
53
+
54
+ def load_options(options = {})
55
+ @options = {
56
+ :message => @behavior
57
+ }.merge(options)
58
+ end
59
+
60
+ def value_valid?
61
+ if @behavior == :exclusion
62
+ return true if bad?(@good_value)
63
+ @missing = "#{@attribute} can be set to #{@good_value.inspect}"
64
+ else
65
+ return true if good?(@good_value)
66
+ @missing = "#{@attribute} cannot be set to #{@good_value.inspect}"
67
+ end
68
+
69
+ false
70
+ end
71
+
72
+ def expectation
73
+ if @behavior == :invalid
74
+ "allow #{@attribute} to be set to #{@good_value.inspect}"
75
+ else
76
+ "validate #{@behavior} of #{@good_value.inspect} in #{@attribute}"
77
+ end
78
+ end
79
+ end
80
+
81
+ end
82
+ end
83
+ end