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,172 @@
1
+ module Remarkable # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers # :nodoc:
4
+ class EnsureValueInRangeMatcher < Remarkable::Matcher::Base
5
+ include Remarkable::ActiveRecord::Helpers
6
+
7
+ def initialize(attribute, behavior, range, *options)
8
+ @attribute = attribute
9
+ @range = range
10
+ @behavior = behavior
11
+ load_options(options.extract_options!)
12
+ end
13
+
14
+ def low_message(message)
15
+ warn "[DEPRECATION] should_ensure_value_in_range.low_message is deprecated. " <<
16
+ "Use should_validate_inclusion_of.message instead."
17
+ @options[:low_message] = message
18
+ self
19
+ end
20
+
21
+ def high_message(message)
22
+ warn "[DEPRECATION] should_ensure_value_in_range.high_message is deprecated. " <<
23
+ "Use should_validate_inclusion_of.message instead."
24
+ @options[:high_message] = message
25
+ self
26
+ end
27
+
28
+ # TODO Low message and High message should be deprecated (not supported by
29
+ # Rails API). In this while, we have to hack message.
30
+ def message(message)
31
+ @options[:high_message] = message
32
+ @options[:low_message] = message
33
+ @options[:message] = message
34
+ self
35
+ end
36
+
37
+ def matches?(subject)
38
+ @subject = subject
39
+
40
+ assert_matcher do
41
+ less_than_minimum? && accepts_minimum? && more_than_minimum? && allow_nil? &&
42
+ more_than_maximum? && accepts_maximum? && less_than_maximum? && allow_blank?
43
+ end
44
+ end
45
+
46
+ def description
47
+ "ensure #{expectation}"
48
+ end
49
+
50
+ def failure_message
51
+ "Expected #{expectation} (#{@missing})"
52
+ end
53
+
54
+ def negative_failure_message
55
+ "Did not expect #{expectation}"
56
+ end
57
+
58
+ private
59
+
60
+ def expectation
61
+ message = "that the #{@attribute} is #{'not ' if @behavior == :exclusion}in #{@range}"
62
+ message << " or is nil" if @options[:allow_nil]
63
+ message << " or is blank" if @options[:allow_blank]
64
+ message
65
+ end
66
+
67
+ def less_than_minimum?
68
+ return true unless @behavior == :inclusion
69
+ return true if bad?(@range.first - 1, :low_message)
70
+
71
+ @missing = "allow #{@attribute} to be less than #{@range.first}"
72
+ return false
73
+ end
74
+
75
+ def more_than_maximum?
76
+ return true unless @behavior == :inclusion
77
+ return true if bad?(@range.last + 1, :high_message)
78
+
79
+ @missing = "allow #{@attribute} to be more than #{@range.last}"
80
+ return false
81
+ end
82
+
83
+ def less_than_maximum?
84
+ return true unless @behavior == :exclusion
85
+ return true if bad?(@range.last - 1, :low_message)
86
+
87
+ @missing = "allow #{@attribute} to be less than #{@range.last}"
88
+ return false
89
+ end
90
+
91
+ def more_than_minimum?
92
+ return true unless @behavior == :exclusion
93
+ return true if bad?(@range.first + 1, :high_message)
94
+
95
+ @missing = "allow #{@attribute} to be more than #{@range.first}"
96
+ return false
97
+ end
98
+
99
+ def accepts_minimum?
100
+ return true if boundary?(@range.first, :low_message)
101
+
102
+ @missing = create_boundary_message("allow #{@attribute} to be #{@range.first}")
103
+ return false
104
+ end
105
+
106
+ def accepts_maximum?
107
+ return true if boundary?(@range.last, :high_message)
108
+
109
+ @missing = create_boundary_message("allow #{@attribute} to be #{@range.last}")
110
+ return false
111
+ end
112
+
113
+ # Create a message based on the behavior (inclusion or exclusion).
114
+ #
115
+ # Let's suppose the following validation:
116
+ #
117
+ # validate_inclusion_of :age, 18..100
118
+ #
119
+ # If eventually :age cannot be a 100, the message should be:
120
+ #
121
+ # (not allow :age to be 100)
122
+ #
123
+ # When we have:
124
+ #
125
+ # validate_exclusion_of :age, 18..100
126
+ #
127
+ # If eventually :age can be a 100, the message should be:
128
+ #
129
+ # (allow :age to be 100)
130
+ #
131
+ def create_boundary_message(message)
132
+ if @behavior == :exclusion
133
+ message
134
+ else
135
+ "not " << message
136
+ end
137
+ end
138
+
139
+ def boundary?(value, message_sym)
140
+ if @behavior == :exclusion
141
+ bad?(value, message_sym)
142
+ else
143
+ good?(value, message_sym)
144
+ end
145
+ end
146
+
147
+ def load_options(options = {})
148
+ warn "[DEPRECATION] should_ensure_value_in_range with :low_message is deprecated. " <<
149
+ "Use should_validate_inclusion_of with :message instead." if options[:low_message]
150
+
151
+ warn "[DEPRECATION] should_ensure_value_in_range with :high_message is deprecated. " <<
152
+ "Use should_validate_inclusion_of with :message instead." if options[:high_message]
153
+
154
+ warn "[DEPRECATION] should_ensure_value_in_range is deprecated. " <<
155
+ "Use should_validate_inclusion_of instead." if options[:low_message].blank? && options[:high_message].blank?
156
+
157
+ @options = {
158
+ :low_message => @behavior,
159
+ :high_message => @behavior,
160
+ :message => @behavior
161
+ }.merge(options)
162
+ end
163
+ end
164
+
165
+
166
+ # TODO Deprecate this method, but not the matcher.
167
+ def ensure_value_in_range(attribute, range, *options) #:nodoc:
168
+ EnsureValueInRangeMatcher.new(attribute, :inclusion, range, *options)
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,54 @@
1
+ module Remarkable # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers # :nodoc:
4
+ class HaveClassMethods < Remarkable::Matcher::Base
5
+ def initialize(*methods)
6
+ methods.extract_options!
7
+ @methods = methods
8
+ end
9
+
10
+ def matches?(subject)
11
+ @subject = subject
12
+
13
+ assert_matcher_for(@methods) do |method|
14
+ @method = method
15
+ have_class_method?
16
+ end
17
+ end
18
+
19
+ def description
20
+ "respond to class method #{@methods.to_sentence}"
21
+ end
22
+
23
+ def failure_message
24
+ "Expected #{expectation} (#{@missing})"
25
+ end
26
+
27
+ def negative_failure_message
28
+ "Did not expect #{expectation}"
29
+ end
30
+
31
+ private
32
+
33
+ def have_class_method?
34
+ return true if subject_class.respond_to?(@method)
35
+
36
+ @missing = "#{subject_name} does not have class method #{@method}"
37
+ return false
38
+ end
39
+
40
+ def expectation
41
+ "#{subject_name} to respond to class method #{@method}"
42
+ end
43
+ end
44
+
45
+ # Ensure that the given class methods are defined on the model.
46
+ #
47
+ # it { should have_class_methods(:find, :destroy) }
48
+ #
49
+ def have_class_methods(*methods)
50
+ HaveClassMethods.new(*methods)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,54 @@
1
+ module Remarkable # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers # :nodoc:
4
+ class HaveInstanceMethods < Remarkable::Matcher::Base
5
+ def initialize(*methods)
6
+ methods.extract_options!
7
+ @methods = methods
8
+ end
9
+
10
+ def matches?(subject)
11
+ @subject = subject
12
+
13
+ assert_matcher_for(@methods) do |method|
14
+ @method = method
15
+ have_instance_method?
16
+ end
17
+ end
18
+
19
+ def description
20
+ "respond to instance method #{@methods.to_sentence}"
21
+ end
22
+
23
+ def failure_message
24
+ "Expected #{expectation} (#{@missing})"
25
+ end
26
+
27
+ def negative_failure_message
28
+ "Did not expect #{expectation}"
29
+ end
30
+
31
+ private
32
+
33
+ def have_instance_method?
34
+ return true if subject_class.new.respond_to?(@method)
35
+
36
+ @missing = "#{subject_name} does not have instance method #{@method}"
37
+ return false
38
+ end
39
+
40
+ def expectation
41
+ "#{subject_name} to respond to instance method #{@method}"
42
+ end
43
+ end
44
+
45
+ # Ensure that the given instance methods are defined on the model.
46
+ #
47
+ # it { should have_instance_methods(:email, :name, :name=) }
48
+ #
49
+ def have_instance_methods(*methods)
50
+ HaveInstanceMethods.new(*methods)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,94 @@
1
+ module Remarkable # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers # :nodoc:
4
+ class HaveNamedScope < Remarkable::Matcher::Base
5
+ def initialize(scope_call, *args)
6
+ @scope_opts = args.extract_options!
7
+ @scope_call = scope_call.to_s
8
+ @args = args
9
+ end
10
+
11
+ def matches?(subject)
12
+ @subject = subject
13
+
14
+ assert_matcher do
15
+ @scope = eval("#{subject_class}.#{@scope_call}")
16
+ return_scope_object? && scope_itself_to_options?
17
+ end
18
+ end
19
+
20
+ def description
21
+ "have to scope itself to #{@scope_opts.inspect} when #{@scope_call} is called"
22
+ end
23
+
24
+ def failure_message
25
+ "Expected #{expectation} (#{@missing})"
26
+ end
27
+
28
+ def negative_failure_message
29
+ "Did not expect #{expectation}"
30
+ end
31
+
32
+ private
33
+
34
+ def return_scope_object?
35
+ return true if @scope.class == ::ActiveRecord::NamedScope::Scope
36
+
37
+ @missing = "#{@scope_call} didn't return a scope object"
38
+ return false
39
+ end
40
+
41
+ def scope_itself_to_options?
42
+ return true if @scope_opts.empty?
43
+ return true if @scope.proxy_options == @scope_opts
44
+
45
+ @missing = "#{subject_name} didn't scope itself to #{@scope_opts.inspect}"
46
+ return false
47
+ end
48
+
49
+ def expectation
50
+ "#{subject_name} have to scope itself to #{@scope_opts.inspect} when #{@scope_call} is called"
51
+ end
52
+ end
53
+
54
+ # Ensures that the model has a method named scope_name that returns a NamedScope object with the
55
+ # proxy options set to the options you supply. scope_name can be either a symbol, or a method
56
+ # call which will be evaled against the model. The eval'd method call has access to all the same
57
+ # instance variables that a should statement would.
58
+ #
59
+ # Options: Any of the options that the named scope would pass on to find.
60
+ #
61
+ # Example:
62
+ #
63
+ # it { should have_named_scope(:visible, :conditions => {:visible => true}) }
64
+ #
65
+ # Passes for
66
+ #
67
+ # named_scope :visible, :conditions => {:visible => true}
68
+ #
69
+ # Or for
70
+ #
71
+ # def self.visible
72
+ # scoped(:conditions => {:visible => true})
73
+ # end
74
+ #
75
+ # You can test lambdas or methods that return ActiveRecord#scoped calls:
76
+ #
77
+ # it { should have_named_scope('recent(5)', :limit => 5) }
78
+ # it { should have_named_scope('recent(1)', :limit => 1) }
79
+ #
80
+ # Passes for
81
+ # named_scope :recent, lambda {|c| {:limit => c}}
82
+ #
83
+ # Or for
84
+ #
85
+ # def self.recent(c)
86
+ # scoped(:limit => c)
87
+ # end
88
+ #
89
+ def have_named_scope(scope_call, *args)
90
+ HaveNamedScope.new(scope_call, *args)
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,48 @@
1
+ module Remarkable # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers # :nodoc:
4
+ class HaveReadonlyAttributes < 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
+ assert_matcher_for(@attributes) do |attribute|
13
+ @attribute = attribute
14
+ attribute_readonly?
15
+ end
16
+ end
17
+
18
+ def description
19
+ "make #{@attributes.to_sentence} read-only"
20
+ end
21
+
22
+ private
23
+
24
+ def attribute_readonly?
25
+ attribute = @attribute.to_sym
26
+ readonly = subject_class.readonly_attributes || []
27
+ return true if readonly.include?(attribute.to_s)
28
+
29
+ @missing = (readonly.empty? ? "#{subject_class} attribute #{attribute} is not read-only" :
30
+ "#{subject_class} is making #{readonly.to_a.to_sentence} read-only, but not #{attribute}.")
31
+ return false
32
+ end
33
+
34
+ def expectation
35
+ "that #{@attribute} cann be changed once the record has been created"
36
+ end
37
+ end
38
+
39
+ # Ensures that the attribute cannot be changed once the record has been created.
40
+ #
41
+ # it { should have_readonly_attributes(:password, :admin_flag) }
42
+ #
43
+ def have_readonly_attributes(*attributes)
44
+ HaveReadonlyAttributes.new(*attributes)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,51 @@
1
+ module Remarkable # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers # :nodoc:
4
+ class ProtectAttributes < 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
+ protect_from_mass_updates?
16
+ end
17
+ end
18
+
19
+ def description
20
+ "protect #{@attributes.to_sentence} from mass updates"
21
+ end
22
+
23
+ private
24
+
25
+ def protect_from_mass_updates?
26
+ attribute = @attribute.to_sym
27
+ protected = subject_class.protected_attributes || []
28
+ accessible = subject_class.accessible_attributes || []
29
+
30
+ return true if protected.include?(attribute.to_s)
31
+ return true if !accessible.empty? && !accessible.include?(attribute.to_s)
32
+
33
+ @missing = accessible.empty? ? "#{subject_class} is protecting #{protected.to_a.to_sentence}, but not #{attribute}." :
34
+ "#{subject_class} has made #{attribute} accessible"
35
+ return false
36
+ end
37
+
38
+ def expectation
39
+ "that #{@attribute} cannot be set on mass update"
40
+ end
41
+ end
42
+
43
+ # TODO Deprecate this method and the matcher.
44
+ def protect_attributes(*attributes) #:nodoc:
45
+ warn "[DEPRECATION] should_protect_attributes is deprecated. " <<
46
+ "Use should_not_allow_mass_assignment_of instead."
47
+ ProtectAttributes.new(*attributes)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,79 @@
1
+ module Remarkable # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers # :nodoc:
4
+ class ValidateAcceptanceOfMatcher < Remarkable::Matcher::Base
5
+ include Remarkable::ActiveRecord::Helpers
6
+
7
+ undef_method :allow_blank, :allow_blank?
8
+
9
+ arguments :attributes
10
+ optional :accept
11
+ assertions :allow_nil?, :require_accepted?, :accept_is_valid?
12
+
13
+ def description
14
+ message = "require #{@attributes.to_sentence} to be accepted"
15
+ message << " or nil" if @options[:nil]
16
+ message
17
+ end
18
+
19
+ private
20
+
21
+ def require_accepted?
22
+ return true if bad?(false)
23
+
24
+ @missing = "not require #{@attribute} to be accepted"
25
+ return false
26
+ end
27
+
28
+ def accept_is_valid?
29
+ return true unless @options.key? :accept
30
+ return true if good?(@options[:accept])
31
+
32
+ @missing = "is not accepted when #{@attribute} is #{@options[:accept].inspect}"
33
+ false
34
+ end
35
+
36
+ # Receives a Hash
37
+ def load_options(options = {})
38
+ @options = {
39
+ :message => :accepted
40
+ }.merge(options)
41
+ end
42
+
43
+ def expectation
44
+ message = "that the #{subject_name} can be saved if #{@attribute} is accepted"
45
+ message << " or nil" if @options[:allow_nil]
46
+ message
47
+ end
48
+ end
49
+
50
+ # Ensures that the model cannot be saved if one of the attributes listed is not accepted.
51
+ #
52
+ # If an instance variable has been created in the setup named after the
53
+ # model being tested, then this method will use that. Otherwise, it will
54
+ # create a new instance to test against.
55
+ #
56
+ # Options:
57
+ # * <tt>:accept</tt> - the expected value to be accepted.
58
+ # * <tt>:allow_nil</tt> - when supplied, validates if it allows nil or not.
59
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
60
+ # Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.accepted')</tt>
61
+ #
62
+ # Example:
63
+ # it { should validate_acceptance_of(:eula, :terms) }
64
+ # it { should validate_acceptance_of(:eula, :terms, :accept => true) }
65
+ #
66
+ def validate_acceptance_of(*attributes)
67
+ ValidateAcceptanceOfMatcher.new(*attributes)
68
+ end
69
+
70
+ # TODO Deprecate this method.
71
+ def require_acceptance_of(*attributes) #:nodoc:
72
+ warn "[DEPRECATION] should_require_acceptance_of is deprecated. " <<
73
+ "Use should_validate_acceptance_of instead."
74
+ ValidateAcceptanceOfMatcher.new(*attributes)
75
+ end
76
+
77
+ end
78
+ end
79
+ end