faster_shoulda 2.11.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (259) hide show
  1. data/CONTRIBUTION_GUIDELINES.rdoc +10 -0
  2. data/MIT-LICENSE +22 -0
  3. data/README.rdoc +154 -0
  4. data/Rakefile +74 -0
  5. data/bin/convert_to_should_syntax +42 -0
  6. data/lib/shoulda/action_controller/macros.rb +221 -0
  7. data/lib/shoulda/action_controller/matchers/assign_to_matcher.rb +114 -0
  8. data/lib/shoulda/action_controller/matchers/filter_param_matcher.rb +74 -0
  9. data/lib/shoulda/action_controller/matchers/redirect_to_matcher.rb +62 -0
  10. data/lib/shoulda/action_controller/matchers/render_template_matcher.rb +54 -0
  11. data/lib/shoulda/action_controller/matchers/render_with_layout_matcher.rb +99 -0
  12. data/lib/shoulda/action_controller/matchers/respond_with_content_type_matcher.rb +74 -0
  13. data/lib/shoulda/action_controller/matchers/respond_with_matcher.rb +85 -0
  14. data/lib/shoulda/action_controller/matchers/route_matcher.rb +93 -0
  15. data/lib/shoulda/action_controller/matchers/set_session_matcher.rb +98 -0
  16. data/lib/shoulda/action_controller/matchers/set_the_flash_matcher.rb +100 -0
  17. data/lib/shoulda/action_controller/matchers.rb +39 -0
  18. data/lib/shoulda/action_controller.rb +34 -0
  19. data/lib/shoulda/action_mailer/assertions.rb +42 -0
  20. data/lib/shoulda/action_mailer/matchers/have_sent_email.rb +110 -0
  21. data/lib/shoulda/action_mailer/matchers.rb +22 -0
  22. data/lib/shoulda/action_mailer.rb +13 -0
  23. data/lib/shoulda/active_record/assertions.rb +69 -0
  24. data/lib/shoulda/active_record/helpers.rb +32 -0
  25. data/lib/shoulda/active_record/macros.rb +457 -0
  26. data/lib/shoulda/active_record/matchers/allow_mass_assignment_of_matcher.rb +83 -0
  27. data/lib/shoulda/active_record/matchers/allow_value_matcher.rb +110 -0
  28. data/lib/shoulda/active_record/matchers/association_matcher.rb +226 -0
  29. data/lib/shoulda/active_record/matchers/ensure_inclusion_of_matcher.rb +87 -0
  30. data/lib/shoulda/active_record/matchers/ensure_length_of_matcher.rb +141 -0
  31. data/lib/shoulda/active_record/matchers/have_db_column_matcher.rb +169 -0
  32. data/lib/shoulda/active_record/matchers/have_db_index_matcher.rb +112 -0
  33. data/lib/shoulda/active_record/matchers/have_readonly_attribute_matcher.rb +59 -0
  34. data/lib/shoulda/active_record/matchers/validate_acceptance_of_matcher.rb +41 -0
  35. data/lib/shoulda/active_record/matchers/validate_format_of_matcher.rb +65 -0
  36. data/lib/shoulda/active_record/matchers/validate_numericality_of_matcher.rb +39 -0
  37. data/lib/shoulda/active_record/matchers/validate_presence_of_matcher.rb +60 -0
  38. data/lib/shoulda/active_record/matchers/validate_uniqueness_of_matcher.rb +148 -0
  39. data/lib/shoulda/active_record/matchers/validation_matcher.rb +56 -0
  40. data/lib/shoulda/active_record/matchers.rb +42 -0
  41. data/lib/shoulda/active_record.rb +16 -0
  42. data/lib/shoulda/assertions.rb +79 -0
  43. data/lib/shoulda/autoload_macros.rb +46 -0
  44. data/lib/shoulda/context.rb +537 -0
  45. data/lib/shoulda/helpers.rb +8 -0
  46. data/lib/shoulda/integrations/rspec.rb +13 -0
  47. data/lib/shoulda/integrations/rspec2.rb +22 -0
  48. data/lib/shoulda/integrations/test_unit.rb +22 -0
  49. data/lib/shoulda/macros.rb +161 -0
  50. data/lib/shoulda/private_helpers.rb +13 -0
  51. data/lib/shoulda/proc_extensions.rb +14 -0
  52. data/lib/shoulda/rails.rb +8 -0
  53. data/lib/shoulda/tasks/list_tests.rake +29 -0
  54. data/lib/shoulda/tasks/yaml_to_shoulda.rake +28 -0
  55. data/lib/shoulda/tasks.rb +3 -0
  56. data/lib/shoulda/version.rb +4 -0
  57. data/lib/shoulda.rb +9 -0
  58. data/rails/init.rb +8 -0
  59. data/test/README +36 -0
  60. data/test/fail_macros.rb +55 -0
  61. data/test/fixtures/addresses.yml +3 -0
  62. data/test/fixtures/friendships.yml +0 -0
  63. data/test/fixtures/posts.yml +5 -0
  64. data/test/fixtures/products.yml +0 -0
  65. data/test/fixtures/taggings.yml +0 -0
  66. data/test/fixtures/tags.yml +9 -0
  67. data/test/fixtures/users.yml +6 -0
  68. data/test/functional/posts_controller_test.rb +121 -0
  69. data/test/functional/users_controller_test.rb +19 -0
  70. data/test/hello_should.rb +37 -0
  71. data/test/matchers/action_mailer/have_sent_email_test.rb +76 -0
  72. data/test/matchers/active_record/allow_mass_assignment_of_matcher_test.rb +74 -0
  73. data/test/matchers/active_record/allow_value_matcher_test.rb +64 -0
  74. data/test/matchers/active_record/association_matcher_test.rb +263 -0
  75. data/test/matchers/active_record/ensure_inclusion_of_matcher_test.rb +81 -0
  76. data/test/matchers/active_record/ensure_length_of_matcher_test.rb +158 -0
  77. data/test/matchers/active_record/have_db_column_matcher_test.rb +169 -0
  78. data/test/matchers/active_record/have_db_index_matcher_test.rb +91 -0
  79. data/test/matchers/active_record/have_readonly_attributes_matcher_test.rb +29 -0
  80. data/test/matchers/active_record/validate_acceptance_of_matcher_test.rb +44 -0
  81. data/test/matchers/active_record/validate_format_of_matcher_test.rb +39 -0
  82. data/test/matchers/active_record/validate_numericality_of_matcher_test.rb +52 -0
  83. data/test/matchers/active_record/validate_presence_of_matcher_test.rb +86 -0
  84. data/test/matchers/active_record/validate_uniqueness_of_matcher_test.rb +147 -0
  85. data/test/matchers/controller/assign_to_matcher_test.rb +55 -0
  86. data/test/matchers/controller/filter_param_matcher_test.rb +40 -0
  87. data/test/matchers/controller/redirect_to_matcher_test.rb +37 -0
  88. data/test/matchers/controller/render_template_matcher_test.rb +37 -0
  89. data/test/matchers/controller/render_with_layout_matcher_test.rb +47 -0
  90. data/test/matchers/controller/respond_with_content_type_matcher_test.rb +32 -0
  91. data/test/matchers/controller/respond_with_matcher_test.rb +96 -0
  92. data/test/matchers/controller/route_matcher_test.rb +75 -0
  93. data/test/matchers/controller/set_session_matcher_test.rb +48 -0
  94. data/test/matchers/controller/set_the_flash_matcher.rb +95 -0
  95. data/test/other/autoload_macro_test.rb +18 -0
  96. data/test/other/context_test.rb +372 -0
  97. data/test/other/convert_to_should_syntax_test.rb +63 -0
  98. data/test/other/helpers_test.rb +317 -0
  99. data/test/other/private_helpers_test.rb +32 -0
  100. data/test/other/should_test.rb +271 -0
  101. data/test/rails2_model_builder.rb +130 -0
  102. data/test/rails2_root/app/controllers/application_controller.rb +22 -0
  103. data/test/rails2_root/app/controllers/posts_controller.rb +87 -0
  104. data/test/rails2_root/app/controllers/users_controller.rb +84 -0
  105. data/test/rails2_root/app/helpers/application_helper.rb +3 -0
  106. data/test/rails2_root/app/helpers/posts_helper.rb +2 -0
  107. data/test/rails2_root/app/helpers/users_helper.rb +2 -0
  108. data/test/rails2_root/app/models/address.rb +7 -0
  109. data/test/rails2_root/app/models/flea.rb +11 -0
  110. data/test/rails2_root/app/models/friendship.rb +4 -0
  111. data/test/rails2_root/app/models/notifier.rb +8 -0
  112. data/test/rails2_root/app/models/pets/cat.rb +7 -0
  113. data/test/rails2_root/app/models/pets/dog.rb +10 -0
  114. data/test/rails2_root/app/models/post.rb +12 -0
  115. data/test/rails2_root/app/models/product.rb +12 -0
  116. data/test/rails2_root/app/models/profile.rb +2 -0
  117. data/test/rails2_root/app/models/registration.rb +2 -0
  118. data/test/rails2_root/app/models/tag.rb +8 -0
  119. data/test/rails2_root/app/models/tagging.rb +4 -0
  120. data/test/rails2_root/app/models/treat.rb +3 -0
  121. data/test/rails2_root/app/models/user.rb +32 -0
  122. data/test/rails2_root/app/views/layouts/posts.rhtml +19 -0
  123. data/test/rails2_root/app/views/layouts/users.rhtml +17 -0
  124. data/test/rails2_root/app/views/layouts/wide.html.erb +1 -0
  125. data/test/rails2_root/app/views/notifier/the_email.html.erb +1 -0
  126. data/test/rails2_root/app/views/posts/edit.rhtml +27 -0
  127. data/test/rails2_root/app/views/posts/index.rhtml +25 -0
  128. data/test/rails2_root/app/views/posts/new.rhtml +26 -0
  129. data/test/rails2_root/app/views/posts/show.rhtml +18 -0
  130. data/test/rails2_root/app/views/users/edit.rhtml +22 -0
  131. data/test/rails2_root/app/views/users/index.rhtml +22 -0
  132. data/test/rails2_root/app/views/users/new.rhtml +21 -0
  133. data/test/rails2_root/app/views/users/show.rhtml +13 -0
  134. data/test/rails2_root/config/boot.rb +110 -0
  135. data/test/rails2_root/config/database.yml +4 -0
  136. data/test/rails2_root/config/environment.rb +17 -0
  137. data/test/rails2_root/config/environments/test.rb +23 -0
  138. data/test/rails2_root/config/initializers/new_rails_defaults.rb +15 -0
  139. data/test/rails2_root/config/initializers/shoulda.rb +8 -0
  140. data/test/rails2_root/config/routes.rb +6 -0
  141. data/test/rails2_root/db/migrate/001_create_users.rb +19 -0
  142. data/test/rails2_root/db/migrate/002_create_posts.rb +13 -0
  143. data/test/rails2_root/db/migrate/003_create_taggings.rb +12 -0
  144. data/test/rails2_root/db/migrate/004_create_tags.rb +11 -0
  145. data/test/rails2_root/db/migrate/005_create_dogs.rb +12 -0
  146. data/test/rails2_root/db/migrate/006_create_addresses.rb +14 -0
  147. data/test/rails2_root/db/migrate/007_create_fleas.rb +11 -0
  148. data/test/rails2_root/db/migrate/008_create_dogs_fleas.rb +12 -0
  149. data/test/rails2_root/db/migrate/009_create_products.rb +17 -0
  150. data/test/rails2_root/db/migrate/010_create_friendships.rb +14 -0
  151. data/test/rails2_root/db/migrate/011_create_treats.rb +12 -0
  152. data/test/rails2_root/db/migrate/20090506203502_create_profiles.rb +12 -0
  153. data/test/rails2_root/db/migrate/20090506203536_create_registrations.rb +14 -0
  154. data/test/rails2_root/db/migrate/20090513104502_create_cats.rb +12 -0
  155. data/test/rails2_root/db/schema.rb +0 -0
  156. data/test/rails2_root/public/404.html +30 -0
  157. data/test/rails2_root/public/422.html +30 -0
  158. data/test/rails2_root/public/500.html +30 -0
  159. data/test/rails2_root/script/console +3 -0
  160. data/test/rails2_root/script/generate +3 -0
  161. data/test/rails2_root/test/shoulda_macros/custom_macro.rb +6 -0
  162. data/test/rails2_root/vendor/gems/gem_with_macro-0.0.1/shoulda_macros/gem_macro.rb +6 -0
  163. data/test/rails2_root/vendor/plugins/plugin_with_macro/shoulda_macros/plugin_macro.rb +6 -0
  164. data/test/rails2_test_helper.rb +6 -0
  165. data/test/rails3_model_builder.rb +118 -0
  166. data/test/rails3_root/Gemfile +28 -0
  167. data/test/rails3_root/README +244 -0
  168. data/test/rails3_root/Rakefile +10 -0
  169. data/test/rails3_root/app/controllers/application_controller.rb +22 -0
  170. data/test/rails3_root/app/controllers/posts_controller.rb +87 -0
  171. data/test/rails3_root/app/controllers/users_controller.rb +82 -0
  172. data/test/rails3_root/app/helpers/application_helper.rb +2 -0
  173. data/test/rails3_root/app/models/address.rb +7 -0
  174. data/test/rails3_root/app/models/flea.rb +11 -0
  175. data/test/rails3_root/app/models/friendship.rb +4 -0
  176. data/test/rails3_root/app/models/notifier.rb +8 -0
  177. data/test/rails3_root/app/models/pets/cat.rb +7 -0
  178. data/test/rails3_root/app/models/pets/dog.rb +10 -0
  179. data/test/rails3_root/app/models/post.rb +12 -0
  180. data/test/rails3_root/app/models/product.rb +12 -0
  181. data/test/rails3_root/app/models/profile.rb +2 -0
  182. data/test/rails3_root/app/models/registration.rb +2 -0
  183. data/test/rails3_root/app/models/tag.rb +8 -0
  184. data/test/rails3_root/app/models/tagging.rb +4 -0
  185. data/test/rails3_root/app/models/treat.rb +3 -0
  186. data/test/rails3_root/app/models/user.rb +32 -0
  187. data/test/rails3_root/app/views/layouts/application.html.erb +14 -0
  188. data/test/rails3_root/app/views/layouts/posts.rhtml +19 -0
  189. data/test/rails3_root/app/views/layouts/users.rhtml +17 -0
  190. data/test/rails3_root/app/views/layouts/wide.html.erb +1 -0
  191. data/test/rails3_root/app/views/notifier/the_email.html.erb +1 -0
  192. data/test/rails3_root/app/views/posts/edit.rhtml +27 -0
  193. data/test/rails3_root/app/views/posts/index.rhtml +25 -0
  194. data/test/rails3_root/app/views/posts/new.rhtml +24 -0
  195. data/test/rails3_root/app/views/posts/show.rhtml +18 -0
  196. data/test/rails3_root/app/views/users/edit.rhtml +22 -0
  197. data/test/rails3_root/app/views/users/index.rhtml +22 -0
  198. data/test/rails3_root/app/views/users/new.rhtml +21 -0
  199. data/test/rails3_root/app/views/users/show.rhtml +13 -0
  200. data/test/rails3_root/config/application.rb +46 -0
  201. data/test/rails3_root/config/boot.rb +6 -0
  202. data/test/rails3_root/config/database.yml +22 -0
  203. data/test/rails3_root/config/environment.rb +5 -0
  204. data/test/rails3_root/config/environments/development.rb +19 -0
  205. data/test/rails3_root/config/environments/production.rb +42 -0
  206. data/test/rails3_root/config/environments/test.rb +32 -0
  207. data/test/rails3_root/config/initializers/backtrace_silencers.rb +7 -0
  208. data/test/rails3_root/config/initializers/inflections.rb +10 -0
  209. data/test/rails3_root/config/initializers/mime_types.rb +5 -0
  210. data/test/rails3_root/config/initializers/secret_token.rb +7 -0
  211. data/test/rails3_root/config/initializers/session_store.rb +8 -0
  212. data/test/rails3_root/config/locales/en.yml +5 -0
  213. data/test/rails3_root/config/routes.rb +4 -0
  214. data/test/rails3_root/config.ru +4 -0
  215. data/test/rails3_root/db/migrate/001_create_users.rb +19 -0
  216. data/test/rails3_root/db/migrate/002_create_posts.rb +13 -0
  217. data/test/rails3_root/db/migrate/003_create_taggings.rb +12 -0
  218. data/test/rails3_root/db/migrate/004_create_tags.rb +11 -0
  219. data/test/rails3_root/db/migrate/005_create_dogs.rb +12 -0
  220. data/test/rails3_root/db/migrate/006_create_addresses.rb +14 -0
  221. data/test/rails3_root/db/migrate/007_create_fleas.rb +11 -0
  222. data/test/rails3_root/db/migrate/008_create_dogs_fleas.rb +12 -0
  223. data/test/rails3_root/db/migrate/009_create_products.rb +17 -0
  224. data/test/rails3_root/db/migrate/010_create_friendships.rb +14 -0
  225. data/test/rails3_root/db/migrate/011_create_treats.rb +12 -0
  226. data/test/rails3_root/db/migrate/20090506203502_create_profiles.rb +12 -0
  227. data/test/rails3_root/db/migrate/20090506203536_create_registrations.rb +14 -0
  228. data/test/rails3_root/db/migrate/20090513104502_create_cats.rb +12 -0
  229. data/test/rails3_root/db/seeds.rb +7 -0
  230. data/test/rails3_root/public/404.html +26 -0
  231. data/test/rails3_root/public/422.html +26 -0
  232. data/test/rails3_root/public/500.html +26 -0
  233. data/test/rails3_root/public/favicon.ico +0 -0
  234. data/test/rails3_root/public/images/rails.png +0 -0
  235. data/test/rails3_root/public/index.html +279 -0
  236. data/test/rails3_root/public/javascripts/application.js +2 -0
  237. data/test/rails3_root/public/javascripts/controls.js +965 -0
  238. data/test/rails3_root/public/javascripts/dragdrop.js +974 -0
  239. data/test/rails3_root/public/javascripts/effects.js +1123 -0
  240. data/test/rails3_root/public/javascripts/prototype.js +4874 -0
  241. data/test/rails3_root/public/javascripts/rails.js +118 -0
  242. data/test/rails3_root/public/robots.txt +5 -0
  243. data/test/rails3_root/script/rails +9 -0
  244. data/test/rails3_root/test/performance/browsing_test.rb +9 -0
  245. data/test/rails3_root/test/test_helper.rb +13 -0
  246. data/test/rails3_test_helper.rb +6 -0
  247. data/test/rspec_test.rb +207 -0
  248. data/test/test_helper.rb +36 -0
  249. data/test/unit/address_test.rb +10 -0
  250. data/test/unit/cat_test.rb +7 -0
  251. data/test/unit/dog_test.rb +9 -0
  252. data/test/unit/flea_test.rb +14 -0
  253. data/test/unit/friendship_test.rb +6 -0
  254. data/test/unit/post_test.rb +15 -0
  255. data/test/unit/product_test.rb +23 -0
  256. data/test/unit/tag_test.rb +11 -0
  257. data/test/unit/tagging_test.rb +6 -0
  258. data/test/unit/user_test.rb +46 -0
  259. metadata +325 -0
@@ -0,0 +1,112 @@
1
+ module Shoulda # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensures that there are DB indices on the given columns or tuples of
6
+ # columns.
7
+ #
8
+ # Options:
9
+ # * <tt>unique</tt> - whether or not the index has a unique
10
+ # constraint. Use <tt>true</tt> to explicitly test for a unique
11
+ # constraint. Use <tt>false</tt> to explicitly test for a non-unique
12
+ # constraint. Use <tt>nil</tt> if you don't care whether the index is
13
+ # unique or not. Default = <tt>nil</tt>
14
+ #
15
+ # Examples:
16
+ #
17
+ # it { should have_db_index(:age) }
18
+ # it { should have_db_index([:commentable_type, :commentable_id]) }
19
+ # it { should have_db_index(:ssn).unique(true) }
20
+ #
21
+ def have_db_index(columns)
22
+ HaveDbIndexMatcher.new(:have_index, columns)
23
+ end
24
+
25
+ class HaveDbIndexMatcher # :nodoc:
26
+ def initialize(macro, columns)
27
+ @macro = macro
28
+ @columns = normalize_columns_to_array(columns)
29
+ end
30
+
31
+ def unique(unique)
32
+ @unique = unique
33
+ self
34
+ end
35
+
36
+ def matches?(subject)
37
+ @subject = subject
38
+ index_exists? && correct_unique?
39
+ end
40
+
41
+ def failure_message
42
+ "Expected #{expectation} (#{@missing})"
43
+ end
44
+
45
+ def negative_failure_message
46
+ "Did not expect #{expectation}"
47
+ end
48
+
49
+ def description
50
+ "have a #{index_type} index on columns #{@columns.join(' and ')}"
51
+ end
52
+
53
+ protected
54
+
55
+ def index_exists?
56
+ ! matched_index.nil?
57
+ end
58
+
59
+ def correct_unique?
60
+ return true if @unique.nil?
61
+ if matched_index.unique == @unique
62
+ true
63
+ else
64
+ @missing = "#{table_name} has an index named #{matched_index.name} " <<
65
+ "of unique #{matched_index.unique}, not #{@unique}."
66
+ false
67
+ end
68
+ end
69
+
70
+ def matched_index
71
+ indexes.detect { |each| each.columns == @columns }
72
+ end
73
+
74
+ def model_class
75
+ @subject.class
76
+ end
77
+
78
+ def table_name
79
+ model_class.table_name
80
+ end
81
+
82
+ def indexes
83
+ ::ActiveRecord::Base.connection.indexes(table_name)
84
+ end
85
+
86
+ def expectation
87
+ expected = "#{model_class.name} to #{description}"
88
+ end
89
+
90
+ def index_type
91
+ case @unique
92
+ when nil
93
+ ''
94
+ when false
95
+ 'non-unique'
96
+ else
97
+ 'unique'
98
+ end
99
+ end
100
+
101
+ def normalize_columns_to_array(columns)
102
+ if columns.class == Array
103
+ columns.collect { |each| each.to_s }
104
+ else
105
+ [columns.to_s]
106
+ end
107
+ end
108
+ end
109
+
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,59 @@
1
+ module Shoulda # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensures that the attribute cannot be changed once the record has been
6
+ # created.
7
+ #
8
+ # it { should have_readonly_attributes(:password) }
9
+ #
10
+ def have_readonly_attribute(value)
11
+ HaveReadonlyAttributeMatcher.new(value)
12
+ end
13
+
14
+ class HaveReadonlyAttributeMatcher # :nodoc:
15
+
16
+ def initialize(attribute)
17
+ @attribute = attribute.to_s
18
+ end
19
+
20
+ def matches?(subject)
21
+ @subject = subject
22
+ if readonly_attributes.include?(@attribute)
23
+ @negative_failure_message =
24
+ "Did not expect #{@attribute} to be read-only"
25
+ true
26
+ else
27
+ if readonly_attributes.empty?
28
+ @failure_message = "#{class_name} attribute #{@attribute} " <<
29
+ "is not read-only"
30
+ else
31
+ @failure_message = "#{class_name} is making " <<
32
+ "#{readonly_attributes.to_sentence} " <<
33
+ "read-only, but not #{@attribute}."
34
+ end
35
+ false
36
+ end
37
+ end
38
+
39
+ attr_reader :failure_message, :negative_failure_message
40
+
41
+ def description
42
+ "make #{@attribute} read-only"
43
+ end
44
+
45
+ private
46
+
47
+ def readonly_attributes
48
+ @readonly_attributes ||= (@subject.class.readonly_attributes || [])
49
+ end
50
+
51
+ def class_name
52
+ @subject.class.name
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,41 @@
1
+ module Shoulda # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensures that the model cannot be saved the given attribute is not
6
+ # accepted.
7
+ #
8
+ # Options:
9
+ # * <tt>with_message</tt> - value the test expects to find in
10
+ # <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
11
+ # translation for <tt>:accepted</tt>.
12
+ #
13
+ # Example:
14
+ # it { should validate_acceptance_of(:eula) }
15
+ #
16
+ def validate_acceptance_of(attr)
17
+ ValidateAcceptanceOfMatcher.new(attr)
18
+ end
19
+
20
+ class ValidateAcceptanceOfMatcher < ValidationMatcher # :nodoc:
21
+
22
+ def with_message(message)
23
+ @expected_message = message if message
24
+ self
25
+ end
26
+
27
+ def matches?(subject)
28
+ super(subject)
29
+ @expected_message ||= :accepted
30
+ disallows_value_of(false, @expected_message)
31
+ end
32
+
33
+ def description
34
+ "require #{@attribute} to be accepted"
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,65 @@
1
+ module Shoulda # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensures that the model is not valid if the given attribute is not
6
+ # formatted correctly.
7
+ #
8
+ # Options:
9
+ # * <tt>with_message</tt> - value the test expects to find in
10
+ # <tt>errors.on(:attribute)</tt>. <tt>Regexp</tt> or <tt>String</tt>.
11
+ # Defaults to the translation for <tt>:blank</tt>.
12
+ # * <tt>with(string to test against)</tt>
13
+ # * <tt>not_with(string to test against)</tt>
14
+ #
15
+ # Examples:
16
+ # it { should validate_format_of(:name).
17
+ # with('12345').
18
+ # with_message(/is not optional/) }
19
+ # it { should validate_format_of(:name).
20
+ # not_with('12D45').
21
+ # with_message(/is not optional/) }
22
+ #
23
+ def validate_format_of(attr)
24
+ ValidateFormatOfMatcher.new(attr)
25
+ end
26
+
27
+ class ValidateFormatOfMatcher < ValidationMatcher # :nodoc:
28
+
29
+ def initialize(attribute)
30
+ super
31
+ end
32
+
33
+ def with_message(message)
34
+ @expected_message = message if message
35
+ self
36
+ end
37
+
38
+ def with(value)
39
+ raise "You may not call both with and not_with" if @value_to_fail
40
+ @value_to_pass = value
41
+ self
42
+ end
43
+
44
+ def not_with(value)
45
+ raise "You may not call both with and not_with" if @value_to_pass
46
+ @value_to_fail = value
47
+ self
48
+ end
49
+
50
+ def matches?(subject)
51
+ super(subject)
52
+ @expected_message ||= :blank
53
+ return disallows_value_of(@value_to_fail, @expected_message) if @value_to_fail
54
+ allows_value_of(@value_to_pass, @expected_message) if @value_to_pass
55
+ end
56
+
57
+ def description
58
+ "#{@attribute} have a valid format"
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,39 @@
1
+ module Shoulda # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensure that the attribute is numeric
6
+ #
7
+ # Options:
8
+ # * <tt>with_message</tt> - value the test expects to find in
9
+ # <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
10
+ # translation for <tt>:not_a_number</tt>.
11
+ #
12
+ # Example:
13
+ # it { should validate_numericality_of(:age) }
14
+ #
15
+ def validate_numericality_of(attr)
16
+ ValidateNumericalityOfMatcher.new(attr)
17
+ end
18
+
19
+ class ValidateNumericalityOfMatcher < ValidationMatcher # :nodoc:
20
+
21
+ def with_message(message)
22
+ @expected_message = message if message
23
+ self
24
+ end
25
+
26
+ def matches?(subject)
27
+ super(subject)
28
+ @expected_message ||= :not_a_number
29
+ disallows_value_of('abcd', @expected_message)
30
+ end
31
+
32
+ def description
33
+ "only allow numeric values for #{@attribute}"
34
+ end
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,60 @@
1
+ module Shoulda # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensures that the model is not valid if the given attribute is not
6
+ # present.
7
+ #
8
+ # Options:
9
+ # * <tt>with_message</tt> - value the test expects to find in
10
+ # <tt>errors.on(:attribute)</tt>. <tt>Regexp</tt> or <tt>String</tt>.
11
+ # Defaults to the translation for <tt>:blank</tt>.
12
+ #
13
+ # Examples:
14
+ # it { should validate_presence_of(:name) }
15
+ # it { should validate_presence_of(:name).
16
+ # with_message(/is not optional/) }
17
+ #
18
+ def validate_presence_of(attr)
19
+ ValidatePresenceOfMatcher.new(attr)
20
+ end
21
+
22
+ class ValidatePresenceOfMatcher < ValidationMatcher # :nodoc:
23
+
24
+ def with_message(message)
25
+ @expected_message = message if message
26
+ self
27
+ end
28
+
29
+ def matches?(subject)
30
+ super(subject)
31
+ @expected_message ||= :blank
32
+ disallows_value_of(blank_value, @expected_message)
33
+ end
34
+
35
+ def description
36
+ "require #{@attribute} to be set"
37
+ end
38
+
39
+ private
40
+
41
+ def blank_value
42
+ if collection?
43
+ []
44
+ else
45
+ nil
46
+ end
47
+ end
48
+
49
+ def collection?
50
+ if reflection = @subject.class.reflect_on_association(@attribute)
51
+ [:has_many, :has_and_belongs_to_many].include?(reflection.macro)
52
+ else
53
+ false
54
+ end
55
+ end
56
+ end
57
+
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,148 @@
1
+ module Shoulda # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensures that the model is invalid if the given attribute is not unique.
6
+ #
7
+ # Internally, this uses values from existing records to test validations,
8
+ # so this will always fail if you have not saved at least one record for
9
+ # the model being tested, like so:
10
+ #
11
+ # describe User do
12
+ # before(:each) { User.create!(:email => 'address@example.com') }
13
+ # it { should validate_uniqueness_of(:email) }
14
+ # end
15
+ #
16
+ # Options:
17
+ #
18
+ # * <tt>with_message</tt> - value the test expects to find in
19
+ # <tt>errors.on(:attribute)</tt>. <tt>Regexp</tt> or <tt>String</tt>.
20
+ # Defaults to the translation for <tt>:taken</tt>.
21
+ # * <tt>scoped_to</tt> - field(s) to scope the uniqueness to.
22
+ # * <tt>case_insensitive</tt> - ensures that the validation does not
23
+ # check case. Off by default. Ignored by non-text attributes.
24
+ #
25
+ # Examples:
26
+ # it { should validate_uniqueness_of(:keyword) }
27
+ # it { should validate_uniqueness_of(:keyword).with_message(/dup/) }
28
+ # it { should validate_uniqueness_of(:email).scoped_to(:name) }
29
+ # it { should validate_uniqueness_of(:email).
30
+ # scoped_to(:first_name, :last_name) }
31
+ # it { should validate_uniqueness_of(:keyword).case_insensitive }
32
+ #
33
+ def validate_uniqueness_of(attr)
34
+ ValidateUniquenessOfMatcher.new(attr)
35
+ end
36
+
37
+ class ValidateUniquenessOfMatcher < ValidationMatcher # :nodoc:
38
+ include Helpers
39
+
40
+ def initialize(attribute)
41
+ @attribute = attribute
42
+ end
43
+
44
+ def scoped_to(*scopes)
45
+ @scopes = [*scopes].flatten
46
+ self
47
+ end
48
+
49
+ def with_message(message)
50
+ @expected_message = message
51
+ self
52
+ end
53
+
54
+ def case_insensitive
55
+ @case_insensitive = true
56
+ self
57
+ end
58
+
59
+ def description
60
+ result = "require "
61
+ result << "case sensitive " unless @case_insensitive
62
+ result << "unique value for #{@attribute}"
63
+ result << " scoped to #{@scopes.join(', ')}" unless @scopes.blank?
64
+ result
65
+ end
66
+
67
+ def matches?(subject)
68
+ @subject = subject.class.new
69
+ @expected_message ||= :taken
70
+ find_existing &&
71
+ set_scoped_attributes &&
72
+ validate_attribute &&
73
+ validate_after_scope_change
74
+ end
75
+
76
+ private
77
+
78
+ def find_existing
79
+ if @existing = @subject.class.find(:first)
80
+ true
81
+ else
82
+ @failure_message = "Can't find first #{class_name}"
83
+ false
84
+ end
85
+ end
86
+
87
+ def set_scoped_attributes
88
+ unless @scopes.blank?
89
+ @scopes.each do |scope|
90
+ setter = :"#{scope}="
91
+ unless @subject.respond_to?(setter)
92
+ @failure_message =
93
+ "#{class_name} doesn't seem to have a #{scope} attribute."
94
+ return false
95
+ end
96
+ @subject.send("#{scope}=", @existing.send(scope))
97
+ end
98
+ end
99
+ true
100
+ end
101
+
102
+ def validate_attribute
103
+ disallows_value_of(existing_value, @expected_message)
104
+ end
105
+
106
+ # TODO: There is a chance that we could change the scoped field
107
+ # to a value that's already taken. An alternative implementation
108
+ # could actually find all values for scope and create a unique
109
+ def validate_after_scope_change
110
+ if @scopes.blank?
111
+ true
112
+ else
113
+ @scopes.all? do |scope|
114
+ previous_value = @existing.send(scope)
115
+
116
+ # Assume the scope is a foreign key if the field is nil
117
+ previous_value ||= 0
118
+
119
+ next_value = previous_value.next
120
+
121
+ @subject.send("#{scope}=", next_value)
122
+
123
+ if allows_value_of(existing_value, @expected_message)
124
+ @negative_failure_message <<
125
+ " (with different value of #{scope})"
126
+ true
127
+ else
128
+ @failure_message << " (with different value of #{scope})"
129
+ false
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ def class_name
136
+ @subject.class.name
137
+ end
138
+
139
+ def existing_value
140
+ value = @existing.send(@attribute)
141
+ value.swapcase! if @case_insensitive && value.respond_to?(:swapcase!)
142
+ value
143
+ end
144
+ end
145
+
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,56 @@
1
+ module Shoulda # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers
4
+
5
+ class ValidationMatcher # :nodoc:
6
+
7
+ attr_reader :failure_message
8
+
9
+ def initialize(attribute)
10
+ @attribute = attribute
11
+ end
12
+
13
+ def negative_failure_message
14
+ @negative_failure_message || @failure_message
15
+ end
16
+
17
+ def matches?(subject)
18
+ @subject = subject
19
+ false
20
+ end
21
+
22
+ private
23
+
24
+ def allows_value_of(value, message = nil)
25
+ allow = AllowValueMatcher.
26
+ new(value).
27
+ for(@attribute).
28
+ with_message(message)
29
+ if allow.matches?(@subject)
30
+ @negative_failure_message = allow.failure_message
31
+ true
32
+ else
33
+ @failure_message = allow.negative_failure_message
34
+ false
35
+ end
36
+ end
37
+
38
+ def disallows_value_of(value, message = nil)
39
+ disallow = AllowValueMatcher.
40
+ new(value).
41
+ for(@attribute).
42
+ with_message(message)
43
+ if disallow.matches?(@subject)
44
+ @failure_message = disallow.negative_failure_message
45
+ false
46
+ else
47
+ @negative_failure_message = disallow.failure_message
48
+ true
49
+ end
50
+ end
51
+ end
52
+
53
+ end
54
+ end
55
+ end
56
+
@@ -0,0 +1,42 @@
1
+ require 'shoulda/active_record/helpers'
2
+ require 'shoulda/active_record/matchers/validation_matcher'
3
+ require 'shoulda/active_record/matchers/allow_value_matcher'
4
+ require 'shoulda/active_record/matchers/ensure_length_of_matcher'
5
+ require 'shoulda/active_record/matchers/ensure_inclusion_of_matcher'
6
+ require 'shoulda/active_record/matchers/validate_presence_of_matcher'
7
+ require 'shoulda/active_record/matchers/validate_format_of_matcher'
8
+ require 'shoulda/active_record/matchers/validate_uniqueness_of_matcher'
9
+ require 'shoulda/active_record/matchers/validate_acceptance_of_matcher'
10
+ require 'shoulda/active_record/matchers/validate_numericality_of_matcher'
11
+ require 'shoulda/active_record/matchers/association_matcher'
12
+ require 'shoulda/active_record/matchers/have_db_column_matcher'
13
+ require 'shoulda/active_record/matchers/have_db_index_matcher'
14
+ require 'shoulda/active_record/matchers/have_readonly_attribute_matcher'
15
+ require 'shoulda/active_record/matchers/allow_mass_assignment_of_matcher'
16
+
17
+
18
+ module Shoulda # :nodoc:
19
+ module ActiveRecord # :nodoc:
20
+ # = Matchers for your active record models
21
+ #
22
+ # These matchers will test most of the validations and associations for your
23
+ # ActiveRecord models.
24
+ #
25
+ # describe User do
26
+ # it { should validate_presence_of(:name) }
27
+ # it { should validate_presence_of(:phone_number) }
28
+ # %w(abcd 1234).each do |value|
29
+ # it { should_not allow_value(value).for(:phone_number) }
30
+ # end
31
+ # it { should allow_value("(123) 456-7890").for(:phone_number) }
32
+ # it { should_not allow_mass_assignment_of(:password) }
33
+ # it { should have_one(:profile) }
34
+ # it { should have_many(:dogs) }
35
+ # it { should have_many(:messes).through(:dogs) }
36
+ # it { should belong_to(:lover) }
37
+ # end
38
+ #
39
+ module Matchers
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,16 @@
1
+ require 'shoulda'
2
+ require 'shoulda/active_record/helpers'
3
+ require 'shoulda/active_record/matchers'
4
+ require 'shoulda/active_record/assertions'
5
+ require 'shoulda/active_record/macros'
6
+
7
+ module Test # :nodoc: all
8
+ module Unit
9
+ class TestCase
10
+ include Shoulda::ActiveRecord::Helpers
11
+ include Shoulda::ActiveRecord::Matchers
12
+ include Shoulda::ActiveRecord::Assertions
13
+ extend Shoulda::ActiveRecord::Macros
14
+ end
15
+ end
16
+ end