instant-api 0.1.1

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 (118) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/Gemfile +14 -0
  4. data/Gemfile.lock +142 -0
  5. data/README.md +231 -0
  6. data/instant_api.gemspec +20 -0
  7. data/lib/instant_api.rb +13 -0
  8. data/lib/instant_api/controller/build_create.rb +34 -0
  9. data/lib/instant_api/controller/build_destroy.rb +23 -0
  10. data/lib/instant_api/controller/build_edit.rb +11 -0
  11. data/lib/instant_api/controller/build_index.rb +39 -0
  12. data/lib/instant_api/controller/build_new.rb +22 -0
  13. data/lib/instant_api/controller/build_resource.rb +29 -0
  14. data/lib/instant_api/controller/build_show.rb +25 -0
  15. data/lib/instant_api/controller/build_update.rb +46 -0
  16. data/lib/instant_api/controller/builder.rb +54 -0
  17. data/lib/instant_api/controller/exception_handler.rb +38 -0
  18. data/lib/instant_api/controller/parameters.rb +43 -0
  19. data/lib/instant_api/controller/routes.rb +46 -0
  20. data/lib/instant_api/model/active_record_query_builder.rb +36 -0
  21. data/lib/instant_api/model/association_reflector.rb +93 -0
  22. data/lib/instant_api/model/builder.rb +95 -0
  23. data/lib/instant_api/model/collection.rb +43 -0
  24. data/lib/instant_api/model/resource.rb +14 -0
  25. data/lib/instant_api/util/array.rb +15 -0
  26. data/lib/instant_api/version.rb +3 -0
  27. data/spec/dummy/Gemfile +58 -0
  28. data/spec/dummy/Gemfile.lock +193 -0
  29. data/spec/dummy/Rakefile +6 -0
  30. data/spec/dummy/app/assets/images/.keep +0 -0
  31. data/spec/dummy/app/assets/javascripts/application.js +16 -0
  32. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  33. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  34. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  35. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  36. data/spec/dummy/app/mailers/.keep +0 -0
  37. data/spec/dummy/app/models/.keep +0 -0
  38. data/spec/dummy/app/models/a.rb +5 -0
  39. data/spec/dummy/app/models/address.rb +4 -0
  40. data/spec/dummy/app/models/b.rb +4 -0
  41. data/spec/dummy/app/models/c.rb +4 -0
  42. data/spec/dummy/app/models/concerns/.keep +0 -0
  43. data/spec/dummy/app/models/country.rb +4 -0
  44. data/spec/dummy/app/models/d.rb +3 -0
  45. data/spec/dummy/app/models/movie.rb +4 -0
  46. data/spec/dummy/app/models/user.rb +8 -0
  47. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  48. data/spec/dummy/bin/bundle +3 -0
  49. data/spec/dummy/bin/rails +4 -0
  50. data/spec/dummy/bin/rake +4 -0
  51. data/spec/dummy/config.ru +4 -0
  52. data/spec/dummy/config/application.rb +23 -0
  53. data/spec/dummy/config/boot.rb +4 -0
  54. data/spec/dummy/config/database.yml +39 -0
  55. data/spec/dummy/config/environment.rb +5 -0
  56. data/spec/dummy/config/environments/development.rb +29 -0
  57. data/spec/dummy/config/environments/production.rb +80 -0
  58. data/spec/dummy/config/environments/test.rb +36 -0
  59. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  60. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  61. data/spec/dummy/config/initializers/inflections.rb +16 -0
  62. data/spec/dummy/config/initializers/instant_api.rb +3 -0
  63. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  64. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  65. data/spec/dummy/config/initializers/session_store.rb +3 -0
  66. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  67. data/spec/dummy/config/locales/en.yml +23 -0
  68. data/spec/dummy/config/routes.rb +3 -0
  69. data/spec/dummy/db/migrate/20131019140756_create_users.rb +14 -0
  70. data/spec/dummy/db/migrate/20131019141942_create_addresses.rb +14 -0
  71. data/spec/dummy/db/migrate/20131020003152_a.rb +9 -0
  72. data/spec/dummy/db/migrate/20131020003245_b.rb +10 -0
  73. data/spec/dummy/db/migrate/20131020003354_c.rb +11 -0
  74. data/spec/dummy/db/migrate/20131020164202_d.rb +9 -0
  75. data/spec/dummy/db/migrate/20131020164349_ad.rb +8 -0
  76. data/spec/dummy/db/migrate/20140419205834_create_countries.rb +9 -0
  77. data/spec/dummy/db/migrate/20140421005321_create_movies.rb +8 -0
  78. data/spec/dummy/db/migrate/20140421005435_create_countries_movies.rb +8 -0
  79. data/spec/dummy/db/schema.rb +88 -0
  80. data/spec/dummy/db/seeds.rb +7 -0
  81. data/spec/dummy/lib/assets/.keep +0 -0
  82. data/spec/dummy/lib/tasks/.keep +0 -0
  83. data/spec/dummy/log/.keep +0 -0
  84. data/spec/dummy/public/404.html +58 -0
  85. data/spec/dummy/public/422.html +58 -0
  86. data/spec/dummy/public/500.html +57 -0
  87. data/spec/dummy/public/favicon.ico +0 -0
  88. data/spec/dummy/public/robots.txt +5 -0
  89. data/spec/dummy/vendor/assets/javascripts/.keep +0 -0
  90. data/spec/dummy/vendor/assets/stylesheets/.keep +0 -0
  91. data/spec/factories/address_factory.rb +8 -0
  92. data/spec/factories/country_factory.rb +5 -0
  93. data/spec/factories/movies_factory.rb +5 -0
  94. data/spec/factories/user_factory.rb +10 -0
  95. data/spec/functional/create_spec.rb +16 -0
  96. data/spec/functional/destroy_spec.rb +21 -0
  97. data/spec/functional/edit_spec.rb +19 -0
  98. data/spec/functional/index_spec.rb +82 -0
  99. data/spec/functional/new_spec.rb +14 -0
  100. data/spec/functional/show_spec.rb +20 -0
  101. data/spec/functional/update_spec.rb +47 -0
  102. data/spec/spec_helper.rb +34 -0
  103. data/spec/support/database_cleaner.rb +20 -0
  104. data/spec/support/helpers.rb +31 -0
  105. data/spec/unit/lib/instant_api/controller/build_create_spec.rb +38 -0
  106. data/spec/unit/lib/instant_api/controller/build_destroy_spec.rb +25 -0
  107. data/spec/unit/lib/instant_api/controller/build_edit_spec.rb +24 -0
  108. data/spec/unit/lib/instant_api/controller/build_index_spec.rb +49 -0
  109. data/spec/unit/lib/instant_api/controller/build_new_spec.rb +18 -0
  110. data/spec/unit/lib/instant_api/controller/build_resource_spec.rb +27 -0
  111. data/spec/unit/lib/instant_api/controller/build_show_spec.rb +24 -0
  112. data/spec/unit/lib/instant_api/controller/build_update_spec.rb +66 -0
  113. data/spec/unit/lib/instant_api/controller/builder_spec.rb +18 -0
  114. data/spec/unit/lib/instant_api/controller/parameters_spec.rb +53 -0
  115. data/spec/unit/lib/instant_api/model/active_record_query_builder_spec.rb +133 -0
  116. data/spec/unit/lib/instant_api/model/builder_spec.rb +237 -0
  117. data/spec/unit/lib/instant_api/model/join_calculator_spec.rb +27 -0
  118. metadata +202 -0
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe InstantApi::Controller::BuildNew do
4
+ let(:controller) { Object.new }
5
+
6
+ subject { InstantApi::Controller::BuildNew.new(controller) }
7
+
8
+ describe '#build' do
9
+ before { subject.build }
10
+
11
+ it { controller.respond_to?(:new).should be_true }
12
+
13
+ context 'call to new' do
14
+ before { controller.should_receive(:head).with(:ok).and_return(true) }
15
+ it { controller.new.should be_true }
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe InstantApi::Controller::BuildResource do
4
+ let(:controller) { Object.new }
5
+ let(:model_class_name) { 'Aclass' }
6
+
7
+ subject { InstantApi::Controller::BuildResource.new(controller, model_class_name) }
8
+
9
+ describe '#build' do
10
+ before { subject.build }
11
+
12
+ context 'call to resource' do
13
+ let(:resource) { Object.new }
14
+ let(:params) { {id: 3} }
15
+ let(:request) { double(:request, path: '/a/3')}
16
+ class Aclass; end
17
+
18
+ before do
19
+ controller.should_receive(:params).and_return(params)
20
+ controller.should_receive(:request).and_return(request)
21
+ Aclass.should_receive(:find).with(3).and_return(resource)
22
+ end
23
+
24
+ it { controller.send(:resource).should eq(resource) }
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe InstantApi::Controller::BuildShow do
4
+ let(:controller) { Object.new }
5
+
6
+ subject { InstantApi::Controller::BuildShow.new(controller) }
7
+
8
+ describe '#build' do
9
+ before { subject.build }
10
+
11
+ it { controller.respond_to?(:show).should be_true }
12
+
13
+ context 'call to show' do
14
+ let(:resource) { Object.new }
15
+
16
+ before do
17
+ controller.should_receive(:resource).and_return(resource)
18
+ controller.should_receive(:render).with({json: resource}).and_return(true)
19
+ end
20
+
21
+ it { controller.show.should be_true }
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe InstantApi::Controller::BuildUpdate do
4
+ let(:controller) { Object.new }
5
+ let(:model_class_name) { 'OtherClass' }
6
+
7
+ subject { InstantApi::Controller::BuildUpdate.new(controller, model_class_name) }
8
+
9
+ describe '#build' do
10
+ before { subject.build }
11
+
12
+ it { controller.respond_to?(:update).should be_true }
13
+
14
+ describe '#update' do
15
+ let(:resource) { double('resource', valid?: true, invalid?: false) }
16
+ let(:strong_params) { Object.new }
17
+
18
+ before do
19
+ resource.should_receive(:update_attributes!).with(strong_params)
20
+
21
+ controller.should_receive(:resource).any_number_of_times.and_return(resource)
22
+ controller.should_receive(:check_strong_parameters).and_return(strong_params)
23
+ controller.should_receive(:render).with(json: resource).and_return(true)
24
+ end
25
+
26
+ it { controller.update.should be_true }
27
+ end
28
+
29
+ describe '#check_strong_parameters' do
30
+ context 'with strong parameters' do
31
+ let(:params) { Object.new }
32
+ let(:params_require) { Object.new }
33
+ let(:model_class_name) { 'AnotherClass' }
34
+ class AnotherClass
35
+ def self.strong_parameters
36
+ 'a'
37
+ end
38
+ end
39
+
40
+ before do
41
+ params.should_receive(:require).with(:another_class).and_return(params_require)
42
+ params_require.should_receive(:permit).with('a').and_return(true)
43
+ controller.should_receive(:params).and_return(params)
44
+ end
45
+
46
+ it { controller.send(:check_strong_parameters).should be_true }
47
+ end
48
+
49
+ context 'without strong parameters' do
50
+ let(:params) { Object.new }
51
+ let(:params_require) { Object.new }
52
+ let(:model_class_name) { 'OtherClass' }
53
+ class OtherClass; end
54
+
55
+
56
+ before do
57
+ params.should_receive(:require).with(:other_class).and_return(params_require)
58
+ params_require.should_receive(:permit!).and_return(true)
59
+ controller.should_receive(:params).and_return(params)
60
+ end
61
+
62
+ it { controller.send(:check_strong_parameters).should be_true }
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+ require 'instant_api/controller/builder'
3
+
4
+ describe InstantApi::Controller::Builder do
5
+ subject { InstantApi::Controller::Builder.new('users', [:index, :show]) }
6
+
7
+ describe '#build_class' do
8
+ it do
9
+ controller = subject.build_class
10
+
11
+ instance = controller.new
12
+ instance.respond_to?(:show).should be_true
13
+ instance.respond_to?(:index).should be_true
14
+ instance.respond_to?(:edit).should be_false
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+ require 'instant_api/controller/parameters'
3
+
4
+ describe InstantApi::Controller::Parameters do
5
+ subject { InstantApi::Controller::Parameters.new(params, request_path) }
6
+
7
+ describe '#[]' do
8
+ let(:request_path) { '/users' }
9
+
10
+ context 'a string key' do
11
+ let(:params) { { 'id' => 3 } }
12
+ it { subject['id'].should eq(3) }
13
+ end
14
+
15
+ context 'a symbol key' do
16
+ let(:params) { { id: 3 } }
17
+ it { subject[:id].should eq(3) }
18
+ end
19
+
20
+ context 'remove internal rails params' do
21
+ %w(controller action format _method only_path).each do |param|
22
+ let(:params) { { param => 'hi' } }
23
+ it { subject[param].should be_nil }
24
+ end
25
+ end
26
+ end
27
+
28
+ describe '#resources' do
29
+ let(:params) { Hash.new }
30
+
31
+ context 'return the resources' do
32
+ let(:request_path) { '/users/2/addresses' }
33
+ it { subject.resources.should eq([:users, :addresses]) }
34
+ end
35
+
36
+ context 'ignore trailing /' do
37
+ let(:request_path) { '/users/2/addresses/' }
38
+ it { subject.resources.should eq([:users, :addresses]) }
39
+ end
40
+
41
+ context 'ignore trailing /' do
42
+ let(:request_path) { '/users/2/addresses/2/ ' }
43
+ it { subject.resources.should eq([:users, :addresses]) }
44
+ end
45
+
46
+ context 'remove the rails methods' do
47
+ %w(new edit).each do |method|
48
+ let(:request_path) { "/users/2/addresses/#{method}" }
49
+ it { subject.resources.should eq([:users, :addresses]) }
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,133 @@
1
+ require 'spec_helper'
2
+ require 'instant_api/controller/parameters'
3
+ require 'instant_api/model/builder'
4
+ require 'instant_api/model/active_record_query_builder'
5
+
6
+ describe InstantApi::Model::ActiveRecordQueryBuilder do
7
+ subject { InstantApi::Model::ActiveRecordQueryBuilder.new(model) }
8
+ let(:params) { InstantApi::Controller::Parameters.new(rails_params, request_path) }
9
+
10
+ let(:user) { create(:user) }
11
+ let(:address) { create(:address, user_id: user.id) }
12
+ let(:country) { create(:country, address_id: address.id) }
13
+
14
+ let(:another_user) { create(:user) }
15
+ let(:another_address) { create(:address, user_id: another_user.id) }
16
+ let(:another_country) { create(:country, address_id: another_address.id) }
17
+
18
+ describe '#query' do
19
+ context 'returns all first level model' do
20
+ let(:model) { User }
21
+ let(:rails_params) { Hash.new }
22
+ let(:request_path) { '/users' }
23
+ it { subject.query(params).should eq([user]) }
24
+ end
25
+
26
+ context 'return all second level model' do
27
+ let(:model) { Address }
28
+ let(:rails_params) { { user_id: user.id } }
29
+ let(:request_path) { "/users/#{user.id}/addresses" }
30
+
31
+ it { subject.query(params).should eq([address]) }
32
+ end
33
+
34
+ context 'additional parameters' do
35
+ let(:model) { Address }
36
+ let(:rails_params) { { user_id: user.id, city: 'Barcelona' } }
37
+ let(:request_path) { "/users/#{user.id}/addresses" }
38
+ let(:address2) { create(:address, user_id: user.id, city: 'Barcelona') }
39
+ let(:address3) { create(:address, user_id: user.id, city: 'Madrid') }
40
+
41
+ it { subject.query(params).should eq([address2]) }
42
+ end
43
+
44
+ context 'query second level model with an invalid id, returns nothing' do
45
+ let(:model) { Address }
46
+ let(:rails_params) { { user_id: user.id } }
47
+ let(:request_path) { "/users/#{user.id}/addresses/#{another_address.id}" }
48
+
49
+ it { subject.query(params).should be_empty }
50
+ end
51
+
52
+ context 'return all third level model' do
53
+ let(:model) { Country }
54
+ let(:rails_params) { { user_id: user.id, address_id: address.id } }
55
+ let(:request_path) { "/users/#{user.id}/addresses/#{address.id}/countries" }
56
+
57
+ it { subject.query(params).should eq([country]) }
58
+ end
59
+
60
+ context 'query third level model with an invalid id, returns nothing' do
61
+ let(:model) { Country }
62
+ let(:rails_params) { { user_id: user.id, address_id: another_address.id } }
63
+ let(:request_path) { "/users/#{user.id}/addresses/#{another_address.id}/countries" }
64
+
65
+ it { subject.query(params).should be_empty }
66
+ end
67
+
68
+ context 'return all third level model with additional params' do
69
+ let(:model) { Country }
70
+ let(:rails_params) { { user_id: user.id, address_id: address.id } }
71
+ let(:request_path) { "/users/#{user.id}/addresses/#{address.id}/countries" }
72
+
73
+ context 'return data' do
74
+ before { rails_params[:name] = country.name }
75
+ it { subject.query(params).should eq([country]) }
76
+ end
77
+
78
+ # TODO
79
+ #context 'return nothins' do
80
+ # before { params[:name] = country.name + 'nothing' }
81
+ # it { subject.query(params).should be_empty }
82
+ #end
83
+ end
84
+
85
+ context 'has_and_belongs_to_many' do
86
+ let(:movie) { create(:movie, countries: [country]) }
87
+ before { country.movies = [movie] }
88
+
89
+ let(:model) { Country }
90
+ let(:rails_params) { { movie_id: movie.id } }
91
+ let(:request_path) { "/movies/#{movie.id}/countries" }
92
+
93
+ it { subject.query(params).should eq([country]) }
94
+ end
95
+ end
96
+
97
+ describe '#find_first' do
98
+ context 'returns first level model' do
99
+ let(:model) { User }
100
+ let(:rails_params) { { id: user.id } }
101
+ let(:request_path) { "/users/#{user.id}" }
102
+ it { subject.find_first(params).should eq(user) }
103
+ end
104
+
105
+ context 'raises exception if no item exists' do
106
+ let(:model) { User }
107
+ let(:rails_params) { { id: user.id + another_user.id } }
108
+ let(:request_path) { "/users/#{user.id}" }
109
+ it { expect { subject.find_first(params) }.to raise_error(ActiveRecord::RecordNotFound) }
110
+ end
111
+
112
+ context 'raises exception if no id is passed' do
113
+ let(:model) { User }
114
+ let(:rails_params) { Hash.new }
115
+ let(:request_path) { "/users/#{user.id}" }
116
+ it { expect { subject.find_first(params) }.to raise_error(ActiveRecord::RecordNotFound) }
117
+ end
118
+
119
+ context 'returns second level model' do
120
+ let(:model) { Address }
121
+ let(:rails_params) { { user_id: user.id, id: address.id } }
122
+ let(:request_path) { "/users/#{user.id}/addresses/#{address.id}" }
123
+ it { subject.find_first(params).should eq(address) }
124
+ end
125
+
126
+ context 'second level model with invalid id, raises exception' do
127
+ let(:model) { Address }
128
+ let(:rails_params) { { user_id: user.id, id: another_address.id } }
129
+ let(:request_path) { "/users/#{user.id}/addresses/#{another_address.id}" }
130
+ it { expect { subject.find_first(params) }.to raise_error(ActiveRecord::RecordNotFound) }
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,237 @@
1
+ require 'spec_helper'
2
+ require 'instant_api/model/builder'
3
+
4
+ describe InstantApi::Model::Builder do
5
+ subject { InstantApi::Model::Builder.new(params, model, true) }
6
+
7
+ describe '#build' do
8
+ let(:model) { User }
9
+ let(:params) { { user: data }.with_indifferent_access }
10
+
11
+ context 'successful' do
12
+ context 'string parameter' do
13
+ let(:data) { { email: 'a' } }
14
+ it { validate_record(subject.build, :email, 'a') }
15
+ end
16
+
17
+ context 'integer parameter' do
18
+ let(:data) { { age: 1 } }
19
+ it { validate_record(subject.build, :age, 1) }
20
+ end
21
+
22
+ context 'date parameter' do
23
+ let(:data) { { born_at: '01/03/1974' } }
24
+ let(:born_at) { Date.parse(data[:born_at]) }
25
+ it { validate_record(subject.build, :born_at, born_at) }
26
+ end
27
+
28
+ context 'datetime parameter' do
29
+ let(:data) { { registered_at: '10:29 01/03/1974' } }
30
+ let(:registered_at) { DateTime.parse(data[:registered_at]) }
31
+ it { validate_record(subject.build, :registered_at, registered_at) }
32
+ end
33
+
34
+ context 'decimal parameter' do
35
+ let(:data) { { money: '10.99' } }
36
+ let(:money) { BigDecimal.new('10.99') }
37
+ it { validate_record(subject.build, :money, money) }
38
+ end
39
+
40
+ context 'boolean parameter' do
41
+ context 'value true' do
42
+ let(:data) { { terms_accepted: 1 } }
43
+ it { validate_record(subject.build, :terms_accepted, true) }
44
+ end
45
+
46
+ context 'value false' do
47
+ let(:data) { { terms_accepted: 0 } }
48
+ it { validate_record(subject.build, :terms_accepted, false) }
49
+ end
50
+ end
51
+
52
+ context 'build nested model' do
53
+ let(:data) { { addresses: { street: 'a' } }.with_indifferent_access }
54
+
55
+ it 'creates a record' do
56
+ record = subject.build
57
+ record.valid?.should be_true
58
+ record.addresses.size.should eq(1)
59
+ end
60
+ end
61
+ end
62
+
63
+ context 'nested objects' do
64
+ let(:model) { A }
65
+ let(:params) do
66
+ { a: { value: 'a', b: { value: 'b', c: { value: 'c' } } } }.with_indifferent_access
67
+ end
68
+
69
+ context 'successful' do
70
+ it 'build the nested objects' do
71
+ record = subject.build
72
+
73
+ record.value.should eq('a')
74
+ record.b.size.should eq(1)
75
+ record.b.first.value.should eq('b')
76
+ record.b.first.c.size.should eq(1)
77
+ record.b.first.c.first.value.should eq('c')
78
+ end
79
+ end
80
+ end
81
+
82
+ context 'has_one' do
83
+ let(:model) { A }
84
+ let(:params) do
85
+ { a: { value: 'a', c: { value: 'c' } } }.with_indifferent_access
86
+ end
87
+
88
+ it 'build the nested objects' do
89
+ record = subject.build
90
+
91
+ record.valid?.should be_true
92
+ record.value.should eq('a')
93
+ record.c.value.should eq('c')
94
+ record.c.a.value.should eq('a')
95
+ end
96
+ end
97
+
98
+ context 'has_many' do
99
+ let(:model) { A }
100
+ let(:params) do
101
+ { a: { value: 'a', b: { value: 'b' } } }.with_indifferent_access
102
+ end
103
+
104
+ it 'build the nested objects' do
105
+ record = subject.build
106
+
107
+ record.valid?.should be_true
108
+ record.value.should eq('a')
109
+ record.b.map(&:value).to_a.should eq(['b'])
110
+ record.b.first.a.should eq(record)
111
+ end
112
+ end
113
+
114
+ context 'belongs_to' do
115
+ let(:model) { B }
116
+ let(:params) do
117
+ { b: { value: 'b', a: { value: 'a' } } }.with_indifferent_access
118
+ end
119
+
120
+ it 'build the nested objects' do
121
+ record = subject.build
122
+
123
+ record.valid?.should be_true
124
+ record.value.should eq('b')
125
+ record.a.value.should eq('a')
126
+ record.a.b.to_a.should eq([record])
127
+ end
128
+ end
129
+
130
+ context 'has_and_belongs_to_many' do
131
+ let(:model) { A }
132
+ let(:params) do
133
+ { a: { value: 'a', d: { value: 'd' } } }.with_indifferent_access
134
+ end
135
+
136
+ it 'build the nested objects' do
137
+ record = subject.build
138
+
139
+ record.valid?.should be_true
140
+ record.value.should eq('a')
141
+ record.d.map(&:value).to_a.should eq(['d'])
142
+ record.d.first.a.to_a.should eq([record])
143
+ end
144
+ end
145
+
146
+ context 'failed' do
147
+ let(:data) { { email: 'a' } }
148
+ after { User.reset_callbacks(:validate) }
149
+
150
+ context 'mandatory parameter' do
151
+ let(:data) { Hash.new }
152
+ before { User.class_eval { validates_presence_of :email } }
153
+
154
+ it {
155
+ begin
156
+ validate_error(subject.build, :email, "can't be blank")
157
+ rescue
158
+ puts $!.message
159
+ puts $!.backtrace
160
+
161
+ end
162
+ }
163
+ end
164
+
165
+ context 'length validations' do
166
+ before { User.class_eval { validates :email, length: { minimum: 2 } } }
167
+
168
+ it { validate_error(subject.build, :email, 'is too short (minimum is 2 characters)') }
169
+ end
170
+
171
+ context 'acceptance validation' do
172
+ before { User.class_eval { validates_acceptance_of :terms, allow_nil: false } }
173
+
174
+ it { validate_error(subject.build, :terms, 'must be accepted') }
175
+ end
176
+
177
+ context 'confirmation validation' do
178
+ let(:data) { { email: 'a', email_confirmation: 'b' } }
179
+ before { User.class_eval { validates :email, confirmation: true } }
180
+
181
+ it { validate_error(subject.build, :email_confirmation, "doesn't match Email") }
182
+ end
183
+
184
+ context 'exclusion validation' do
185
+ before { User.class_eval { validates :email, exclusion: { in: %w(a b) } } }
186
+
187
+ it { validate_error(subject.build, :email, 'is reserved') }
188
+ end
189
+
190
+ context 'format validation' do
191
+ before { User.class_eval { validates :email, format: { with: /[0-9]/ } } }
192
+
193
+ it { validate_error(subject.build, :email, 'is invalid') }
194
+ end
195
+
196
+ context 'inclusion validation' do
197
+ before { User.class_eval { validates :email, inclusion: { in: %w(b c) } } }
198
+
199
+ it { validate_error(subject.build, :email, 'is not included in the list') }
200
+ end
201
+
202
+ context 'numericality validation' do
203
+ before { User.class_eval { validates :email, numericality: true } }
204
+
205
+ it { validate_error(subject.build, :email, 'is not a number') }
206
+ end
207
+
208
+ context 'absence validation' do
209
+ before { User.class_eval { validates :email, absence: true } }
210
+
211
+ it { validate_error(subject.build, :email, 'must be blank') }
212
+ end
213
+
214
+ context 'uniqueness validation' do
215
+ before do
216
+ User.create!(data)
217
+ User.class_eval { validates :email, uniqueness: true }
218
+ end
219
+
220
+ it { validate_error(subject.build, :email, 'has already been taken', 1) }
221
+ end
222
+ end
223
+
224
+ private
225
+ def validate_error(record, field, msg, count = 0)
226
+ record.valid?.should be_false
227
+ record.errors.size.should eq(1)
228
+ record.errors[field].should eq([msg])
229
+ record.class.count.should eq(count)
230
+ end
231
+
232
+ def validate_record(record, field, value)
233
+ record.valid?.should be_true
234
+ record.send(field).should eq(value)
235
+ end
236
+ end
237
+ end