trax_controller 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.travis.yml +7 -0
  4. data/Gemfile +7 -1
  5. data/Guardfile +37 -0
  6. data/config.ru +7 -0
  7. data/lib/Guardfile +38 -0
  8. data/lib/trax/controller/action_types.rb +29 -0
  9. data/lib/trax/controller/actions.rb +60 -26
  10. data/lib/trax/controller/collection/pageable.rb +47 -0
  11. data/lib/trax/controller/collection/searchable.rb +4 -14
  12. data/lib/trax/controller/collection.rb +1 -0
  13. data/lib/trax/controller/engine.rb +9 -0
  14. data/lib/trax/controller/resource/base.rb +0 -1
  15. data/lib/trax/controller/serialization/adapters/json.rb +63 -0
  16. data/lib/trax/controller/serialization/adapters.rb +11 -0
  17. data/lib/trax/controller/serialization.rb +9 -0
  18. data/lib/trax/controller.rb +10 -10
  19. data/lib/trax_controller/version.rb +1 -1
  20. data/spec/internal/app/controllers/application_controller.rb +2 -0
  21. data/spec/internal/app/controllers/categories_controller.rb +53 -0
  22. data/spec/internal/app/controllers/products_controller.rb +14 -0
  23. data/spec/internal/app/models/category.rb +5 -0
  24. data/spec/internal/app/models/product.rb +5 -0
  25. data/spec/internal/app/models/widget.rb +4 -0
  26. data/spec/internal/app/serializers/categories_serializer.rb +3 -0
  27. data/spec/internal/app/serializers/category_serializer.rb +9 -0
  28. data/spec/internal/app/serializers/product_serializer.rb +3 -0
  29. data/spec/internal/app/serializers/widget_serializer.rb +3 -0
  30. data/spec/internal/config/database.yml +3 -0
  31. data/spec/internal/config/routes.rb +17 -0
  32. data/spec/internal/db/combustion_test.sqlite +0 -0
  33. data/spec/internal/db/schema.rb +61 -0
  34. data/spec/internal/log/.gitignore +1 -0
  35. data/spec/internal/public/favicon.ico +0 -0
  36. data/spec/spec_helper.rb +12 -5
  37. data/spec/trax/controller_spec.rb +1 -4
  38. data/spec/trax/controllers/categories_controller_spec.rb +206 -0
  39. data/spec/trax/controllers/products_controller_spec.rb +40 -0
  40. data/trax_controller.gemspec +4 -0
  41. metadata +92 -4
  42. data/lib/trax/controller/resource/errors.rb +0 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 073eda93c53968cf904385eae5a197bc0f32163a
4
- data.tar.gz: e3e923554c0072ee104eb984590706b103459850
3
+ metadata.gz: bdbaf7390613351761a1a3babb39b491bcdf7b1d
4
+ data.tar.gz: ea407ce95d7f6a417606e8ea8a022bc4f49ce392
5
5
  SHA512:
6
- metadata.gz: 81311299663e3952883c591f091e96fd749058088db5afc30fa3878f58c9778552eadbe6cb303c5b6c675e9553c44e8b00414b15e911b2932713651f2cbf964e
7
- data.tar.gz: 46174f18c3e3f0bb6e9d648fb6443122e3a13b619ad7c6f2ee73c9e42e3fd97a3b8477400338e79394e0a989f2378c4ae95c4cbd51df338940cc300480028f37
6
+ metadata.gz: 98358ac5bc8959c7abd30f3c8a80b1eeeeba40a25944997ee70e70a864940b4d6a35bd61b8304be3b3c730b740cbb7f6e7a209a04ed6313e9d63b1f040b3ff49
7
+ data.tar.gz: 821754fbed325e38de212341e8b6b6bbd583a248debf91b75edf3278fef22d7754a2fc8824243b10de9dec1c165da376c748f8be44999269987b7c155d8f3653
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ script: bundle exec rspec
3
+ rvm:
4
+ - 2.2
5
+ - 2.1
6
+ notifications:
7
+ email: false
data/Gemfile CHANGED
@@ -1,5 +1,11 @@
1
-
2
1
  source 'https://rubygems.org'
3
2
 
4
3
  # Specify your gem's dependencies in trax_controller.gemspec
5
4
  gemspec
5
+ # gem 'active_model_serializers', :github => 'rails-api/active_model_serializers', :branch => "0-10-stable"
6
+ gem 'active_model_serializers', :github => 'rails-api/active_model_serializers', :branch => "0-10-stable", :ref => "6aba26049154712d65176708ffad55fcfe512ca0"
7
+
8
+ group :test do
9
+ gem 'activerecord'
10
+ gem 'rspec-rails'
11
+ end
data/Guardfile ADDED
@@ -0,0 +1,37 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :bundler do
5
+ watch('Gemfile')
6
+ # Uncomment next line if your Gemfile contains the `gemspec' command.
7
+ # watch(/^.+\.gemspec/)
8
+ end
9
+
10
+ # Note: The cmd option is now required due to the increasing number of ways
11
+ # rspec may be run, below are examples of the most common uses.
12
+ # * bundler: 'bundle exec rspec'
13
+ # * bundler binstubs: 'bin/rspec'
14
+ # * spring: 'bin/rsspec' (This will use spring if running and you have
15
+ # installed the spring binstubs per the docs)
16
+ # * zeus: 'zeus rspec' (requires the server to be started separetly)
17
+ # * 'just' rspec: 'rspec'
18
+ guard :rspec, cmd: 'bundle exec rspec' do
19
+ watch(%r{^spec/.+_spec\.rb$})
20
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
21
+ watch('spec/spec_helper.rb') { "spec" }
22
+
23
+ # Rails example
24
+ watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
25
+ watch(%r{^app/(.*)(\.erb|\.haml|\.slim)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
26
+ watch(%r{^spec/internal/app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/trax/controllers/#{m[1]}_controller_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
27
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
28
+ watch('config/routes.rb') { "spec/routing" }
29
+ watch('spec/rails_helper.rb') { "spec" }
30
+
31
+ # Capybara features specs
32
+ watch(%r{^app/views/(.+)/.*\.(erb|haml|slim)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
33
+
34
+ # Turnip features and steps
35
+ watch(%r{^spec/acceptance/(.+)\.feature$})
36
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
37
+ end
data/config.ru ADDED
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ Bundler.require :default, :development
5
+
6
+ Combustion.initialize! :all
7
+ run Combustion::Application
data/lib/Guardfile ADDED
@@ -0,0 +1,38 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :bundler do
5
+ watch('Gemfile')
6
+ # Uncomment next line if your Gemfile contains the `gemspec' command.
7
+ # watch(/^.+\.gemspec/)
8
+ end
9
+
10
+ # Note: The cmd option is now required due to the increasing number of ways
11
+ # rspec may be run, below are examples of the most common uses.
12
+ # * bundler: 'bundle exec rspec'
13
+ # * bundler binstubs: 'bin/rspec'
14
+ # * spring: 'bin/rsspec' (This will use spring if running and you have
15
+ # installed the spring binstubs per the docs)
16
+ # * zeus: 'zeus rspec' (requires the server to be started separetly)
17
+ # * 'just' rspec: 'rspec'
18
+ guard :rspec, cmd: 'bundle exec rspec' do
19
+ watch(%r{^spec/.+_spec\.rb$})
20
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
21
+ watch('spec/spec_helper.rb') { "spec" }
22
+
23
+ # Rails example
24
+ watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
25
+ watch(%r{^app/(.*)(\.erb|\.haml|\.slim)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
26
+ watch(%r{^spec/internal/app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/trax/controllers/#{m[1]}_controller_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
27
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
28
+ watch('config/routes.rb') { "spec/routing" }
29
+ # watch('spec/internal/app/controllers/application_controller.rb') { "spec/controllers" }
30
+ watch('spec/rails_helper.rb') { "spec" }
31
+
32
+ # Capybara features specs
33
+ watch(%r{^app/views/(.+)/.*\.(erb|haml|slim)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
34
+
35
+ # Turnip features and steps
36
+ watch(%r{^spec/acceptance/(.+)\.feature$})
37
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
38
+ end
@@ -0,0 +1,29 @@
1
+ module Trax
2
+ module Controller
3
+ module ActionTypes
4
+ extend ::ActiveSupport::Concern
5
+ #todo: pretty janky but works for now
6
+
7
+ included do
8
+ class_attribute :action_types
9
+
10
+ self.action_types = Hashie::Mash.new({
11
+ :collection => [:index, :search],
12
+ :resource => [ :show, :delete, :update, :create, :login ]
13
+ })
14
+ end
15
+
16
+ def current_action
17
+ :"#{request.params["action"]}"
18
+ end
19
+
20
+ def collection_action?
21
+ self.class.action_types[:collection].include?(current_action)
22
+ end
23
+
24
+ def resource_action?
25
+ self.class.action_types[:resource].include? current_action
26
+ end
27
+ end
28
+ end
29
+ end
@@ -7,80 +7,114 @@ module Trax
7
7
  respond_to :json
8
8
  end
9
9
 
10
- def index
10
+ def index(collection: _collection, meta: collection_response_meta, serializer: collection_serializer, root: collection_root)
11
11
  render :json => collection,
12
- :meta => collection_response_meta,
13
- :each_serializer => collection_serializer,
14
- :root => collection_root
12
+ :meta => meta,
13
+ :each_serializer => serializer,
14
+ :root => root
15
15
  end
16
16
 
17
- def create
17
+ #setting resource ivar stops it from trying to create via the default behavior
18
+ def create(success_status:201, failure_status: :unprocessable_entity, **options)
19
+ set_resource_ivar(options[:resource]) if options.has_key?(:resource)
20
+
18
21
  create! do |success, failure|
19
22
  success.json do
20
- render_resource(201)
23
+ render_resource(success_status, **options)
21
24
  end
22
25
 
23
26
  failure.json do
24
- render_errors(:unprocessable_entity, *resource.errors.full_messages)
27
+ render_errors(failure_status, **options)
25
28
  end
26
29
  end
27
30
  end
28
31
 
29
- def update
32
+ def update(success_status:200, failure_status: :unprocessable_entity, **options)
33
+ set_resource_ivar(options[:resource]) if options.has_key?(:resource)
34
+
30
35
  update! do |success, failure|
31
36
  success.json do
32
- render_resource
37
+ render_resource(success_status, **options)
33
38
  end
34
39
 
35
40
  failure.json do
36
- render_errors(:unprocessable_entity, *resource.errors.full_messages)
41
+ render_errors(failure_status, **options)
37
42
  end
38
43
  end
39
44
  end
40
45
 
41
- def destroy
46
+ def destroy(success_status:200, failure_status: :method_not_allowed, **options)
47
+ set_resource_ivar(options[:resource]) if options.has_key?(:resource)
48
+
42
49
  destroy! do |success, failure|
43
50
  success.json do
44
- render_resource
51
+ render_resource(success_status, **options)
45
52
  end
46
53
 
47
54
  failure.json do
48
- render_errors(:method_not_allowed, *resource_response_meta[:errors])
55
+ render_errors(failure_status, **options)
49
56
  end
50
57
  end
51
58
  end
52
59
 
53
- def show
54
- render_resource
60
+ def show(*args, **options)
61
+ render_resource(*args, **options)
55
62
  end
56
63
 
57
64
  private
58
65
 
59
- def render_resource(status = :ok)
60
- render json: resource, serializer: resource_serializer, meta: resource_response_meta, status: status, scope: serialization_scope
66
+ def collection_serializer
67
+ resource_serializer
61
68
  end
62
69
 
63
- def render_errors(status, *errors)
64
- errors = errors.map { |str| str.last == "." ? str : str << "." }
65
- render json: { meta: { errors: errors } }, status: status, serializer: nil
70
+ def collection_root
71
+ controller_name
66
72
  end
67
73
 
68
- def collection_serializer
69
- resource_serializer
74
+ #this is to avoid circular reference argument that we would otherwise get with render_resource
75
+ def _collection
76
+ collection
70
77
  end
71
78
 
72
- def resource_serializer
73
- "#{resource_class.name.demodulize}Serializer".constantize
79
+ def _resource
80
+ resource
74
81
  end
75
82
 
76
- def collection_root
77
- controller_name
83
+ def resource_serializer
84
+ "#{resource_class.name.demodulize}Serializer".constantize
78
85
  end
79
86
 
80
87
  def resource_root
81
88
  collection_root.singularize
82
89
  end
83
90
 
91
+ #will set the resource instance var to whatever you pass it, then render
92
+ def render_resource!(object, **options)
93
+ instance_variable_set(:"@#{self.class.resources_configuration[:self][:instance_name]}", object)
94
+ render_resource(**options)
95
+ end
96
+
97
+ def render_resource(status = :ok, resource: _resource, serializer: resource_serializer, meta: resource_response_meta, scope: serialization_scope, root: resource_root)
98
+ render json: resource, serializer: serializer, meta: meta, status: status, scope: scope, root: root
99
+ end
100
+
101
+ def render_errors(status = :unprocessable_entity, error_messages_hash:{}, **options)
102
+ errors = error_messages_hash.merge(resource_error_messages(**options))
103
+ render json: { meta: { errors: errors } }, status: status, serializer: nil
104
+ end
105
+
106
+ def resource_error_messages(resource: _resource, **options)
107
+ return nil unless resource.errors.any?
108
+
109
+ resource.errors.inject({}) do |result, error|
110
+ field = "#{error[0]}"
111
+ message = error[1]
112
+ result[field] = message.downcase.include?(field) ? message : "#{field.titleize} #{message}"
113
+ result
114
+ end
115
+ end
116
+
117
+ #this metthod is in limbo because it appears this was removed from master branch of AMS?
84
118
  def serialized_resource
85
119
  @serialized_resource ||= resource_serializer.new(resource, scope: serialization_scope)
86
120
  end
@@ -0,0 +1,47 @@
1
+ module Trax
2
+ module Controller
3
+ module Collection
4
+ module Pageable
5
+ extend ::ActiveSupport::Concern
6
+
7
+ PAGINATION_META_METHODS = [
8
+ :current_page,
9
+ :per_page,
10
+ :size,
11
+ :total_pages,
12
+ :total_entries
13
+ ].freeze
14
+
15
+ included do
16
+ collection_defaults :page => 1, :per_page => 25
17
+ end
18
+
19
+ def pagination_params
20
+ params[:page] ||= self.class.collection_options.page
21
+ params[:per_page] ||= self.class.collection_options.per_page
22
+
23
+ @pagination_params ||= { :page => params[:page], :per_page => params[:per_page] }
24
+ end
25
+
26
+ def collection
27
+ @collection ||= super.paginate(pagination_params)
28
+ end
29
+
30
+ def collection_pagination_meta
31
+ @collection_pagination_meta ||= ::Trax::Controller::Collection::Pageable::PAGINATION_META_METHODS.inject({}) do |h, key|
32
+ h[key] = collection.try(key)
33
+ h
34
+ end
35
+ end
36
+
37
+ def collection_response_meta
38
+ if collection_action?
39
+ super.merge!(:pagination => collection_pagination_meta)
40
+ else
41
+ super
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -4,22 +4,12 @@ module Trax
4
4
  module Searchable
5
5
  extend ::ActiveSupport::Concern
6
6
 
7
- def search
8
- render :json => search_collection,
9
- :meta => collection_response_meta,
10
- :each_serializer => collection_serializer,
11
- :root => collection_root
7
+ def search(**options)
8
+ index(**options)
12
9
  end
13
10
 
14
- def search_collection
15
- @search_collection ||= begin
16
- search_keys = self.class.scopes_configuration.keys
17
-
18
- search_keys.inject(collection.all) do |result, key|
19
- relation = params.has_key?("#{key}") ? result.__send__(key, params[key]) : result
20
- relation
21
- end.all
22
- end
11
+ def collection_response_meta
12
+ super.merge!(:search_scopes => current_scopes)
23
13
  end
24
14
  end
25
15
  end
@@ -6,6 +6,7 @@ module Trax
6
6
  autoload :Base
7
7
  autoload :ResponseMeta
8
8
  autoload :Searchable
9
+ autoload :Pageable
9
10
  end
10
11
  end
11
12
  end
@@ -0,0 +1,9 @@
1
+ module Trax
2
+ module Controller
3
+ class Engine < ::Rails::Engine
4
+ initializer 'trax_controller.active_model_serializers' do |app|
5
+ ::ActiveModel::Serializer.config.adapter = Trax::Controller::Serialization::Adapters::Json
6
+ end
7
+ end
8
+ end
9
+ end
@@ -9,7 +9,6 @@ module Trax
9
9
  end
10
10
 
11
11
  include ::Trax::Controller::Resource::ResponseMeta
12
- include ::Trax::Controller::Resource::Errors
13
12
  end
14
13
  end
15
14
  end
@@ -0,0 +1,63 @@
1
+ # This adapter is a workaround for
2
+ # https://github.com/rails-api/active_model_serializers/issues/835 until
3
+ # https://github.com/rails-api/active_model_serializers/pull/952 is merged
4
+
5
+ module Trax
6
+ module Controller
7
+ module Serialization
8
+ module Adapters
9
+ class Json < ActiveModel::Serializer::Adapter::Json
10
+ def serializable_hash(options={})
11
+ is_collection? ? collection_serializable_hash(options) : resource_serializable_hash(options)
12
+ end
13
+
14
+ private
15
+
16
+ def collection_result
17
+ @collection_result ||= []
18
+ end
19
+
20
+ def collection_serializable_hash(options={})
21
+ _result = collection_result + serializer.map { |s| FlattenJson.new(s).serializable_hash(options) }
22
+ { root => _result }
23
+ end
24
+
25
+ def is_collection?
26
+ serializer.respond_to?(:each)
27
+ end
28
+
29
+ def resource_result
30
+ @resource_result ||= {}
31
+ end
32
+
33
+ def resource_serializable_hash(options={})
34
+ cache_check(serializer){ resource_result.merge!(serializer.attributes(options)) }
35
+ serialize_resource_associations
36
+ { root => resource_result }
37
+ end
38
+
39
+ def serialize_resource_associations
40
+ serializer.associations.each do |association|
41
+ association_serializer = association.serializer
42
+ association_options = association.options
43
+
44
+ if association_serializer.respond_to?(:each)
45
+ array_serializer = association_serializer
46
+
47
+ resource_result[association.key] = array_serializer.map do |item|
48
+ cache_check(item) { FlattenJson.new(item).serializable_hash }
49
+ end
50
+ else
51
+ resource_result[association.key] = if association_serializer && association_serializer.object
52
+ cache_check(association_serializer) { FlattenJson.new(association_serializer).serializable_hash }
53
+ elsif association_options[:virtual_value]
54
+ association_options[:virtual_value]
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,11 @@
1
+ module Trax
2
+ module Controller
3
+ module Serialization
4
+ module Adapters
5
+ extend ::ActiveSupport::Autoload
6
+
7
+ autoload :Json
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ module Trax
2
+ module Controller
3
+ module Serialization
4
+ extend ::ActiveSupport::Autoload
5
+
6
+ autoload :Adapters
7
+ end
8
+ end
9
+ end
@@ -2,6 +2,9 @@ require 'hashie/dash'
2
2
  require 'hashie/mash'
3
3
  require 'trax_core'
4
4
  require 'inherited_resources'
5
+ require 'will_paginate'
6
+ require 'active_model_serializers'
7
+ require 'has_scope'
5
8
 
6
9
  module Trax
7
10
  module Controller
@@ -14,22 +17,19 @@ module Trax
14
17
 
15
18
  autoload :Actions
16
19
  autoload :Collection
20
+ autoload :Engine
17
21
  autoload :Resource
18
22
  autoload :InheritResources
23
+ autoload :ActionTypes
24
+ autoload :Engine
25
+ autoload :Serialization
19
26
 
20
27
  include ::Trax::Controller::InheritResources
21
28
  include ::Trax::Controller::Collection::Base
22
29
  include ::Trax::Controller::Resource::Base
23
30
  include ::Trax::Controller::Actions
24
-
25
- # inherit_resources
26
-
27
-
28
-
29
- # included do
30
- # inherit_resources
31
- # end
32
-
33
-
31
+ include ::Trax::Controller::ActionTypes
34
32
  end
35
33
  end
34
+
35
+ ::Trax::Controller::Engine if defined?(::Rails)
@@ -1,3 +1,3 @@
1
1
  module TraxController
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -0,0 +1,2 @@
1
+ class ApplicationController < ActionController::Base
2
+ end
@@ -0,0 +1,53 @@
1
+ class CategoriesController < ::ApplicationController
2
+ include ::Trax::Controller
3
+ include ::Trax::Controller::Collection::Pageable
4
+
5
+ defaults :resource_class => ::Category
6
+
7
+ has_scope :by_id
8
+
9
+ actions :index, :show, :create, :update, :destroy
10
+
11
+ def create_with_modified_response_codes
12
+ create(success_status: :accepted, failure_status: 404)
13
+ end
14
+
15
+ def create_with_modified_resource
16
+ @widget = Widget.create(widget_params)
17
+ create(resource: @widget, root: 'widget', serializer: WidgetSerializer)
18
+ end
19
+
20
+ def show_without_products
21
+ render_resource(serializer: ::CategoriesSerializer)
22
+ end
23
+
24
+ def widget
25
+ render_resource(serializer: ::CategoriesSerializer, resource: Widget.new(:name => 'whatever'))
26
+ end
27
+
28
+ def widget_with_renamed_root
29
+ render_resource(serializer: ::CategoriesSerializer, root: 'widget', resource: Widget.new(:name => 'whatever'))
30
+ end
31
+
32
+ def show_by_calling_original_action
33
+ show(serializer: ::CategoriesSerializer, root: 'show_by_calling_original_action')
34
+ end
35
+
36
+ private
37
+
38
+ def category_params
39
+ params.require(:category).permit(:name)
40
+ end
41
+
42
+ def widget_params
43
+ params.require(:widget).permit(:name, :quantity)
44
+ end
45
+
46
+ def resource_serializer
47
+ ::CategorySerializer
48
+ end
49
+
50
+ def collection_serializer
51
+ ::CategoriesSerializer
52
+ end
53
+ end
@@ -0,0 +1,14 @@
1
+ class ProductsController < ::ApplicationController
2
+ include ::Trax::Controller
3
+ include ::Trax::Controller::Collection::Pageable
4
+
5
+ defaults :resource_class => ::Product
6
+
7
+ has_scope :by_id
8
+
9
+ actions :index, :show, :create, :update, :destroy
10
+
11
+ def product_params
12
+ params.require(:product).permit(:name, :category_id)
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ class Category < ::ActiveRecord::Base
2
+ validates :name, :length => { :minimum => 5 }
3
+
4
+ has_many :products
5
+ end
@@ -0,0 +1,5 @@
1
+ class Product < ::ActiveRecord::Base
2
+ validates_presence_of(:name)
3
+
4
+ belongs_to :category
5
+ end
@@ -0,0 +1,4 @@
1
+ class Widget < ::ActiveRecord::Base
2
+ validates :name, :length => { :minimum => 5 }
3
+ validates :quantity, :numericality => { :greater_than => 0 }
4
+ end
@@ -0,0 +1,3 @@
1
+ class CategoriesSerializer < ::ActiveModel::Serializer
2
+ attributes :name
3
+ end
@@ -0,0 +1,9 @@
1
+ class CategorySerializer < ::ActiveModel::Serializer
2
+ attributes :name, :slug
3
+
4
+ def slug
5
+ name.parameterize('-')
6
+ end
7
+
8
+ has_many :products, :serializer => ::ProductSerializer
9
+ end
@@ -0,0 +1,3 @@
1
+ class ProductSerializer < ::ActiveModel::Serializer
2
+ attributes :id, :name, :quantity, :category_id
3
+ end
@@ -0,0 +1,3 @@
1
+ class WidgetSerializer < ::ActiveModel::Serializer
2
+ attributes :id, :name, :quantity
3
+ end
@@ -0,0 +1,3 @@
1
+ test:
2
+ adapter: sqlite3
3
+ database: db/combustion_test.sqlite
@@ -0,0 +1,17 @@
1
+ Rails.application.routes.draw do
2
+ resources :products, :defaults => { :format => :json }
3
+ resources :categories, :defaults => { :format => :json } do
4
+ collection do
5
+ post 'create_with_modified_response_codes'
6
+ post 'create_with_modified_resource'
7
+ end
8
+
9
+ member do
10
+ delete 'destroy_with_modified_response_codes'
11
+ get 'show_without_products'
12
+ get 'show_by_calling_original_action'
13
+ get 'widget'
14
+ get 'widget_with_renamed_root'
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,61 @@
1
+
2
+ ::ActiveRecord::Schema.define do
3
+ create_table "products", :force => true do |t|
4
+ t.string "name"
5
+ t.integer "category_id"
6
+ t.integer "user_id"
7
+ t.decimal "price"
8
+ t.integer "quantity"
9
+ t.boolean "in_stock"
10
+ t.datetime "created_at", :null => false
11
+ t.datetime "updated_at", :null => false
12
+ end
13
+
14
+ create_table "categories", :force => true do |t|
15
+ t.string "name"
16
+ t.datetime "created_at", :null => false
17
+ t.datetime "updated_at", :null => false
18
+ end
19
+
20
+ create_table "users", :force => true do |t|
21
+ t.string "first_name"
22
+ t.string "last_name"
23
+ t.string "email"
24
+ t.datetime "created_at", :null => false
25
+ t.datetime "updated_at", :null => false
26
+ end
27
+
28
+ create_table "widgets", :force => true do |t|
29
+ t.string "uuid"
30
+ t.string "name"
31
+ t.string "subdomain"
32
+ t.string "website"
33
+ t.integer "status"
34
+ t.integer "quantity"
35
+ t.datetime "created_at", :null => false
36
+ t.datetime "updated_at", :null => false
37
+ end
38
+
39
+ create_table "people", :force => true do |t|
40
+ t.string "name"
41
+ t.string "uuid"
42
+ t.datetime "created_at", :null => false
43
+ t.datetime "updated_at", :null => false
44
+ end
45
+
46
+ create_table "staplers", :force => true do |t|
47
+ t.string "name"
48
+ t.string "type"
49
+ t.integer "attribute_set_id"
50
+ t.datetime "created_at", :null => false
51
+ t.datetime "updated_at", :null => false
52
+ end
53
+
54
+ create_table "swingline_stapler_attribute_sets", :force => true do |t|
55
+ t.float "speed"
56
+ t.string "owner"
57
+
58
+ t.datetime "created_at", :null => false
59
+ t.datetime "updated_at", :null => false
60
+ end
61
+ end
@@ -0,0 +1 @@
1
+ *.log
File without changes
data/spec/spec_helper.rb CHANGED
@@ -1,18 +1,25 @@
1
1
  require 'rubygems'
2
2
  require 'bundler'
3
+ require 'combustion'
4
+ require 'pry'
5
+ require 'active_record'
6
+
7
+ ::Combustion.schema_format = :ruby
8
+ ::Combustion.initialize! :all
9
+
10
+ require 'rspec/rails'
3
11
  require 'simplecov'
4
12
  require 'pry'
5
13
  require 'trax_controller'
6
14
 
7
- SimpleCov.start do
15
+ ::SimpleCov.start do
8
16
  add_filter '/spec/'
9
17
  end
10
18
 
11
- RSpec.configure do |config|
12
- config.before(:suite) do
13
- end
19
+ ::RSpec.configure do |config|
20
+ config.include RSpec::Rails::RequestExampleGroup, type: :feature
14
21
  end
15
22
 
16
- Bundler.require(:default, :development, :test)
23
+ ::Bundler.require(:default, :development, :test)
17
24
 
18
25
  ::Dir["#{::File.dirname(__FILE__)}/support/*.rb"].each {|f| require f }
@@ -1,7 +1,4 @@
1
1
  require 'spec_helper'
2
-
3
2
  describe ::Trax::Controller do
4
- it {
5
- should respond_to(:defaults)
6
- }
3
+
7
4
  end
@@ -0,0 +1,206 @@
1
+ require 'spec_helper'
2
+
3
+ ::RSpec.describe CategoriesController, :type => :controller do
4
+ before(:all) do
5
+ @shoes_category = ::Category.create(:name => 'mens shoes')
6
+ @watches_category = ::Category.create(:name => 'mens watches')
7
+
8
+ @product1 = ::Product.create(:name => "DC Villan", :quantity => 0, :in_stock => false, :category => @shoes_category)
9
+ @product2 = ::Product.create(:name => "Nixon Rotolog", :quantity => 2, :in_stock => true, :category => @watches_category)
10
+ end
11
+
12
+ context '#index' do
13
+ it {
14
+ get :index
15
+ expect(response).to be_ok
16
+ json = ::JSON.parse(response.body)
17
+ expect(json).to have_key('categories')
18
+ }
19
+
20
+ context "serialization of associations" do
21
+ it {
22
+ get :index
23
+ json = ::JSON.parse(response.body)
24
+ expect(json["categories"][0]).to_not have_key('products')
25
+ }
26
+ end
27
+ end
28
+
29
+ context "#show" do
30
+ context "serialization of associations" do
31
+ it {
32
+ get :show, :id => @shoes_category.id
33
+ json = ::JSON.parse(response.body)
34
+ expect(json["category"]).to have_key('products')
35
+ expect(json["category"]["products"][0]["name"]).to eq("DC Villan")
36
+ }
37
+ end
38
+ end
39
+
40
+ context "overriding default resource actions and render resource" do
41
+ describe "#show_without_products" do
42
+ it {
43
+ get :show_without_products, :id => @shoes_category.id
44
+ json = ::JSON.parse(response.body)
45
+ expect(json["category"]).to_not have_key('products')
46
+ }
47
+ end
48
+
49
+ describe "#widget" do
50
+ it "render resource can have resource passed to it" do
51
+ get :widget, :id => @shoes_category.id
52
+ json = ::JSON.parse(response.body)
53
+ expect(json["category"]["name"]).to eq "whatever"
54
+ end
55
+ end
56
+
57
+ describe "#widget_with_renamed_root" do
58
+ it "allows root key to be overridden" do
59
+ get :widget_with_renamed_root, :id => @shoes_category.id
60
+ json = ::JSON.parse(response.body)
61
+ expect(json["widget"]["name"]).to eq "whatever"
62
+ end
63
+ end
64
+
65
+ describe "#show_by_calling_original_action" do
66
+ it "allows show method itself to be called passing in overridden options" do
67
+ get :show_by_calling_original_action, :id => @shoes_category.id
68
+ json = ::JSON.parse(response.body)
69
+ expect(json["show_by_calling_original_action"]["name"]).to eq @shoes_category.name
70
+ end
71
+ end
72
+ end
73
+
74
+ context '#create_with_modified_resource' do
75
+ let(:widget_name) { 'My New Widget' }
76
+ let(:widget_quantity) { 10 }
77
+ let(:params){ {
78
+ :format => :json,
79
+ :widget => widget_params
80
+ }}
81
+ let(:widget_params) { { :name => widget_name, :quantity => widget_quantity } }
82
+ before { post :create_with_modified_resource, params }
83
+
84
+ context 'success' do
85
+ it { expect(response).to be_successful }
86
+ it {
87
+ json = JSON.parse response.body
88
+ expect(json).to have_key("widget")
89
+ }
90
+ it { expect(response.status).to eq 201 }
91
+ end
92
+
93
+ context 'failure' do
94
+ let(:widget_name) { 'My New Widget' }
95
+ let(:widget_quantity) { 0 }
96
+
97
+ context 'default failure action' do
98
+ before { post :create_with_modified_resource, params }
99
+
100
+ it { expect(response).to_not be_successful }
101
+ it {
102
+ json = JSON.parse response.body
103
+ expect(json["meta"]["errors"]).to have_key("quantity")
104
+ }
105
+ it { expect(response.status).to eq 422 }
106
+ end
107
+ end
108
+ end
109
+
110
+ context '#create' do
111
+ let(:category_name) { 'My New Category'}
112
+ let(:params){ {
113
+ :format => :json,
114
+ :category => category_params
115
+ }}
116
+ let(:category_params) { { :name => category_name } }
117
+
118
+ context 'success' do
119
+ context 'with default create action' do
120
+ before { post :create, params }
121
+
122
+ it { expect(response).to be_successful }
123
+ it {
124
+ json = JSON.parse response.body
125
+ expect(json).to have_key("category")
126
+ }
127
+ it { expect(response.status).to eq 201 }
128
+ end
129
+
130
+ context 'allows overrides' do
131
+ context 'response code override' do
132
+ before { post :create_with_modified_response_codes, params }
133
+
134
+ it { expect(response.status).to eq 202 }
135
+ end
136
+ end
137
+ end
138
+
139
+ context 'failure' do
140
+ let(:category_name) { 'a' }
141
+
142
+ context 'default create action' do
143
+ before { post :create, params }
144
+
145
+ it { expect(response).to_not be_successful }
146
+ it {
147
+ json = JSON.parse response.body
148
+ expect(json["meta"]["errors"]).to have_key("name")
149
+ }
150
+ it { expect(response.status).to eq 422 }
151
+ end
152
+
153
+ context 'allows overrides' do
154
+ context 'response code override' do
155
+ before { post :create_with_modified_response_codes, params }
156
+
157
+ it { expect(response.status).to eq 404 }
158
+ end
159
+ end
160
+ end
161
+ end
162
+
163
+ context '#update' do
164
+ let(:category) { ::Category.create(:name => category_name) }
165
+ let(:category_name) { 'My New Category'}
166
+ let(:new_category_name) { 'mynewcategoryname' }
167
+ let(:params){ {
168
+ :format => :json,
169
+ :id => category.id,
170
+ :category => category_params
171
+ }}
172
+ let(:category_params) { { :name => new_category_name } }
173
+
174
+ context 'success' do
175
+ context 'with default update action' do
176
+ before { put(:update, params) }
177
+
178
+ it { expect(response).to be_successful }
179
+ it {
180
+ json = JSON.parse response.body
181
+ expect(json).to have_key("category")
182
+ }
183
+ it {
184
+ json = JSON.parse response.body
185
+ expect(json["category"]["name"]).to eq(new_category_name)
186
+ }
187
+ it { expect(response.status).to eq 200 }
188
+ end
189
+ end
190
+
191
+ context 'failure' do
192
+ let(:new_category_name) { 'a' }
193
+
194
+ context 'default update action' do
195
+ before { put :update, params }
196
+
197
+ it { expect(response).to_not be_successful }
198
+ it {
199
+ json = JSON.parse response.body
200
+ expect(json["meta"]["errors"]).to have_key("name")
201
+ }
202
+ it { expect(response.status).to eq 422 }
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ ::RSpec.describe ProductsController, :type => :controller do
4
+ before(:all) do
5
+ @product1 = ::Product.create(:name => "apple imac", :quantity => 0, :in_stock => false)
6
+ @product2 = ::Product.create(:name => "apple macbook", :quantity => 2, :in_stock => true)
7
+ @product3 = ::Product.create(:name => "apple ipod", :quantity => 3, :in_stock => true)
8
+ end
9
+
10
+ context '#index' do
11
+ it {
12
+ get :index
13
+ expect(response).to be_ok
14
+ json = JSON.parse response.body
15
+ expect(json).to have_key('products')
16
+ }
17
+
18
+ context 'pagination' do
19
+ ["current_page", "per_page", "size", "total_pages", "total_entries"].each do |k|
20
+ it {
21
+ get :index
22
+ json = JSON.parse response.body
23
+ expect(json["meta"]["pagination"]).to have_key(k)
24
+ }
25
+ end
26
+
27
+
28
+ end
29
+ end
30
+
31
+ describe '#show' do
32
+ it 'shows' do
33
+ get :show, id: @product1.id
34
+
35
+ expect(response).to be_ok
36
+ json = JSON.parse response.body
37
+ expect(json).to have_key('product')
38
+ end
39
+ end
40
+ end
@@ -19,8 +19,11 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_dependency "trax_core"
22
+ spec.add_dependency "will_paginate"
23
+ spec.add_dependency "has_scope"
22
24
  spec.add_development_dependency "rails"
23
25
  spec.add_development_dependency "inherited_resources"
26
+ spec.add_development_dependency 'combustion', '~> 0.5.3'
24
27
  spec.add_development_dependency "bundler", "~> 1.6"
25
28
  spec.add_development_dependency "rake"
26
29
  spec.add_development_dependency "sqlite3"
@@ -35,4 +38,5 @@ Gem::Specification.new do |spec|
35
38
  spec.add_development_dependency 'guard-bundler', '~> 2'
36
39
  spec.add_development_dependency 'rb-fsevent'
37
40
  spec.add_development_dependency 'terminal-notifier-guard'
41
+
38
42
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trax_controller
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Ayre
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-08 00:00:00.000000000 Z
11
+ date: 2015-10-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: trax_core
@@ -24,6 +24,34 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: will_paginate
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: has_scope
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: rails
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +80,20 @@ dependencies:
52
80
  - - ">="
53
81
  - !ruby/object:Gem::Version
54
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: combustion
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.5.3
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.5.3
55
97
  - !ruby/object:Gem::Dependency
56
98
  name: bundler
57
99
  requirement: !ruby/object:Gem::Requirement
@@ -256,26 +298,54 @@ extensions: []
256
298
  extra_rdoc_files: []
257
299
  files:
258
300
  - ".gitignore"
301
+ - ".rspec"
302
+ - ".travis.yml"
259
303
  - Gemfile
304
+ - Guardfile
260
305
  - LICENSE.txt
261
306
  - README.md
262
307
  - Rakefile
308
+ - config.ru
309
+ - lib/Guardfile
263
310
  - lib/trax.rb
264
311
  - lib/trax/controller.rb
312
+ - lib/trax/controller/action_types.rb
265
313
  - lib/trax/controller/actions.rb
266
314
  - lib/trax/controller/collection.rb
267
315
  - lib/trax/controller/collection/base.rb
316
+ - lib/trax/controller/collection/pageable.rb
268
317
  - lib/trax/controller/collection/response_meta.rb
269
318
  - lib/trax/controller/collection/searchable.rb
319
+ - lib/trax/controller/engine.rb
270
320
  - lib/trax/controller/inherit_resources.rb
271
321
  - lib/trax/controller/resource.rb
272
322
  - lib/trax/controller/resource/base.rb
273
- - lib/trax/controller/resource/errors.rb
274
323
  - lib/trax/controller/resource/response_meta.rb
324
+ - lib/trax/controller/serialization.rb
325
+ - lib/trax/controller/serialization/adapters.rb
326
+ - lib/trax/controller/serialization/adapters/json.rb
275
327
  - lib/trax_controller.rb
276
328
  - lib/trax_controller/version.rb
329
+ - spec/internal/app/controllers/application_controller.rb
330
+ - spec/internal/app/controllers/categories_controller.rb
331
+ - spec/internal/app/controllers/products_controller.rb
332
+ - spec/internal/app/models/category.rb
333
+ - spec/internal/app/models/product.rb
334
+ - spec/internal/app/models/widget.rb
335
+ - spec/internal/app/serializers/categories_serializer.rb
336
+ - spec/internal/app/serializers/category_serializer.rb
337
+ - spec/internal/app/serializers/product_serializer.rb
338
+ - spec/internal/app/serializers/widget_serializer.rb
339
+ - spec/internal/config/database.yml
340
+ - spec/internal/config/routes.rb
341
+ - spec/internal/db/combustion_test.sqlite
342
+ - spec/internal/db/schema.rb
343
+ - spec/internal/log/.gitignore
344
+ - spec/internal/public/favicon.ico
277
345
  - spec/spec_helper.rb
278
346
  - spec/trax/controller_spec.rb
347
+ - spec/trax/controllers/categories_controller_spec.rb
348
+ - spec/trax/controllers/products_controller_spec.rb
279
349
  - trax_controller.gemspec
280
350
  homepage: http://www.github.com/jasonayre
281
351
  licenses:
@@ -297,10 +367,28 @@ required_rubygems_version: !ruby/object:Gem::Requirement
297
367
  version: '0'
298
368
  requirements: []
299
369
  rubyforge_project:
300
- rubygems_version: 2.4.3
370
+ rubygems_version: 2.4.5
301
371
  signing_key:
302
372
  specification_version: 4
303
373
  summary: Resourceful, standardized controllers
304
374
  test_files:
375
+ - spec/internal/app/controllers/application_controller.rb
376
+ - spec/internal/app/controllers/categories_controller.rb
377
+ - spec/internal/app/controllers/products_controller.rb
378
+ - spec/internal/app/models/category.rb
379
+ - spec/internal/app/models/product.rb
380
+ - spec/internal/app/models/widget.rb
381
+ - spec/internal/app/serializers/categories_serializer.rb
382
+ - spec/internal/app/serializers/category_serializer.rb
383
+ - spec/internal/app/serializers/product_serializer.rb
384
+ - spec/internal/app/serializers/widget_serializer.rb
385
+ - spec/internal/config/database.yml
386
+ - spec/internal/config/routes.rb
387
+ - spec/internal/db/combustion_test.sqlite
388
+ - spec/internal/db/schema.rb
389
+ - spec/internal/log/.gitignore
390
+ - spec/internal/public/favicon.ico
305
391
  - spec/spec_helper.rb
306
392
  - spec/trax/controller_spec.rb
393
+ - spec/trax/controllers/categories_controller_spec.rb
394
+ - spec/trax/controllers/products_controller_spec.rb
@@ -1,21 +0,0 @@
1
- module Trax
2
- module Controller
3
- module Resource
4
- module Errors
5
- extend ::ActiveSupport::Concern
6
-
7
- def resource_errors?
8
- resource.errors.any?
9
- end
10
-
11
- def resource_response_meta
12
- if resource_action? && resource_errors?
13
- super.merge!(:errors => resource.errors.messages)
14
- else
15
- super
16
- end
17
- end
18
- end
19
- end
20
- end
21
- end