nimboids-shoulda 2.11.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) 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 +275 -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 +441 -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/matchers/action_mailer/have_sent_email_test.rb +76 -0
  71. data/test/matchers/active_record/allow_mass_assignment_of_matcher_test.rb +74 -0
  72. data/test/matchers/active_record/allow_value_matcher_test.rb +64 -0
  73. data/test/matchers/active_record/association_matcher_test.rb +462 -0
  74. data/test/matchers/active_record/ensure_inclusion_of_matcher_test.rb +81 -0
  75. data/test/matchers/active_record/ensure_length_of_matcher_test.rb +158 -0
  76. data/test/matchers/active_record/have_db_column_matcher_test.rb +169 -0
  77. data/test/matchers/active_record/have_db_index_matcher_test.rb +91 -0
  78. data/test/matchers/active_record/have_readonly_attributes_matcher_test.rb +29 -0
  79. data/test/matchers/active_record/validate_acceptance_of_matcher_test.rb +44 -0
  80. data/test/matchers/active_record/validate_format_of_matcher_test.rb +39 -0
  81. data/test/matchers/active_record/validate_numericality_of_matcher_test.rb +52 -0
  82. data/test/matchers/active_record/validate_presence_of_matcher_test.rb +86 -0
  83. data/test/matchers/active_record/validate_uniqueness_of_matcher_test.rb +147 -0
  84. data/test/matchers/controller/assign_to_matcher_test.rb +55 -0
  85. data/test/matchers/controller/filter_param_matcher_test.rb +40 -0
  86. data/test/matchers/controller/redirect_to_matcher_test.rb +37 -0
  87. data/test/matchers/controller/render_template_matcher_test.rb +37 -0
  88. data/test/matchers/controller/render_with_layout_matcher_test.rb +47 -0
  89. data/test/matchers/controller/respond_with_content_type_matcher_test.rb +32 -0
  90. data/test/matchers/controller/respond_with_matcher_test.rb +96 -0
  91. data/test/matchers/controller/route_matcher_test.rb +75 -0
  92. data/test/matchers/controller/set_session_matcher_test.rb +48 -0
  93. data/test/matchers/controller/set_the_flash_matcher.rb +95 -0
  94. data/test/other/autoload_macro_test.rb +18 -0
  95. data/test/other/context_test.rb +372 -0
  96. data/test/other/convert_to_should_syntax_test.rb +63 -0
  97. data/test/other/helpers_test.rb +317 -0
  98. data/test/other/private_helpers_test.rb +32 -0
  99. data/test/other/should_test.rb +271 -0
  100. data/test/rails2_model_builder.rb +130 -0
  101. data/test/rails2_root/app/controllers/application_controller.rb +22 -0
  102. data/test/rails2_root/app/controllers/posts_controller.rb +87 -0
  103. data/test/rails2_root/app/controllers/users_controller.rb +84 -0
  104. data/test/rails2_root/app/helpers/application_helper.rb +3 -0
  105. data/test/rails2_root/app/helpers/posts_helper.rb +2 -0
  106. data/test/rails2_root/app/helpers/users_helper.rb +2 -0
  107. data/test/rails2_root/app/models/address.rb +7 -0
  108. data/test/rails2_root/app/models/flea.rb +11 -0
  109. data/test/rails2_root/app/models/friendship.rb +4 -0
  110. data/test/rails2_root/app/models/notifier.rb +8 -0
  111. data/test/rails2_root/app/models/pets/cat.rb +7 -0
  112. data/test/rails2_root/app/models/pets/dog.rb +10 -0
  113. data/test/rails2_root/app/models/post.rb +12 -0
  114. data/test/rails2_root/app/models/product.rb +12 -0
  115. data/test/rails2_root/app/models/profile.rb +2 -0
  116. data/test/rails2_root/app/models/registration.rb +2 -0
  117. data/test/rails2_root/app/models/tag.rb +8 -0
  118. data/test/rails2_root/app/models/tagging.rb +4 -0
  119. data/test/rails2_root/app/models/treat.rb +3 -0
  120. data/test/rails2_root/app/models/user.rb +32 -0
  121. data/test/rails2_root/app/views/layouts/posts.rhtml +19 -0
  122. data/test/rails2_root/app/views/layouts/users.rhtml +17 -0
  123. data/test/rails2_root/app/views/layouts/wide.html.erb +1 -0
  124. data/test/rails2_root/app/views/notifier/the_email.html.erb +1 -0
  125. data/test/rails2_root/app/views/posts/edit.rhtml +27 -0
  126. data/test/rails2_root/app/views/posts/index.rhtml +25 -0
  127. data/test/rails2_root/app/views/posts/new.rhtml +26 -0
  128. data/test/rails2_root/app/views/posts/show.rhtml +18 -0
  129. data/test/rails2_root/app/views/users/edit.rhtml +22 -0
  130. data/test/rails2_root/app/views/users/index.rhtml +22 -0
  131. data/test/rails2_root/app/views/users/new.rhtml +21 -0
  132. data/test/rails2_root/app/views/users/show.rhtml +13 -0
  133. data/test/rails2_root/config/boot.rb +110 -0
  134. data/test/rails2_root/config/database.yml +4 -0
  135. data/test/rails2_root/config/environment.rb +17 -0
  136. data/test/rails2_root/config/environments/test.rb +23 -0
  137. data/test/rails2_root/config/initializers/new_rails_defaults.rb +15 -0
  138. data/test/rails2_root/config/initializers/shoulda.rb +8 -0
  139. data/test/rails2_root/config/routes.rb +6 -0
  140. data/test/rails2_root/db/migrate/001_create_users.rb +19 -0
  141. data/test/rails2_root/db/migrate/002_create_posts.rb +13 -0
  142. data/test/rails2_root/db/migrate/003_create_taggings.rb +12 -0
  143. data/test/rails2_root/db/migrate/004_create_tags.rb +11 -0
  144. data/test/rails2_root/db/migrate/005_create_dogs.rb +12 -0
  145. data/test/rails2_root/db/migrate/006_create_addresses.rb +14 -0
  146. data/test/rails2_root/db/migrate/007_create_fleas.rb +11 -0
  147. data/test/rails2_root/db/migrate/008_create_dogs_fleas.rb +12 -0
  148. data/test/rails2_root/db/migrate/009_create_products.rb +17 -0
  149. data/test/rails2_root/db/migrate/010_create_friendships.rb +14 -0
  150. data/test/rails2_root/db/migrate/011_create_treats.rb +12 -0
  151. data/test/rails2_root/db/migrate/20090506203502_create_profiles.rb +12 -0
  152. data/test/rails2_root/db/migrate/20090506203536_create_registrations.rb +14 -0
  153. data/test/rails2_root/db/migrate/20090513104502_create_cats.rb +12 -0
  154. data/test/rails2_root/db/schema.rb +0 -0
  155. data/test/rails2_root/public/404.html +30 -0
  156. data/test/rails2_root/public/422.html +30 -0
  157. data/test/rails2_root/public/500.html +30 -0
  158. data/test/rails2_root/script/console +3 -0
  159. data/test/rails2_root/script/generate +3 -0
  160. data/test/rails2_root/test/shoulda_macros/custom_macro.rb +6 -0
  161. data/test/rails2_root/vendor/gems/gem_with_macro-0.0.1/shoulda_macros/gem_macro.rb +6 -0
  162. data/test/rails2_root/vendor/plugins/plugin_with_macro/shoulda_macros/plugin_macro.rb +6 -0
  163. data/test/rails2_test_helper.rb +6 -0
  164. data/test/rails3_model_builder.rb +118 -0
  165. data/test/rails3_root/Gemfile +28 -0
  166. data/test/rails3_root/README +244 -0
  167. data/test/rails3_root/Rakefile +10 -0
  168. data/test/rails3_root/app/controllers/application_controller.rb +22 -0
  169. data/test/rails3_root/app/controllers/posts_controller.rb +87 -0
  170. data/test/rails3_root/app/controllers/users_controller.rb +82 -0
  171. data/test/rails3_root/app/helpers/application_helper.rb +2 -0
  172. data/test/rails3_root/app/models/address.rb +7 -0
  173. data/test/rails3_root/app/models/flea.rb +11 -0
  174. data/test/rails3_root/app/models/friendship.rb +4 -0
  175. data/test/rails3_root/app/models/notifier.rb +8 -0
  176. data/test/rails3_root/app/models/pets/cat.rb +7 -0
  177. data/test/rails3_root/app/models/pets/dog.rb +10 -0
  178. data/test/rails3_root/app/models/post.rb +12 -0
  179. data/test/rails3_root/app/models/product.rb +12 -0
  180. data/test/rails3_root/app/models/profile.rb +2 -0
  181. data/test/rails3_root/app/models/registration.rb +2 -0
  182. data/test/rails3_root/app/models/tag.rb +8 -0
  183. data/test/rails3_root/app/models/tagging.rb +4 -0
  184. data/test/rails3_root/app/models/treat.rb +3 -0
  185. data/test/rails3_root/app/models/user.rb +32 -0
  186. data/test/rails3_root/app/views/layouts/application.html.erb +14 -0
  187. data/test/rails3_root/app/views/layouts/posts.rhtml +19 -0
  188. data/test/rails3_root/app/views/layouts/users.rhtml +17 -0
  189. data/test/rails3_root/app/views/layouts/wide.html.erb +1 -0
  190. data/test/rails3_root/app/views/notifier/the_email.html.erb +1 -0
  191. data/test/rails3_root/app/views/posts/edit.rhtml +27 -0
  192. data/test/rails3_root/app/views/posts/index.rhtml +25 -0
  193. data/test/rails3_root/app/views/posts/new.rhtml +24 -0
  194. data/test/rails3_root/app/views/posts/show.rhtml +18 -0
  195. data/test/rails3_root/app/views/users/edit.rhtml +22 -0
  196. data/test/rails3_root/app/views/users/index.rhtml +22 -0
  197. data/test/rails3_root/app/views/users/new.rhtml +21 -0
  198. data/test/rails3_root/app/views/users/show.rhtml +13 -0
  199. data/test/rails3_root/config/application.rb +46 -0
  200. data/test/rails3_root/config/boot.rb +6 -0
  201. data/test/rails3_root/config/database.yml +22 -0
  202. data/test/rails3_root/config/environment.rb +5 -0
  203. data/test/rails3_root/config/environments/development.rb +19 -0
  204. data/test/rails3_root/config/environments/production.rb +42 -0
  205. data/test/rails3_root/config/environments/test.rb +32 -0
  206. data/test/rails3_root/config/initializers/backtrace_silencers.rb +7 -0
  207. data/test/rails3_root/config/initializers/inflections.rb +10 -0
  208. data/test/rails3_root/config/initializers/mime_types.rb +5 -0
  209. data/test/rails3_root/config/initializers/secret_token.rb +7 -0
  210. data/test/rails3_root/config/initializers/session_store.rb +8 -0
  211. data/test/rails3_root/config/locales/en.yml +5 -0
  212. data/test/rails3_root/config/routes.rb +4 -0
  213. data/test/rails3_root/config.ru +4 -0
  214. data/test/rails3_root/db/migrate/001_create_users.rb +19 -0
  215. data/test/rails3_root/db/migrate/002_create_posts.rb +13 -0
  216. data/test/rails3_root/db/migrate/003_create_taggings.rb +12 -0
  217. data/test/rails3_root/db/migrate/004_create_tags.rb +11 -0
  218. data/test/rails3_root/db/migrate/005_create_dogs.rb +12 -0
  219. data/test/rails3_root/db/migrate/006_create_addresses.rb +14 -0
  220. data/test/rails3_root/db/migrate/007_create_fleas.rb +11 -0
  221. data/test/rails3_root/db/migrate/008_create_dogs_fleas.rb +12 -0
  222. data/test/rails3_root/db/migrate/009_create_products.rb +17 -0
  223. data/test/rails3_root/db/migrate/010_create_friendships.rb +14 -0
  224. data/test/rails3_root/db/migrate/011_create_treats.rb +12 -0
  225. data/test/rails3_root/db/migrate/20090506203502_create_profiles.rb +12 -0
  226. data/test/rails3_root/db/migrate/20090506203536_create_registrations.rb +14 -0
  227. data/test/rails3_root/db/migrate/20090513104502_create_cats.rb +12 -0
  228. data/test/rails3_root/db/seeds.rb +7 -0
  229. data/test/rails3_root/public/404.html +26 -0
  230. data/test/rails3_root/public/422.html +26 -0
  231. data/test/rails3_root/public/500.html +26 -0
  232. data/test/rails3_root/public/favicon.ico +0 -0
  233. data/test/rails3_root/public/images/rails.png +0 -0
  234. data/test/rails3_root/public/index.html +279 -0
  235. data/test/rails3_root/public/javascripts/application.js +2 -0
  236. data/test/rails3_root/public/javascripts/controls.js +965 -0
  237. data/test/rails3_root/public/javascripts/dragdrop.js +974 -0
  238. data/test/rails3_root/public/javascripts/effects.js +1123 -0
  239. data/test/rails3_root/public/javascripts/prototype.js +4874 -0
  240. data/test/rails3_root/public/javascripts/rails.js +118 -0
  241. data/test/rails3_root/public/robots.txt +5 -0
  242. data/test/rails3_root/script/rails +9 -0
  243. data/test/rails3_root/test/performance/browsing_test.rb +9 -0
  244. data/test/rails3_root/test/test_helper.rb +13 -0
  245. data/test/rails3_test_helper.rb +6 -0
  246. data/test/rspec_test.rb +207 -0
  247. data/test/test_helper.rb +36 -0
  248. data/test/unit/address_test.rb +10 -0
  249. data/test/unit/cat_test.rb +7 -0
  250. data/test/unit/dog_test.rb +9 -0
  251. data/test/unit/flea_test.rb +14 -0
  252. data/test/unit/friendship_test.rb +6 -0
  253. data/test/unit/post_test.rb +15 -0
  254. data/test/unit/product_test.rb +23 -0
  255. data/test/unit/tag_test.rb +11 -0
  256. data/test/unit/tagging_test.rb +6 -0
  257. data/test/unit/user_test.rb +46 -0
  258. metadata +330 -0
@@ -0,0 +1,275 @@
1
+ module Shoulda # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensure that the belongs_to relationship exists.
6
+ #
7
+ # it { should belong_to(:parent) }
8
+ #
9
+ def belong_to(name)
10
+ AssociationMatcher.new(:belongs_to, name)
11
+ end
12
+
13
+ # Ensures that the has_many relationship exists. Will also test that the
14
+ # associated table has the required columns. Works with polymorphic
15
+ # associations.
16
+ #
17
+ # Options:
18
+ # * <tt>through</tt> - association name for <tt>has_many :through</tt>
19
+ # * <tt>dependent</tt> - tests that the association makes use of the
20
+ # dependent option.
21
+ # * <tt>with_options</tt> - specify values for other options
22
+ #
23
+ # Example:
24
+ # it { should have_many(:friends) }
25
+ # it { should have_many(:enemies).through(:friends) }
26
+ # it { should have_many(:enemies).dependent(:destroy) }
27
+ # it { should have_many(:enemies).with_options(:readonly => true) }
28
+ #
29
+ def have_many(name)
30
+ AssociationMatcher.new(:has_many, name)
31
+ end
32
+
33
+ # Ensure that the has_one relationship exists. Will also test that the
34
+ # associated table has the required columns. Works with polymorphic
35
+ # associations.
36
+ #
37
+ # Options:
38
+ # * <tt>:dependent</tt> - tests that the association makes use of the
39
+ # dependent option.
40
+ # * <tt>with_options</tt> - specify values for other options
41
+ #
42
+ # Example:
43
+ # it { should have_one(:god) } # unless hindu
44
+ # it { should have_one(:god).with_options(:readonly => true) }
45
+ #
46
+ def have_one(name)
47
+ AssociationMatcher.new(:has_one, name)
48
+ end
49
+
50
+ # Ensures that the has_and_belongs_to_many relationship exists, and that
51
+ # the join table is in place.
52
+ #
53
+ # Options:
54
+ # * <tt>with_options</tt> - specify values for other options
55
+ #
56
+ # it { should have_and_belong_to_many(:posts) }
57
+ # it { should have_and_belong_to_many(:posts).with_options(:readonly => true) }
58
+ #
59
+ def have_and_belong_to_many(name)
60
+ AssociationMatcher.new(:has_and_belongs_to_many, name)
61
+ end
62
+
63
+ class AssociationMatcher # :nodoc:
64
+ def initialize(macro, name)
65
+ @macro = macro
66
+ @name = name
67
+ end
68
+
69
+ def through(through)
70
+ @through = through
71
+ self
72
+ end
73
+
74
+ def dependent(dependent)
75
+ @dependent = dependent
76
+ self
77
+ end
78
+
79
+ def with_options(options)
80
+ @options = options
81
+ self
82
+ end
83
+
84
+ def matches?(subject)
85
+ @subject = subject
86
+ association_exists? &&
87
+ macro_correct? &&
88
+ foreign_key_exists? &&
89
+ through_association_valid? &&
90
+ dependent_correct? &&
91
+ join_table_exists? &&
92
+ options_correct?
93
+ end
94
+
95
+ def failure_message
96
+ "Expected #{expectation} (#{@missing})"
97
+ end
98
+
99
+ def negative_failure_message
100
+ "Did not expect #{expectation}"
101
+ end
102
+
103
+ def description
104
+ description = "#{macro_description} #{@name}"
105
+ description += " through #{@through}" if @through
106
+ description += " dependent => #{@dependent}" if @dependent
107
+ description
108
+ end
109
+
110
+ protected
111
+
112
+ def association_exists?
113
+ if reflection.nil?
114
+ @missing = "no association called #{@name}"
115
+ false
116
+ else
117
+ true
118
+ end
119
+ end
120
+
121
+ def macro_correct?
122
+ if reflection.macro == @macro
123
+ true
124
+ else
125
+ @missing = "actual association type was #{reflection.macro}"
126
+ false
127
+ end
128
+ end
129
+
130
+ def foreign_key_exists?
131
+ !(belongs_foreign_key_missing? || has_foreign_key_missing?)
132
+ end
133
+
134
+ def belongs_foreign_key_missing?
135
+ @macro == :belongs_to && !class_has_foreign_key?(model_class)
136
+ end
137
+
138
+ def has_foreign_key_missing?
139
+ [:has_many, :has_one].include?(@macro) &&
140
+ !through? &&
141
+ !class_has_foreign_key?(associated_class)
142
+ end
143
+
144
+ def through_association_valid?
145
+ @through.nil? || (through_association_exists? && through_association_correct?)
146
+ end
147
+
148
+ def through_association_exists?
149
+ if through_reflection.nil?
150
+ @missing = "#{model_class.name} does not have any relationship to #{@through}"
151
+ false
152
+ else
153
+ true
154
+ end
155
+ end
156
+
157
+ def through_association_correct?
158
+ if @through == reflection.options[:through]
159
+ true
160
+ else
161
+ @missing = "Expected #{model_class.name} to have #{@name} through #{@through}, " <<
162
+ "but got it through #{reflection.options[:through]}"
163
+ false
164
+ end
165
+ end
166
+
167
+ def dependent_correct?
168
+ if @dependent.nil? || @dependent.to_s == reflection.options[:dependent].to_s
169
+ true
170
+ else
171
+ @missing = "#{@name} should have #{@dependent} dependency"
172
+ false
173
+ end
174
+ end
175
+
176
+ def join_table_exists?
177
+ if @macro != :has_and_belongs_to_many ||
178
+ ::ActiveRecord::Base.connection.tables.include?(join_table.to_s)
179
+ true
180
+ else
181
+ @missing = "join table #{join_table} doesn't exist"
182
+ false
183
+ end
184
+ end
185
+
186
+ def class_has_foreign_key?(klass)
187
+ if klass.column_names.include?(foreign_key.to_s)
188
+ true
189
+ else
190
+ @missing = "#{klass} does not have a #{foreign_key} foreign key."
191
+ false
192
+ end
193
+ end
194
+
195
+ def options_correct?
196
+ @options.nil? || (all_options_present && no_extra_options && option_values_correct)
197
+ end
198
+
199
+ def all_options_present
200
+ missing_options = @options.keys - reflection.options.keys
201
+ if missing_options.empty?
202
+ true
203
+ else
204
+ @missing = "missing #{missing_options.first.inspect} option"
205
+ false
206
+ end
207
+ end
208
+
209
+ def no_extra_options
210
+ extra_options = reflection.options.keys - @options.keys - [:extend, :join_table]
211
+ if extra_options.empty?
212
+ true
213
+ else
214
+ @missing = "Found unexpected #{extra_options.first.inspect} option"
215
+ false
216
+ end
217
+ end
218
+
219
+ def option_values_correct
220
+ @options.each do |option, expected_value|
221
+ actual_value = reflection.options[option]
222
+ unless actual_value == expected_value
223
+ @missing = "expected #{option.inspect} option to be " <<
224
+ "#{expected_value.inspect}, but got #{actual_value.inspect}"
225
+ return false
226
+ end
227
+ end
228
+ end
229
+
230
+ def model_class
231
+ @subject.class
232
+ end
233
+
234
+ def join_table
235
+ reflection.options[:join_table]
236
+ end
237
+
238
+ def associated_class
239
+ reflection.klass
240
+ end
241
+
242
+ def foreign_key
243
+ reflection.primary_key_name
244
+ end
245
+
246
+ def through?
247
+ reflection.options[:through]
248
+ end
249
+
250
+ def reflection
251
+ @reflection ||= model_class.reflect_on_association(@name)
252
+ end
253
+
254
+ def through_reflection
255
+ @through_reflection ||= model_class.reflect_on_association(@through)
256
+ end
257
+
258
+ def expectation
259
+ "#{model_class.name} to have a #{@macro} association called #{@name}"
260
+ end
261
+
262
+ def macro_description
263
+ case @macro.to_s
264
+ when 'belongs_to' then 'belong to'
265
+ when 'has_many' then 'have many'
266
+ when 'has_one' then 'have one'
267
+ when 'has_and_belongs_to_many' then
268
+ 'have and belong to many'
269
+ end
270
+ end
271
+ end
272
+
273
+ end
274
+ end
275
+ end
@@ -0,0 +1,87 @@
1
+ module Shoulda # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensure that the attribute's value is in the range specified
6
+ #
7
+ # Options:
8
+ # * <tt>in_range</tt> - the range of allowed values for this attribute
9
+ # * <tt>with_low_message</tt> - value the test expects to find in
10
+ # <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
11
+ # translation for :inclusion.
12
+ # * <tt>with_high_message</tt> - value the test expects to find in
13
+ # <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
14
+ # translation for :inclusion.
15
+ #
16
+ # Example:
17
+ # it { should ensure_inclusion_of(:age).in_range(0..100) }
18
+ #
19
+ def ensure_inclusion_of(attr)
20
+ EnsureInclusionOfMatcher.new(attr)
21
+ end
22
+
23
+ class EnsureInclusionOfMatcher < ValidationMatcher # :nodoc:
24
+
25
+ def in_range(range)
26
+ @range = range
27
+ @minimum = range.first
28
+ @maximum = range.last
29
+ self
30
+ end
31
+
32
+ def with_message(message)
33
+ if message
34
+ @low_message = message
35
+ @high_message = message
36
+ end
37
+ self
38
+ end
39
+
40
+ def with_low_message(message)
41
+ @low_message = message if message
42
+ self
43
+ end
44
+
45
+ def with_high_message(message)
46
+ @high_message = message if message
47
+ self
48
+ end
49
+
50
+ def description
51
+ "ensure inclusion of #{@attribute} in #{@range.inspect}"
52
+ end
53
+
54
+ def matches?(subject)
55
+ super(subject)
56
+
57
+ @low_message ||= :inclusion
58
+ @high_message ||= :inclusion
59
+
60
+ disallows_lower_value &&
61
+ allows_minimum_value &&
62
+ disallows_higher_value &&
63
+ allows_maximum_value
64
+ end
65
+
66
+ private
67
+
68
+ def disallows_lower_value
69
+ @minimum == 0 || disallows_value_of(@minimum - 1, @low_message)
70
+ end
71
+
72
+ def disallows_higher_value
73
+ disallows_value_of(@maximum + 1, @high_message)
74
+ end
75
+
76
+ def allows_minimum_value
77
+ allows_value_of(@minimum, @low_message)
78
+ end
79
+
80
+ def allows_maximum_value
81
+ allows_value_of(@maximum, @high_message)
82
+ end
83
+ end
84
+
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,141 @@
1
+ module Shoulda # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensures that the length of the attribute is validated.
6
+ #
7
+ # Options:
8
+ # * <tt>is_at_least</tt> - minimum length of this attribute
9
+ # * <tt>is_at_most</tt> - maximum length of this attribute
10
+ # * <tt>is_equal_to</tt> - exact requred length of this attribute
11
+ # * <tt>with_short_message</tt> - value the test expects to find in
12
+ # <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
13
+ # translation for :too_short.
14
+ # * <tt>with_long_message</tt> - value the test expects to find in
15
+ # <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
16
+ # translation for :too_long.
17
+ # * <tt>with_message</tt> - value the test expects to find in
18
+ # <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
19
+ # translation for :wrong_length. Used in conjunction with
20
+ # <tt>is_equal_to</tt>.
21
+ #
22
+ # Examples:
23
+ # it { should ensure_length_of(:password).
24
+ # is_at_least(6).
25
+ # is_at_most(20) }
26
+ # it { should ensure_length_of(:name).
27
+ # is_at_least(3).
28
+ # with_short_message(/not long enough/) }
29
+ # it { should ensure_length_of(:ssn).
30
+ # is_equal_to(9).
31
+ # with_message(/is invalid/) }
32
+ def ensure_length_of(attr)
33
+ EnsureLengthOfMatcher.new(attr)
34
+ end
35
+
36
+ class EnsureLengthOfMatcher < ValidationMatcher # :nodoc:
37
+ include Helpers
38
+
39
+ def is_at_least(length)
40
+ @minimum = length
41
+ @short_message ||= :too_short
42
+ self
43
+ end
44
+
45
+ def is_at_most(length)
46
+ @maximum = length
47
+ @long_message ||= :too_long
48
+ self
49
+ end
50
+
51
+ def is_equal_to(length)
52
+ @minimum = length
53
+ @maximum = length
54
+ @short_message ||= :wrong_length
55
+ self
56
+ end
57
+
58
+ def with_short_message(message)
59
+ @short_message = message if message
60
+ self
61
+ end
62
+ alias_method :with_message, :with_short_message
63
+
64
+ def with_long_message(message)
65
+ @long_message = message if message
66
+ self
67
+ end
68
+
69
+ def description
70
+ description = "ensure #{@attribute} has a length "
71
+ if @minimum && @maximum
72
+ if @minimum == @maximum
73
+ description << "of exactly #{@minimum}"
74
+ else
75
+ description << "between #{@minimum} and #{@maximum}"
76
+ end
77
+ else
78
+ description << "of at least #{@minimum}" if @minimum
79
+ description << "of at most #{@maximum}" if @maximum
80
+ end
81
+ description
82
+ end
83
+
84
+ def matches?(subject)
85
+ super(subject)
86
+ translate_messages!
87
+ disallows_lower_length &&
88
+ allows_minimum_length &&
89
+ ((@minimum == @maximum) ||
90
+ (disallows_higher_length &&
91
+ allows_maximum_length))
92
+ end
93
+
94
+ private
95
+
96
+ def translate_messages!
97
+ if Symbol === @short_message
98
+ @short_message = default_error_message(@short_message,
99
+ :count => @minimum)
100
+ end
101
+
102
+ if Symbol === @long_message
103
+ @long_message = default_error_message(@long_message,
104
+ :count => @maximum)
105
+ end
106
+ end
107
+
108
+ def disallows_lower_length
109
+ @minimum == 0 ||
110
+ @minimum.nil? ||
111
+ disallows_length_of(@minimum - 1, @short_message)
112
+ end
113
+
114
+ def disallows_higher_length
115
+ @maximum.nil? || disallows_length_of(@maximum + 1, @long_message)
116
+ end
117
+
118
+ def allows_minimum_length
119
+ allows_length_of(@minimum, @short_message)
120
+ end
121
+
122
+ def allows_maximum_length
123
+ allows_length_of(@maximum, @long_message)
124
+ end
125
+
126
+ def allows_length_of(length, message)
127
+ length.nil? || allows_value_of(string_of_length(length), message)
128
+ end
129
+
130
+ def disallows_length_of(length, message)
131
+ length.nil? || disallows_value_of(string_of_length(length), message)
132
+ end
133
+
134
+ def string_of_length(length)
135
+ 'x' * length
136
+ end
137
+ end
138
+
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,169 @@
1
+ module Shoulda # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensures the database column exists.
6
+ #
7
+ # Options:
8
+ # * <tt>of_type</tt> - db column type (:integer, :string, etc.)
9
+ # * <tt>with_options</tt> - same options available in migrations
10
+ # (:default, :null, :limit, :precision, :scale)
11
+ #
12
+ # Examples:
13
+ # it { should_not have_db_column(:admin).of_type(:boolean) }
14
+ # it { should have_db_column(:salary).
15
+ # of_type(:decimal).
16
+ # with_options(:precision => 10, :scale => 2) }
17
+ #
18
+ def have_db_column(column)
19
+ HaveDbColumnMatcher.new(:have_db_column, column)
20
+ end
21
+
22
+ class HaveDbColumnMatcher # :nodoc:
23
+ def initialize(macro, column)
24
+ @macro = macro
25
+ @column = column
26
+ end
27
+
28
+ def of_type(column_type)
29
+ @column_type = column_type
30
+ self
31
+ end
32
+
33
+ def with_options(opts = {})
34
+ @precision = opts[:precision]
35
+ @limit = opts[:limit]
36
+ @default = opts[:default]
37
+ @null = opts[:null]
38
+ @scale = opts[:scale]
39
+ self
40
+ end
41
+
42
+ def matches?(subject)
43
+ @subject = subject
44
+ column_exists? &&
45
+ correct_column_type? &&
46
+ correct_precision? &&
47
+ correct_limit? &&
48
+ correct_default? &&
49
+ correct_null? &&
50
+ correct_scale?
51
+ end
52
+
53
+ def failure_message
54
+ "Expected #{expectation} (#{@missing})"
55
+ end
56
+
57
+ def negative_failure_message
58
+ "Did not expect #{expectation}"
59
+ end
60
+
61
+ def description
62
+ desc = "have db column named #{@column}"
63
+ desc << " of type #{@column_type}" unless @column_type.nil?
64
+ desc << " of precision #{@precision}" unless @precision.nil?
65
+ desc << " of limit #{@limit}" unless @limit.nil?
66
+ desc << " of default #{@default}" unless @default.nil?
67
+ desc << " of null #{@null}" unless @null.nil?
68
+ desc << " of primary #{@primary}" unless @primary.nil?
69
+ desc << " of scale #{@scale}" unless @scale.nil?
70
+ desc
71
+ end
72
+
73
+ protected
74
+
75
+ def column_exists?
76
+ if model_class.column_names.include?(@column.to_s)
77
+ true
78
+ else
79
+ @missing = "#{model_class} does not have a db column named #{@column}."
80
+ false
81
+ end
82
+ end
83
+
84
+ def correct_column_type?
85
+ return true if @column_type.nil?
86
+ if matched_column.type.to_s == @column_type.to_s
87
+ true
88
+ else
89
+ @missing = "#{model_class} has a db column named #{@column} " <<
90
+ "of type #{matched_column.type}, not #{@column_type}."
91
+ false
92
+ end
93
+ end
94
+
95
+ def correct_precision?
96
+ return true if @precision.nil?
97
+ if matched_column.precision.to_s == @precision.to_s
98
+ true
99
+ else
100
+ @missing = "#{model_class} has a db column named #{@column} " <<
101
+ "of precision #{matched_column.precision}, " <<
102
+ "not #{@precision}."
103
+ false
104
+ end
105
+ end
106
+
107
+ def correct_limit?
108
+ return true if @limit.nil?
109
+ if matched_column.limit.to_s == @limit.to_s
110
+ true
111
+ else
112
+ @missing = "#{model_class} has a db column named #{@column} " <<
113
+ "of limit #{matched_column.limit}, " <<
114
+ "not #{@limit}."
115
+ false
116
+ end
117
+ end
118
+
119
+ def correct_default?
120
+ return true if @default.nil?
121
+ if matched_column.default.to_s == @default.to_s
122
+ true
123
+ else
124
+ @missing = "#{model_class} has a db column named #{@column} " <<
125
+ "of default #{matched_column.default}, " <<
126
+ "not #{@default}."
127
+ false
128
+ end
129
+ end
130
+
131
+ def correct_null?
132
+ return true if @null.nil?
133
+ if matched_column.null.to_s == @null.to_s
134
+ true
135
+ else
136
+ @missing = "#{model_class} has a db column named #{@column} " <<
137
+ "of null #{matched_column.null}, " <<
138
+ "not #{@null}."
139
+ false
140
+ end
141
+ end
142
+
143
+ def correct_scale?
144
+ return true if @scale.nil?
145
+ if matched_column.scale.to_s == @scale.to_s
146
+ true
147
+ else
148
+ @missing = "#{model_class} has a db column named #{@column} " <<
149
+ "of scale #{matched_column.scale}, not #{@scale}."
150
+ false
151
+ end
152
+ end
153
+
154
+ def matched_column
155
+ model_class.columns.detect { |each| each.name == @column.to_s }
156
+ end
157
+
158
+ def model_class
159
+ @subject.class
160
+ end
161
+
162
+ def expectation
163
+ expected = "#{model_class.name} to #{description}"
164
+ end
165
+ end
166
+
167
+ end
168
+ end
169
+ end