pager-resource_controller 1.0.20080513

Sign up to get free protection for your applications and to get access to all the features.
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