trax_controller 0.0.2 → 0.0.3

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 (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