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,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Create', type: :controller do
4
+ let(:user_params) { attributes_for(:user) }
5
+
6
+ before do
7
+ @controller = InstantApi::Controller::Builder.new('users', [:create]).build_class.new
8
+ end
9
+
10
+ it 'successful' do
11
+ post(:create, user: user_params)
12
+
13
+ validate_user(JSON.parse(response.body).symbolize_keys, user_params)
14
+ end
15
+
16
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Destroy', type: :controller do
4
+ let!(:user) { create(:user) }
5
+
6
+ before do
7
+ @controller = InstantApi::Controller::Builder.new('users', [:destroy]).build_class.new
8
+ end
9
+
10
+ it 'successful' do
11
+ delete(:destroy, id: user.id)
12
+
13
+ response.should be_successful
14
+ User.where(id: user.id).should be_empty
15
+ end
16
+
17
+ it 'returns 404 when the resource does not exists' do
18
+ delete(:destroy, id: user.id + 1)
19
+ response.status.should eq(404)
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Edit', type: :controller do
4
+ let!(:user) { create(:user) }
5
+
6
+ before do
7
+ @controller = InstantApi::Controller::Builder.new('users', [:edit]).build_class.new
8
+ end
9
+
10
+ it 'successful' do
11
+ get(:edit, id: user.id)
12
+ response.should be_successful
13
+ end
14
+
15
+ it 'returns 404 when the resource does not exists' do
16
+ get(:edit, id: user.id + 1)
17
+ response.status.should eq(404)
18
+ end
19
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Index', type: :controller do
4
+ before do
5
+ @controller = InstantApi::Controller::Builder.new('users', [:index]).build_class.new
6
+ end
7
+
8
+ context 'returns empty data' do
9
+ it 'successful' do
10
+ get(:index)
11
+
12
+ json = JSON.parse(response.body).symbolize_keys
13
+ validate_pagination(json[:pagination], 0, 1, 10)
14
+ json[:collection].should be_empty
15
+ end
16
+ end
17
+
18
+ context 'pagination' do
19
+ let!(:user1) { create(:user) }
20
+ let!(:user2) { create(:user) }
21
+
22
+ context 'all in same page' do
23
+ let(:expected_collection) { [user1, user2] }
24
+
25
+ it 'return first user' do
26
+ get(:index)
27
+
28
+ json = JSON.parse(response.body).symbolize_keys
29
+ validate_pagination(json[:pagination], 2, 1, 10)
30
+ validate_user_collection(json[:collection], expected_collection)
31
+ end
32
+ end
33
+
34
+ context 'first page' do
35
+ let(:expected_collection) { [user1] }
36
+
37
+ it 'return first user' do
38
+ get(:index, page: 1, per_page: 1)
39
+
40
+ json = JSON.parse(response.body).symbolize_keys
41
+ validate_pagination(json[:pagination], 2, 1, 1)
42
+ validate_user_collection(json[:collection], expected_collection)
43
+ end
44
+ end
45
+
46
+ context 'second page' do
47
+ let(:expected_collection) { [user2] }
48
+
49
+ it 'record second user' do
50
+ get(:index, page: 2, per_page: 1)
51
+
52
+ json = JSON.parse(response.body).symbolize_keys
53
+ validate_pagination(json[:pagination], 2, 2, 1)
54
+ validate_user_collection(json[:collection], expected_collection)
55
+ end
56
+ end
57
+
58
+ context 'a page that does not exists' do
59
+ let(:expected_collection) { [] }
60
+
61
+ it 'returns nothing' do
62
+ get(:index, page: 2)
63
+
64
+ json = JSON.parse(response.body).symbolize_keys
65
+ validate_pagination(json[:pagination], 2, 2, 10)
66
+ validate_user_collection(json[:collection], expected_collection)
67
+ end
68
+ end
69
+
70
+ context 'invalid page number' do
71
+ let(:expected_collection) { [user1, user2] }
72
+
73
+ it 'returns first page' do
74
+ get(:index, page: 'a')
75
+
76
+ json = JSON.parse(response.body).symbolize_keys
77
+ validate_pagination(json[:pagination], 2, 1, 10)
78
+ validate_user_collection(json[:collection], expected_collection)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'New', type: :controller do
4
+ let!(:user) { create(:user) }
5
+
6
+ before do
7
+ @controller = InstantApi::Controller::Builder.new('users', [:new]).build_class.new
8
+ end
9
+
10
+ it 'successful' do
11
+ get(:new)
12
+ response.should be_successful
13
+ end
14
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Show', type: :controller do
4
+ let(:user) { create(:user) }
5
+
6
+ before do
7
+ @controller = InstantApi::Controller::Builder.new('users', [:show]).build_class.new
8
+ end
9
+
10
+ it 'successful' do
11
+ get(:show, id: user.id)
12
+
13
+ validate_user(JSON.parse(response.body).symbolize_keys, user)
14
+ end
15
+
16
+ it 'returns 404 when the resource does not exists' do
17
+ get(:show, id: user.id + 1)
18
+ response.status.should eq(404)
19
+ end
20
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Update', type: :controller do
4
+ let(:user) { create(:user) }
5
+
6
+ before do
7
+ @controller = InstantApi::Controller::Builder.new('users', [:update]).build_class.new
8
+ end
9
+
10
+ it 'successful' do
11
+ put(:update, id: user.id, user: {email: 'hello@at.com'})
12
+
13
+ user[:email] = 'hello@at.com'
14
+ validate_user(JSON.parse(response.body).symbolize_keys, user)
15
+ response.should be_successful
16
+ end
17
+
18
+ it 'returns 404 when the resource does not exists' do
19
+ put(:update, id: user.id + 1, user: {email: 'hello@at.com'})
20
+ response.status.should eq(404)
21
+ end
22
+
23
+ context 'returns error' do
24
+ after { User.reset_callbacks(:validate) }
25
+
26
+ before { User.class_eval { validates :email, length: { minimum: 2 } } }
27
+
28
+ it 'error in email' do
29
+ put(:update, id: user.id, user: {email: 'a'})
30
+ validate_errors(response.body,
31
+ [['email', ['is too short (minimum is 2 characters)']]])
32
+ end
33
+
34
+ context 'returns several errors' do
35
+ before do
36
+ User.class_eval { validates :age, numericality: { greater_than: 10 } }
37
+ end
38
+
39
+ it 'error in email and age' do
40
+ put(:update, id: user.id, user: {email: 'a', age: 3})
41
+ validate_errors(response.body,
42
+ [['email', ['is too short (minimum is 2 characters)']],
43
+ ['age', ['must be greater than 10']]])
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,34 @@
1
+ ENV['RAILS_ENV'] ||= 'test'
2
+ require File.expand_path('../dummy/config/environment', __FILE__)
3
+
4
+ require 'rspec'
5
+ require 'rspec/rails'
6
+ require 'ffaker'
7
+ require 'factory_girl'
8
+ require 'webmock/rspec'
9
+ require 'instant_api'
10
+
11
+ require 'support/database_cleaner'
12
+ require 'support/helpers'
13
+
14
+ ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration)
15
+
16
+ FactoryGirl.definition_file_paths << File.join(File.dirname(__FILE__), 'factories')
17
+ FactoryGirl.find_definitions
18
+
19
+ RSpec.configure do |config|
20
+ # If true, the base class of anonymous controllers will be inferred
21
+ # automatically. This will be the default behavior in future versions of
22
+ # rspec-rails.
23
+ config.infer_base_class_for_anonymous_controllers = false
24
+ config.render_views = true
25
+
26
+ config.include RSpec::Rails::ControllerExampleGroup,
27
+ type: :controller,
28
+ example_group: {
29
+ file_path: config.escaped_path(%w[spec unit controllers])
30
+ }
31
+
32
+ config.include(FactoryGirl::Syntax::Methods)
33
+ config.include(Helpers)
34
+ end
@@ -0,0 +1,20 @@
1
+ RSpec.configure do |config|
2
+ # disable rspec-rails wrapping of tests in a database transaction
3
+ config.use_transactional_fixtures = false
4
+
5
+ config.before(:suite) do
6
+ DatabaseCleaner.clean_with(:truncation)
7
+ end
8
+
9
+ config.before(:each) do
10
+ DatabaseCleaner.strategy = :transaction
11
+ end
12
+
13
+ config.before(:each) do
14
+ DatabaseCleaner.start
15
+ end
16
+
17
+ config.after(:each) do
18
+ DatabaseCleaner.clean
19
+ end
20
+ end
@@ -0,0 +1,31 @@
1
+ module Helpers
2
+ def validate_user(user, expected_user)
3
+ user[:id].should_not be_nil
4
+ user[:age].should eq(expected_user[:age])
5
+ Date.parse(user[:born_at]).should eq(expected_user[:born_at].to_date)
6
+ DateTime.parse(user[:registered_at]).to_i.should eq(expected_user[:registered_at].to_i)
7
+ user[:terms_accepted].should eq(expected_user[:terms_accepted])
8
+ user[:email].should eq(expected_user[:email])
9
+ BigDecimal.new(user[:money]).should eq(expected_user[:money])
10
+ user[:created_at].should_not be_nil
11
+ user[:updated_at].should_not be_nil
12
+ end
13
+
14
+ def validate_pagination(pagination, count, page, per_page)
15
+ pagination = pagination.symbolize_keys
16
+ pagination[:count].should eq(count)
17
+ pagination[:page].should eq(page)
18
+ pagination[:per_page].should eq(per_page)
19
+ end
20
+
21
+ def validate_user_collection(collection, expected_collection)
22
+ collection.each_with_index do |user, index|
23
+ validate_user(user.symbolize_keys, expected_collection[index])
24
+ end
25
+ end
26
+
27
+ def validate_errors(body, expected_errors)
28
+ json = JSON.parse(body).symbolize_keys
29
+ json[:errors].should eq(expected_errors)
30
+ end
31
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ describe InstantApi::Controller::BuildCreate do
4
+ let(:controller) { Object.new }
5
+ let(:model_class_name) { 'Aclass' }
6
+
7
+ subject { InstantApi::Controller::BuildCreate.new(controller, model_class_name) }
8
+
9
+ describe '#build' do
10
+ before { subject.build }
11
+
12
+ it { controller.respond_to?(:create).should be_true }
13
+
14
+ context 'call to create' do
15
+ let(:resource) { double('resource', valid?: true, invalid?: false) }
16
+ let(:params) { Object.new }
17
+ let(:parameters) { Object.new }
18
+ let(:builder) { double(:builder, build: resource) }
19
+ class Aclass; end
20
+
21
+ before do
22
+ controller.should_receive(:params).and_return(params)
23
+ controller.stub_chain(:request, :path).and_return('/path')
24
+
25
+ InstantApi::Controller::Parameters.should_receive(:new).
26
+ with(params, '/path').
27
+ and_return(parameters)
28
+ InstantApi::Model::Builder.should_receive(:new).
29
+ with(parameters, Aclass, true).
30
+ and_return(builder)
31
+
32
+ controller.should_receive(:render).with({json: resource}).and_return(true)
33
+ end
34
+
35
+ it { controller.create.should be_true }
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe InstantApi::Controller::BuildDestroy do
4
+ let(:controller) { Object.new }
5
+
6
+ subject { InstantApi::Controller::BuildDestroy.new(controller) }
7
+
8
+ describe '#build' do
9
+ before { subject.build }
10
+
11
+ it { controller.respond_to?(:destroy).should be_true }
12
+
13
+ context 'call to destroy' do
14
+ let(:resource) { double(:resource, destroy!: true) }
15
+
16
+ before do
17
+ controller.should_receive(:resource).and_return(resource)
18
+ controller.should_receive(:render).with({status: 200, nothing: true}).
19
+ and_return(true)
20
+ end
21
+
22
+ it { controller.destroy.should be_true }
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe InstantApi::Controller::BuildEdit do
4
+ let(:controller) { Object.new }
5
+
6
+ subject { InstantApi::Controller::BuildEdit.new(controller) }
7
+
8
+ describe '#build' do
9
+ before { subject.build }
10
+
11
+ it { controller.respond_to?(:edit).should be_true }
12
+
13
+ context 'call to edit' 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.edit.should be_true }
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ describe InstantApi::Controller::BuildIndex do
4
+ let(:controller) { Object.new }
5
+ let(:model_class_name) { 'Aclass' }
6
+
7
+ subject { InstantApi::Controller::BuildIndex.new(controller, model_class_name) }
8
+
9
+ describe '#build' do
10
+ before { subject.build }
11
+
12
+ it { controller.respond_to?(:index).should be_true }
13
+
14
+ context 'call to index' do
15
+ let(:paginated_collection) { Object.new }
16
+ let(:params) { Object.new }
17
+ let(:parameters) { Object.new }
18
+ let(:request) { double(:request, path: '/a') }
19
+ let(:response) do
20
+ {
21
+ collection: paginated_collection,
22
+ pagination: {
23
+ count: 7,
24
+ page: 3,
25
+ per_page: 5
26
+ }
27
+ }
28
+ end
29
+
30
+ before do
31
+ model_collection = InstantApi::Model::Collection.any_instance
32
+ model_collection.should_receive(:page).and_return(3)
33
+ model_collection.should_receive(:per_page).and_return(5)
34
+ model_collection.should_receive(:paginated_collection).and_return(paginated_collection)
35
+ model_collection.should_receive(:count).and_return(7)
36
+
37
+ InstantApi::Controller::Parameters.should_receive(:new).
38
+ with(params, request.path).
39
+ and_return(parameters)
40
+
41
+ controller.should_receive(:request).and_return(request)
42
+ controller.should_receive(:params).and_return(params)
43
+ controller.should_receive(:render).with(json: response).and_return(true)
44
+ end
45
+
46
+ it { controller.index.should be_true }
47
+ end
48
+ end
49
+ end