pager-resource_controller 1.0.20080513

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 (201) hide show
  1. data/LICENSE +22 -0
  2. data/README +276 -0
  3. data/Rakefile +55 -0
  4. data/TODO +1 -0
  5. data/generators/scaffold_resource/USAGE +29 -0
  6. data/generators/scaffold_resource/scaffold_resource_generator.rb +101 -0
  7. data/generators/scaffold_resource/templates/controller.rb +2 -0
  8. data/generators/scaffold_resource/templates/fixtures.yml +10 -0
  9. data/generators/scaffold_resource/templates/functional_test.rb +57 -0
  10. data/generators/scaffold_resource/templates/helper.rb +2 -0
  11. data/generators/scaffold_resource/templates/migration.rb +15 -0
  12. data/generators/scaffold_resource/templates/model.rb +2 -0
  13. data/generators/scaffold_resource/templates/old_migration.rb +13 -0
  14. data/generators/scaffold_resource/templates/shoulda_functional_test.rb +19 -0
  15. data/generators/scaffold_resource/templates/unit_test.rb +7 -0
  16. data/generators/scaffold_resource/templates/view__form.erb +6 -0
  17. data/generators/scaffold_resource/templates/view__form.haml +5 -0
  18. data/generators/scaffold_resource/templates/view_edit.erb +16 -0
  19. data/generators/scaffold_resource/templates/view_edit.haml +11 -0
  20. data/generators/scaffold_resource/templates/view_index.erb +22 -0
  21. data/generators/scaffold_resource/templates/view_index.haml +19 -0
  22. data/generators/scaffold_resource/templates/view_new.erb +12 -0
  23. data/generators/scaffold_resource/templates/view_new.haml +9 -0
  24. data/generators/scaffold_resource/templates/view_show.erb +9 -0
  25. data/generators/scaffold_resource/templates/view_show.haml +9 -0
  26. data/init.rb +6 -0
  27. data/install.rb +1 -0
  28. data/lib/resource_controller.rb +11 -0
  29. data/lib/resource_controller/accessors.rb +76 -0
  30. data/lib/resource_controller/action_options.rb +28 -0
  31. data/lib/resource_controller/actions.rb +75 -0
  32. data/lib/resource_controller/base.rb +15 -0
  33. data/lib/resource_controller/class_methods.rb +22 -0
  34. data/lib/resource_controller/controller.rb +63 -0
  35. data/lib/resource_controller/failable_action_options.rb +17 -0
  36. data/lib/resource_controller/helpers.rb +28 -0
  37. data/lib/resource_controller/helpers/current_objects.rb +69 -0
  38. data/lib/resource_controller/helpers/internal.rb +59 -0
  39. data/lib/resource_controller/helpers/nested.rb +45 -0
  40. data/lib/resource_controller/helpers/urls.rb +124 -0
  41. data/lib/resource_controller/response_collector.rb +21 -0
  42. data/lib/urligence.rb +50 -0
  43. data/rails/init.rb +1 -0
  44. data/test/Rakefile +10 -0
  45. data/test/app/controllers/application.rb +7 -0
  46. data/test/app/controllers/cms/options_controller.rb +3 -0
  47. data/test/app/controllers/cms/products_controller.rb +2 -0
  48. data/test/app/controllers/comments_controller.rb +3 -0
  49. data/test/app/controllers/people_controller.rb +9 -0
  50. data/test/app/controllers/photos_controller.rb +10 -0
  51. data/test/app/controllers/posts_controller.rb +10 -0
  52. data/test/app/controllers/projects_controller.rb +3 -0
  53. data/test/app/controllers/somethings_controller.rb +3 -0
  54. data/test/app/controllers/tags_controller.rb +13 -0
  55. data/test/app/controllers/users_controller.rb +12 -0
  56. data/test/app/helpers/application_helper.rb +3 -0
  57. data/test/app/helpers/cms/products_helper.rb +2 -0
  58. data/test/app/helpers/comments_helper.rb +2 -0
  59. data/test/app/helpers/options_helper.rb +2 -0
  60. data/test/app/helpers/people_helper.rb +2 -0
  61. data/test/app/helpers/photos_helper.rb +2 -0
  62. data/test/app/helpers/posts_helper.rb +2 -0
  63. data/test/app/helpers/projects_helper.rb +2 -0
  64. data/test/app/helpers/somethings_helper.rb +2 -0
  65. data/test/app/helpers/tags_helper.rb +2 -0
  66. data/test/app/helpers/users_helper.rb +2 -0
  67. data/test/app/models/account.rb +3 -0
  68. data/test/app/models/comment.rb +3 -0
  69. data/test/app/models/option.rb +3 -0
  70. data/test/app/models/photo.rb +4 -0
  71. data/test/app/models/post.rb +3 -0
  72. data/test/app/models/product.rb +3 -0
  73. data/test/app/models/project.rb +2 -0
  74. data/test/app/models/something.rb +2 -0
  75. data/test/app/models/tag.rb +3 -0
  76. data/test/app/views/cms/options/edit.rhtml +17 -0
  77. data/test/app/views/cms/options/index.rhtml +20 -0
  78. data/test/app/views/cms/options/new.rhtml +16 -0
  79. data/test/app/views/cms/options/show.rhtml +8 -0
  80. data/test/app/views/cms/products/edit.rhtml +17 -0
  81. data/test/app/views/cms/products/index.rhtml +20 -0
  82. data/test/app/views/cms/products/new.rhtml +16 -0
  83. data/test/app/views/cms/products/show.rhtml +8 -0
  84. data/test/app/views/comments/edit.rhtml +27 -0
  85. data/test/app/views/comments/index.rhtml +24 -0
  86. data/test/app/views/comments/new.rhtml +26 -0
  87. data/test/app/views/comments/show.rhtml +18 -0
  88. data/test/app/views/layouts/application.rhtml +17 -0
  89. data/test/app/views/layouts/comments.rhtml +17 -0
  90. data/test/app/views/layouts/options.rhtml +17 -0
  91. data/test/app/views/layouts/people.rhtml +17 -0
  92. data/test/app/views/layouts/photos.rhtml +17 -0
  93. data/test/app/views/layouts/projects.rhtml +17 -0
  94. data/test/app/views/layouts/somethings.rhtml +17 -0
  95. data/test/app/views/layouts/tags.rhtml +17 -0
  96. data/test/app/views/people/edit.rhtml +17 -0
  97. data/test/app/views/people/index.rhtml +20 -0
  98. data/test/app/views/people/new.rhtml +16 -0
  99. data/test/app/views/people/show.rhtml +8 -0
  100. data/test/app/views/photos/edit.rhtml +17 -0
  101. data/test/app/views/photos/index.rhtml +20 -0
  102. data/test/app/views/photos/new.rhtml +16 -0
  103. data/test/app/views/photos/show.rhtml +8 -0
  104. data/test/app/views/posts/edit.rhtml +22 -0
  105. data/test/app/views/posts/index.rhtml +22 -0
  106. data/test/app/views/posts/new.rhtml +21 -0
  107. data/test/app/views/posts/show.rhtml +13 -0
  108. data/test/app/views/projects/edit.rhtml +17 -0
  109. data/test/app/views/projects/index.rhtml +20 -0
  110. data/test/app/views/projects/new.rhtml +16 -0
  111. data/test/app/views/projects/show.rhtml +8 -0
  112. data/test/app/views/somethings/edit.rhtml +17 -0
  113. data/test/app/views/somethings/index.rhtml +20 -0
  114. data/test/app/views/somethings/new.rhtml +16 -0
  115. data/test/app/views/somethings/show.rhtml +8 -0
  116. data/test/app/views/tags/edit.rhtml +17 -0
  117. data/test/app/views/tags/index.rhtml +20 -0
  118. data/test/app/views/tags/index.rjs +0 -0
  119. data/test/app/views/tags/new.rhtml +16 -0
  120. data/test/app/views/tags/show.rhtml +8 -0
  121. data/test/app/views/users/edit.rhtml +17 -0
  122. data/test/app/views/users/index.rhtml +20 -0
  123. data/test/app/views/users/new.rhtml +16 -0
  124. data/test/app/views/users/show.rhtml +8 -0
  125. data/test/config/boot.rb +45 -0
  126. data/test/config/database.yml +16 -0
  127. data/test/config/environment.rb +64 -0
  128. data/test/config/environments/development.rb +21 -0
  129. data/test/config/environments/test.rb +19 -0
  130. data/test/config/routes.rb +51 -0
  131. data/test/db/migrate/001_create_posts.rb +12 -0
  132. data/test/db/migrate/002_create_products.rb +11 -0
  133. data/test/db/migrate/003_create_comments.rb +13 -0
  134. data/test/db/migrate/004_create_options.rb +12 -0
  135. data/test/db/migrate/005_create_photos.rb +11 -0
  136. data/test/db/migrate/006_create_tags.rb +17 -0
  137. data/test/db/migrate/007_create_somethings.rb +11 -0
  138. data/test/db/migrate/008_create_accounts.rb +11 -0
  139. data/test/db/migrate/009_add_account_id_to_photos.rb +9 -0
  140. data/test/db/migrate/010_create_projects.rb +11 -0
  141. data/test/db/schema.rb +65 -0
  142. data/test/script/console +3 -0
  143. data/test/script/destroy +3 -0
  144. data/test/script/generate +3 -0
  145. data/test/script/server +3 -0
  146. data/test/test/fixtures/accounts.yml +7 -0
  147. data/test/test/fixtures/comments.yml +11 -0
  148. data/test/test/fixtures/options.yml +9 -0
  149. data/test/test/fixtures/photos.yml +9 -0
  150. data/test/test/fixtures/photos_tags.yml +3 -0
  151. data/test/test/fixtures/posts.yml +9 -0
  152. data/test/test/fixtures/products.yml +7 -0
  153. data/test/test/fixtures/projects.yml +7 -0
  154. data/test/test/fixtures/somethings.yml +7 -0
  155. data/test/test/fixtures/tags.yml +7 -0
  156. data/test/test/functional/cms/options_controller_test.rb +20 -0
  157. data/test/test/functional/cms/products_controller_test.rb +18 -0
  158. data/test/test/functional/comments_controller_test.rb +26 -0
  159. data/test/test/functional/people_controller_test.rb +34 -0
  160. data/test/test/functional/photos_controller_test.rb +128 -0
  161. data/test/test/functional/posts_controller_test.rb +34 -0
  162. data/test/test/functional/projects_controller_test.rb +18 -0
  163. data/test/test/functional/somethings_controller_test.rb +28 -0
  164. data/test/test/functional/tags_controller_test.rb +64 -0
  165. data/test/test/functional/users_controller_test.rb +24 -0
  166. data/test/test/test_helper.rb +12 -0
  167. data/test/test/unit/accessors_test.rb +91 -0
  168. data/test/test/unit/account_test.rb +7 -0
  169. data/test/test/unit/action_options_test.rb +66 -0
  170. data/test/test/unit/base_test.rb +11 -0
  171. data/test/test/unit/comment_test.rb +10 -0
  172. data/test/test/unit/failable_action_options_test.rb +50 -0
  173. data/test/test/unit/helpers/current_objects_test.rb +127 -0
  174. data/test/test/unit/helpers/internal_test.rb +88 -0
  175. data/test/test/unit/helpers/nested_test.rb +82 -0
  176. data/test/test/unit/helpers/urls_test.rb +71 -0
  177. data/test/test/unit/helpers_test.rb +25 -0
  178. data/test/test/unit/option_test.rb +10 -0
  179. data/test/test/unit/photo_test.rb +10 -0
  180. data/test/test/unit/post_test.rb +10 -0
  181. data/test/test/unit/project_test.rb +10 -0
  182. data/test/test/unit/response_collector_test.rb +31 -0
  183. data/test/test/unit/something_test.rb +10 -0
  184. data/test/test/unit/tag_test.rb +10 -0
  185. data/test/test/unit/urligence_test.rb +203 -0
  186. data/test/vendor/plugins/shoulda/README +123 -0
  187. data/test/vendor/plugins/shoulda/Rakefile +29 -0
  188. data/test/vendor/plugins/shoulda/bin/convert_to_should_syntax +40 -0
  189. data/test/vendor/plugins/shoulda/init.rb +3 -0
  190. data/test/vendor/plugins/shoulda/lib/shoulda.rb +47 -0
  191. data/test/vendor/plugins/shoulda/lib/shoulda/active_record_helpers.rb +338 -0
  192. data/test/vendor/plugins/shoulda/lib/shoulda/color.rb +77 -0
  193. data/test/vendor/plugins/shoulda/lib/shoulda/context.rb +143 -0
  194. data/test/vendor/plugins/shoulda/lib/shoulda/controller_tests/controller_tests.rb +470 -0
  195. data/test/vendor/plugins/shoulda/lib/shoulda/controller_tests/formats/html.rb +192 -0
  196. data/test/vendor/plugins/shoulda/lib/shoulda/controller_tests/formats/xml.rb +162 -0
  197. data/test/vendor/plugins/shoulda/lib/shoulda/general.rb +119 -0
  198. data/test/vendor/plugins/shoulda/lib/shoulda/private_helpers.rb +17 -0
  199. data/test/vendor/plugins/shoulda/tasks/list_tests.rake +40 -0
  200. data/uninstall.rb +1 -0
  201. metadata +410 -0
@@ -0,0 +1,123 @@
1
+ = Shoulda - Making tests easy on the fingers and eyes
2
+
3
+ Shoulda makes it easy to write elegant, understandable, and maintainable tests. Shoulda consists of test macros, assertions, and helpers added on to the Test::Unit framework. It's fully compatible with your existing tests, and requires no retooling to use.
4
+
5
+ Helpers:: #context and #should give you rSpec like test blocks.
6
+ In addition, you get nested contexts and a much more readable syntax.
7
+ Macros:: Generate hundreds of lines of Controller and ActiveRecord tests with these powerful macros.
8
+ They get you started quickly, and can help you ensure that your application is conforming to best practices.
9
+ Assertions:: Many common rails testing idioms have been distilled into a set of useful assertions.
10
+
11
+ = Usage
12
+
13
+ === Context Helpers (ThoughtBot::Shoulda::Context)
14
+
15
+ Stop killing your fingers with all of those underscores... Name your tests with plain sentences!
16
+
17
+ class UserTest << Test::Unit::TestCase
18
+ context "A User instance" do
19
+ setup do
20
+ @user = User.find(:first)
21
+ end
22
+
23
+ should "return its full name"
24
+ assert_equal 'John Doe', @user.full_name
25
+ end
26
+
27
+ context "with a profile" do
28
+ setup do
29
+ @user.profile = Profile.find(:first)
30
+ end
31
+
32
+ should "return true when sent #has_profile?"
33
+ assert @user.has_profile?
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ Produces the following test methods:
40
+
41
+ "test: A User instance should return its full name."
42
+ "test: A User instance with a profile should return true when sent #has_profile?."
43
+
44
+ So readable!
45
+
46
+ === ActiveRecord Tests (ThoughtBot::Shoulda::ActiveRecord)
47
+
48
+ Quick macro tests for your ActiveRecord associations and validations:
49
+
50
+ class PostTest < Test::Unit::TestCase
51
+ load_all_fixtures
52
+
53
+ should_belong_to :user
54
+ should_have_many :tags, :through => :taggings
55
+
56
+ should_require_unique_attributes :title
57
+ should_require_attributes :body, :message => /wtf/
58
+ should_require_attributes :title
59
+ should_only_allow_numeric_values_for :user_id
60
+ end
61
+
62
+ class UserTest < Test::Unit::TestCase
63
+ load_all_fixtures
64
+
65
+ should_have_many :posts
66
+
67
+ should_not_allow_values_for :email, "blah", "b lah"
68
+ should_allow_values_for :email, "a@b.com", "asdf@asdf.com"
69
+ should_ensure_length_in_range :email, 1..100
70
+ should_ensure_value_in_range :age, 1..100
71
+ should_protect_attributes :password
72
+ end
73
+
74
+ Makes TDD so much easier.
75
+
76
+ === Controller Tests (ThoughtBot::Shoulda::Controller::ClassMethods)
77
+
78
+ Macros to test the most common controller patterns...
79
+
80
+ context "on GET to :show for first record" do
81
+ setup do
82
+ get :show, :id => 1
83
+ end
84
+
85
+ should_assign_to :user
86
+ should_respond_with :success
87
+ should_render_template :show
88
+ should_not_set_the_flash
89
+
90
+ should "do something else really cool" do
91
+ assert_equal 1, assigns(:user).id
92
+ end
93
+ end
94
+
95
+ Test entire controllers in a few lines...
96
+
97
+ class PostsControllerTest < Test::Unit::TestCase
98
+ should_be_restful do |resource|
99
+ resource.parent = :user
100
+
101
+ resource.create.params = { :title => "first post", :body => 'blah blah blah'}
102
+ resource.update.params = { :title => "changed" }
103
+ end
104
+ end
105
+
106
+ should_be_restful generates 40 tests on the fly, for both html and xml requests.
107
+
108
+ === Helpful Assertions (ThoughtBot::Shoulda::General)
109
+
110
+ More to come here, but have fun with what's there.
111
+
112
+ load_all_fixtures
113
+ assert_same_elements([:a, :b, :c], [:c, :a, :b])
114
+ assert_contains(['a', '1'], /\d/)
115
+ assert_contains(['a', '1'], 'a')
116
+
117
+ = Credits
118
+
119
+ Shoulda is maintained by {Tammer Saleh}[mailto:tsaleh@thoughtbot.com], and is funded by Thoughtbot[http://www.thoughtbot.com], inc.
120
+
121
+ = License
122
+
123
+ Shoulda is Copyright © 2006-2007 Tammer Saleh, Thoughtbot. It is free software, and may be redistributed under the terms specified in the README file of the Ruby distribution.
@@ -0,0 +1,29 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ #require 'tasks/list_tests.rake'
6
+
7
+ # Test::Unit::UI::VERBOSE
8
+
9
+ Rake::TestTask.new do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/{unit,functional,other}/**/*_test.rb'
12
+ t.verbose = false
13
+ end
14
+
15
+ Rake::RDocTask.new { |rdoc|
16
+ rdoc.rdoc_dir = 'doc'
17
+ rdoc.title = "Shoulda -- Making tests easy on the fingers and eyes"
18
+ rdoc.options << '--line-numbers' << '--inline-source'
19
+ rdoc.template = "#{ENV['template']}.rb" if ENV['template']
20
+ rdoc.rdoc_files.include('README', 'lib/**/*.rb')
21
+ }
22
+
23
+ desc 'Update documentation on website'
24
+ task :sync_docs => 'rdoc' do
25
+ `rsync -ave ssh doc/ dev@dev.thoughtbot.com:/home/dev/www/dev.thoughtbot.com/shoulda`
26
+ end
27
+
28
+ desc 'Default: run tests.'
29
+ task :default => ['test']
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env ruby
2
+ require 'fileutils'
3
+
4
+ def usage(msg = nil)
5
+ puts "Error: #{msg}" if msg
6
+ puts if msg
7
+ puts "Usage: #{File.basename(__FILE__)} normal_test_file.rb"
8
+ puts
9
+ puts "Will convert an existing test file with names like "
10
+ puts
11
+ puts " def test_should_do_stuff"
12
+ puts " ..."
13
+ puts " end"
14
+ puts
15
+ puts "to one using the new syntax: "
16
+ puts
17
+ puts " should \"be super cool\" do"
18
+ puts " ..."
19
+ puts " end"
20
+ puts
21
+ puts "A copy of the old file will be left under /tmp/ in case this script just seriously screws up"
22
+ puts
23
+ exit (msg ? 2 : 0)
24
+ end
25
+
26
+ usage("Wrong number of arguments.") unless ARGV.size == 1
27
+ usage("This system doesn't have a /tmp directory. wtf?") unless File.directory?('/tmp')
28
+
29
+ file = ARGV.shift
30
+ tmpfile = "/tmp/#{File.basename(file)}"
31
+ usage("File '#{file}' doesn't exist") unless File.exists?(file)
32
+
33
+ FileUtils.cp(file, tmpfile)
34
+ contents = File.read(tmpfile)
35
+ contents.gsub!(/def test_should_(.*)\s*$/, 'should "\1" do')
36
+ contents.gsub!(/def test_(.*)\s*$/, 'should "RENAME ME: test \1" do')
37
+ contents.gsub!(/should ".*" do$/) {|line| line.tr!('_', ' ')}
38
+ File.open(file, 'w') { |f| f.write(contents) }
39
+
40
+ puts "File '#{file}' has been converted to 'should' syntax. Old version has been stored in '#{tmpfile}'"
@@ -0,0 +1,3 @@
1
+ require 'rubygems'
2
+ require 'active_support'
3
+ require 'shoulda'
@@ -0,0 +1,47 @@
1
+ require 'yaml'
2
+ require 'shoulda/private_helpers'
3
+ require 'shoulda/general'
4
+ require 'shoulda/context'
5
+ require 'shoulda/active_record_helpers'
6
+ require 'shoulda/controller_tests/controller_tests.rb'
7
+
8
+ shoulda_options = {}
9
+
10
+ possible_config_paths = []
11
+ possible_config_paths << File.join(ENV["HOME"], ".shoulda.conf") if ENV["HOME"]
12
+ possible_config_paths << "shoulda.conf"
13
+ possible_config_paths << File.join("test", "shoulda.conf")
14
+ possible_config_paths << File.join(RAILS_ROOT, "test", "shoulda.conf") if defined?(RAILS_ROOT)
15
+
16
+ possible_config_paths.each do |config_file|
17
+ if File.exists? config_file
18
+ shoulda_options = YAML.load_file(config_file).symbolize_keys
19
+ break
20
+ end
21
+ end
22
+
23
+ require 'shoulda/color' if shoulda_options[:color]
24
+
25
+ module Test # :nodoc: all
26
+ module Unit
27
+ class TestCase
28
+
29
+ include ThoughtBot::Shoulda::Controller
30
+ include ThoughtBot::Shoulda::General
31
+
32
+ class << self
33
+ include ThoughtBot::Shoulda::Context
34
+ include ThoughtBot::Shoulda::ActiveRecord
35
+ # include ThoughtBot::Shoulda::General::ClassMethods
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ module ActionController #:nodoc: all
42
+ module Integration
43
+ class Session
44
+ include ThoughtBot::Shoulda::General
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,338 @@
1
+ module ThoughtBot # :nodoc:
2
+ module Shoulda # :nodoc:
3
+ # = Macro test helpers for your active record models
4
+ #
5
+ # These helpers will test most of the validations and associations for your ActiveRecord models.
6
+ #
7
+ # class UserTest < Test::Unit::TestCase
8
+ # should_require_attributes :name, :phone_number
9
+ # should_not_allow_values_for :phone_number, "abcd", "1234"
10
+ # should_allow_values_for :phone_number, "(123) 456-7890"
11
+ #
12
+ # should_protect_attributes :password
13
+ #
14
+ # should_have_one :profile
15
+ # should_have_many :dogs
16
+ # should_have_many :messes, :through => :dogs
17
+ # should_belong_to :lover
18
+ # end
19
+ #
20
+ # For all of these helpers, the last parameter may be a hash of options.
21
+ #
22
+ module ActiveRecord
23
+ # Ensures that the model cannot be saved if one of the attributes listed is not present.
24
+ # Requires an existing record.
25
+ #
26
+ # Options:
27
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
28
+ # Regexp or string. Default = <tt>/blank/</tt>
29
+ #
30
+ # Example:
31
+ # should_require_attributes :name, :phone_number
32
+ def should_require_attributes(*attributes)
33
+ message = get_options!(attributes, :message)
34
+ message ||= /blank/
35
+ klass = model_class
36
+
37
+ attributes.each do |attribute|
38
+ should "require #{attribute} to be set" do
39
+ object = klass.new
40
+ assert !object.valid?, "#{klass.name} does not require #{attribute}."
41
+ assert object.errors.on(attribute), "#{klass.name} does not require #{attribute}."
42
+ assert_contains(object.errors.on(attribute), message)
43
+ end
44
+ end
45
+ end
46
+
47
+ # Ensures that the model cannot be saved if one of the attributes listed is not unique.
48
+ # Requires an existing record
49
+ #
50
+ # Options:
51
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
52
+ # Regexp or string. Default = <tt>/taken/</tt>
53
+ #
54
+ # Example:
55
+ # should_require_unique_attributes :keyword, :username
56
+ def should_require_unique_attributes(*attributes)
57
+ message, scope = get_options!(attributes, :message, :scoped_to)
58
+ message ||= /taken/
59
+
60
+ klass = model_class
61
+ attributes.each do |attribute|
62
+ attribute = attribute.to_sym
63
+ should "require unique value for #{attribute}#{" scoped to #{scope}" if scope}" do
64
+ assert existing = klass.find(:first), "Can't find first #{klass}"
65
+ object = klass.new
66
+
67
+ object.send(:"#{attribute}=", existing.send(attribute))
68
+ if scope
69
+ assert_respond_to object, :"#{scope}=", "#{klass.name} doesn't seem to have a #{scope} attribute."
70
+ object.send(:"#{scope}=", existing.send(scope))
71
+ end
72
+
73
+ assert !object.valid?, "#{klass.name} does not require a unique value for #{attribute}."
74
+ assert object.errors.on(attribute), "#{klass.name} does not require a unique value for #{attribute}."
75
+
76
+ assert_contains(object.errors.on(attribute), message)
77
+
78
+ if scope
79
+ # Now test that the object is valid when changing the scoped attribute
80
+ # TODO: actually find all values for scope and create a unique one.
81
+ object.send(:"#{scope}=", existing.send(scope).nil? ? 1 : existing.send(scope).next)
82
+ object.errors.clear
83
+ object.valid?
84
+ assert_does_not_contain(object.errors.on(attribute), message,
85
+ "after :#{scope} set to #{object.send(scope.to_sym)}")
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ # Ensures that the attribute cannot be set on update
92
+ # Requires an existing record
93
+ #
94
+ # should_protect_attributes :password, :admin_flag
95
+ def should_protect_attributes(*attributes)
96
+ get_options!(attributes)
97
+ klass = model_class
98
+ attributes.each do |attribute|
99
+ attribute = attribute.to_sym
100
+ should "not allow #{attribute} to be changed by update" do
101
+ assert object = klass.find(:first), "Can't find first #{klass}"
102
+ value = object[attribute]
103
+ # TODO: 1 may not be a valid value for the attribute (due to validations)
104
+ assert object.update_attributes({ attribute => 1 }),
105
+ "Cannot update #{klass} with { :#{attribute} => 1 }, #{object.errors.full_messages.to_sentence}"
106
+ assert object.valid?, "#{klass} isn't valid after changing #{attribute}"
107
+ assert_equal value, object[attribute], "Was able to change #{klass}##{attribute}"
108
+ end
109
+ end
110
+ end
111
+
112
+ # Ensures that the attribute cannot be set to the given values
113
+ # Requires an existing record
114
+ #
115
+ # Options:
116
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
117
+ # Regexp or string. Default = <tt>/invalid/</tt>
118
+ #
119
+ # Example:
120
+ # should_not_allow_values_for :isbn, "bad 1", "bad 2"
121
+ def should_not_allow_values_for(attribute, *bad_values)
122
+ message = get_options!(bad_values, :message)
123
+ message ||= /invalid/
124
+ klass = model_class
125
+ bad_values.each do |v|
126
+ should "not allow #{attribute} to be set to \"#{v}\"" do
127
+ assert object = klass.find(:first), "Can't find first #{klass}"
128
+ object.send("#{attribute}=", v)
129
+ assert !object.save, "Saved #{klass} with #{attribute} set to \"#{v}\""
130
+ assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\""
131
+ assert_contains(object.errors.on(attribute), message, "when set to \"#{v}\"")
132
+ end
133
+ end
134
+ end
135
+
136
+ # Ensures that the attribute can be set to the given values.
137
+ # Requires an existing record
138
+ #
139
+ # Options:
140
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
141
+ # Regexp or string. Default = <tt>/invalid/</tt>
142
+ #
143
+ # Example:
144
+ # should_allow_values_for :isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0"
145
+ def should_allow_values_for(attribute, *good_values)
146
+ message = get_options!(good_values, :message)
147
+ message ||= /invalid/
148
+ klass = model_class
149
+ good_values.each do |v|
150
+ should "allow #{attribute} to be set to \"#{v}\"" do
151
+ assert object = klass.find(:first), "Can't find first #{klass}"
152
+ object.send("#{attribute}=", v)
153
+ object.save
154
+ assert_does_not_contain(object.errors.on(attribute), message, "when set to \"#{v}\"")
155
+ end
156
+ end
157
+ end
158
+
159
+ # Ensures that the length of the attribute is in the given range
160
+ # Requires an existing record
161
+ #
162
+ # Options:
163
+ # * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
164
+ # Regexp or string. Default = <tt>/short/</tt>
165
+ # * <tt>:long_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
166
+ # Regexp or string. Default = <tt>/long/</tt>
167
+ #
168
+ # Example:
169
+ # should_ensure_length_in_range :password, (6..20)
170
+ def should_ensure_length_in_range(attribute, range, opts = {})
171
+ short_message, long_message = get_options!([opts], :short_message, :long_message)
172
+ short_message ||= /short/
173
+ long_message ||= /long/
174
+
175
+ klass = model_class
176
+ min_length = range.first
177
+ max_length = range.last
178
+
179
+ if min_length > 0
180
+ min_value = "x" * (min_length - 1)
181
+ should "not allow #{attribute} to be less than #{min_length} chars long" do
182
+ assert object = klass.find(:first), "Can't find first #{klass}"
183
+ object.send("#{attribute}=", min_value)
184
+ assert !object.save, "Saved #{klass} with #{attribute} set to \"#{min_value}\""
185
+ assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{min_value}\""
186
+ assert_contains(object.errors.on(attribute), short_message, "when set to \"#{min_value}\"")
187
+ end
188
+ end
189
+
190
+ max_value = "x" * (max_length + 1)
191
+ should "not allow #{attribute} to be more than #{max_length} chars long" do
192
+ assert object = klass.find(:first), "Can't find first #{klass}"
193
+ object.send("#{attribute}=", max_value)
194
+ assert !object.save, "Saved #{klass} with #{attribute} set to \"#{max_value}\""
195
+ assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{max_value}\""
196
+ assert_contains(object.errors.on(attribute), long_message, "when set to \"#{max_value}\"")
197
+ end
198
+ end
199
+
200
+ # Ensure that the attribute is in the range specified
201
+ # Requires an existing record
202
+ #
203
+ # Options:
204
+ # * <tt>:low_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
205
+ # Regexp or string. Default = <tt>/included/</tt>
206
+ # * <tt>:high_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
207
+ # Regexp or string. Default = <tt>/included/</tt>
208
+ #
209
+ # Example:
210
+ # should_ensure_value_in_range :age, (0..100)
211
+ def should_ensure_value_in_range(attribute, range, opts = {})
212
+ low_message, high_message = get_options!([opts], :low_message, :high_message)
213
+ low_message ||= /included/
214
+ high_message ||= /included/
215
+
216
+ klass = model_class
217
+ min = range.first
218
+ max = range.last
219
+
220
+ should "not allow #{attribute} to be less than #{min}" do
221
+ v = min - 1
222
+ assert object = klass.find(:first), "Can't find first #{klass}"
223
+ object.send("#{attribute}=", v)
224
+ assert !object.save, "Saved #{klass} with #{attribute} set to \"#{v}\""
225
+ assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\""
226
+ assert_contains(object.errors.on(attribute), low_message, "when set to \"#{v}\"")
227
+ end
228
+
229
+ should "not allow #{attribute} to be more than #{max}" do
230
+ v = max + 1
231
+ assert object = klass.find(:first), "Can't find first #{klass}"
232
+ object.send("#{attribute}=", v)
233
+ assert !object.save, "Saved #{klass} with #{attribute} set to \"#{v}\""
234
+ assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\""
235
+ assert_contains(object.errors.on(attribute), high_message, "when set to \"#{v}\"")
236
+ end
237
+ end
238
+
239
+ # Ensure that the attribute is numeric
240
+ # Requires an existing record
241
+ #
242
+ # Options:
243
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
244
+ # Regexp or string. Default = <tt>/number/</tt>
245
+ #
246
+ # Example:
247
+ # should_only_allow_numeric_values_for :age
248
+ def should_only_allow_numeric_values_for(*attributes)
249
+ message = get_options!(attributes, :message)
250
+ message ||= /number/
251
+ klass = model_class
252
+ attributes.each do |attribute|
253
+ attribute = attribute.to_sym
254
+ should "only allow numeric values for #{attribute}" do
255
+ assert object = klass.find(:first), "Can't find first #{klass}"
256
+ object.send(:"#{attribute}=", "abcd")
257
+ assert !object.valid?, "Instance is still valid"
258
+ assert_contains(object.errors.on(attribute), message)
259
+ end
260
+ end
261
+ end
262
+
263
+ # Ensures that the has_many relationship exists.
264
+ #
265
+ # Options:
266
+ # * <tt>:through</tt> - association name for <tt>has_many :through</tt>
267
+ #
268
+ # Example:
269
+ # should_have_many :friends
270
+ # should_have_many :enemies, :through => :friends
271
+ def should_have_many(*associations)
272
+ through = get_options!(associations, :through)
273
+ klass = model_class
274
+ associations.each do |association|
275
+ should "have many #{association}#{" through #{through}" if through}" do
276
+ reflection = klass.reflect_on_association(association)
277
+ assert reflection, "#{klass.name} does not have any relationship to #{association}"
278
+ assert_equal :has_many, reflection.macro
279
+ if through
280
+ through_reflection = klass.reflect_on_association(through)
281
+ assert through_reflection, "#{klass.name} does not have any relationship to #{through}"
282
+ assert_equal(through, reflection.options[:through])
283
+ end
284
+ end
285
+ end
286
+ end
287
+
288
+ # Ensures that the has_and_belongs_to_many relationship exists.
289
+ #
290
+ # should_have_and_belong_to_many :posts, :cars
291
+ def should_have_and_belong_to_many(*associations)
292
+ get_options!(associations)
293
+ klass = model_class
294
+ associations.each do |association|
295
+ should "should have and belong to many #{association}" do
296
+ assert klass.reflect_on_association(association), "#{klass.name} does not have any relationship to #{association}"
297
+ assert_equal :has_and_belongs_to_many, klass.reflect_on_association(association).macro
298
+ end
299
+ end
300
+ end
301
+
302
+ # Ensure that the has_one relationship exists.
303
+ #
304
+ # should_have_one :god # unless hindu
305
+ def should_have_one(*associations)
306
+ get_options!(associations)
307
+ klass = model_class
308
+ associations.each do |association|
309
+ should "have one #{association}" do
310
+ assert klass.reflect_on_association(association), "#{klass.name} does not have any relationship to #{association}"
311
+ assert_equal :has_one, klass.reflect_on_association(association).macro
312
+ end
313
+ end
314
+ end
315
+
316
+ # Ensure that the belongs_to relationship exists.
317
+ #
318
+ # should_belong_to :parent
319
+ def should_belong_to(*associations)
320
+ get_options!(associations)
321
+ klass = model_class
322
+ associations.each do |association|
323
+ should "belong_to #{association}" do
324
+ reflection = klass.reflect_on_association(association)
325
+ assert reflection, "#{klass.name} does not have any relationship to #{association}"
326
+ assert_equal :belongs_to, reflection.macro
327
+ fk = reflection.options[:foreign_key] || "#{association}_id"
328
+ assert klass.column_names.include?(fk), "#{klass.name} does not have a #{fk} foreign key."
329
+ end
330
+ end
331
+ end
332
+
333
+ private
334
+
335
+ include ThoughtBot::Shoulda::Private
336
+ end
337
+ end
338
+ end