snfoil-rails 0.1.0 → 0.3.0

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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/CODE_OF_CONDUCT.md +74 -0
  3. data/Rakefile +1 -1
  4. data/lib/generators/sn_foil/sn_foil_generator.rb +47 -0
  5. data/lib/sn_foil/controller/api.rb +77 -0
  6. data/lib/sn_foil/controller/base.rb +19 -0
  7. data/lib/sn_foil/controller/concerns/change_controller_concern.rb +21 -0
  8. data/lib/sn_foil/controller/concerns/create_controller_concern.rb +38 -0
  9. data/lib/sn_foil/controller/concerns/destroy_controller_concern.rb +40 -0
  10. data/lib/sn_foil/controller/concerns/index_controller_concern.rb +66 -0
  11. data/lib/sn_foil/controller/concerns/setup_controller_concern.rb +84 -0
  12. data/lib/sn_foil/controller/concerns/show_controller_concern.rb +36 -0
  13. data/lib/sn_foil/controller/concerns/update_controller_concern.rb +38 -0
  14. data/lib/sn_foil/jsonapi_deserializer.rb +151 -0
  15. data/lib/sn_foil/jsonapi_serializer.rb +16 -0
  16. data/lib/sn_foil/rails.rb +18 -7
  17. data/lib/sn_foil/rails/engine.rb +24 -0
  18. data/lib/sn_foil/rails/version.rb +1 -1
  19. data/lib/sn_foil/searcher.rb +123 -0
  20. metadata +47 -13
  21. data/lib/sn_foil/rails/controller/api.rb +0 -22
  22. data/lib/sn_foil/rails/controller/concerns/change_controller_concern.rb +0 -28
  23. data/lib/sn_foil/rails/controller/concerns/create_controller_concern.rb +0 -40
  24. data/lib/sn_foil/rails/controller/concerns/destroy_controller_concern.rb +0 -42
  25. data/lib/sn_foil/rails/controller/concerns/index_controller_concern.rb +0 -72
  26. data/lib/sn_foil/rails/controller/concerns/setup_controller_concern.rb +0 -110
  27. data/lib/sn_foil/rails/controller/concerns/show_controller_concern.rb +0 -38
  28. data/lib/sn_foil/rails/controller/concerns/update_controller_concern.rb +0 -40
  29. data/lib/sn_foil/rails/railtie.rb +0 -10
  30. data/lib/sn_foil/rails/searcher.rb +0 -127
  31. data/lib/tasks/sn_foil/rails_tasks.rake +0 -5
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+ require_relative 'setup_controller_concern'
5
+
6
+ module SnFoil
7
+ module Controller
8
+ module Concerns
9
+ module ShowControllerConcern
10
+ extend ActiveSupport::Concern
11
+
12
+ included do
13
+ include SetupControllerConcern
14
+ end
15
+
16
+ def show(**options)
17
+ options = setup_show(**options)
18
+ model = process_show(**options)
19
+ render_show(model, **options)
20
+ end
21
+
22
+ def setup_show(**options)
23
+ setup_options(**options)
24
+ end
25
+
26
+ def process_show(**options)
27
+ current_context(**options).show(**options)
28
+ end
29
+
30
+ def render_show(model, **_options)
31
+ render model
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+ require_relative 'setup_controller_concern'
5
+ require_relative 'change_controller_concern'
6
+
7
+ module SnFoil
8
+ module Controller
9
+ module Concerns
10
+ module UpdateControllerConcern
11
+ extend ActiveSupport::Concern
12
+
13
+ included do
14
+ include SetupControllerConcern
15
+ include ChangeControllerConcern
16
+ end
17
+
18
+ def update(**options)
19
+ options = setup_update(**options)
20
+ model = process_update(**options)
21
+ render_update(model, **options)
22
+ end
23
+
24
+ def setup_update(**options)
25
+ setup_options(**options)
26
+ end
27
+
28
+ def process_update(**options)
29
+ current_context(**options).update(**options)
30
+ end
31
+
32
+ def render_update(model, **options)
33
+ render_change(model, **options)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+
5
+ module SnFoil
6
+ module JsonapiDeserializer # rubocop:disable Metrics/ModuleLength
7
+ extend ActiveSupport::Concern
8
+
9
+ class_methods do
10
+ attr_reader :i_attribute_fields, :i_attribute_transforms
11
+
12
+ def attributes(*fields)
13
+ @i_attribute_fields ||= []
14
+ @i_attribute_fields |= fields
15
+ end
16
+
17
+ def attribute(key, **options)
18
+ @i_attribute_transforms ||= {}
19
+ @i_attribute_transforms[key] = options.merge(transform_type: :attribute)
20
+ end
21
+
22
+ def has_one(key, deserializer:, **options) # rubocop:disable Naming/PredicateName
23
+ @i_attribute_transforms ||= {}
24
+ @i_attribute_transforms[key] = options.merge(deserializer: deserializer, transform_type: :has_one)
25
+ end
26
+ alias_method :belongs_to, :has_one
27
+
28
+ def has_many(key, deserializer:, **options) # rubocop:disable Naming/PredicateName
29
+ @i_attribute_transforms ||= {}
30
+ @i_attribute_transforms[key] = options.merge(deserializer: deserializer, transform_type: :has_many)
31
+ end
32
+ end
33
+
34
+ attr_reader :object, :included, :options
35
+ def initialize(object, included: nil, **options)
36
+ @object = object
37
+ @included = included || object[:included]
38
+ @options = options
39
+ end
40
+
41
+ def attribute_fields
42
+ self.class.i_attribute_fields || []
43
+ end
44
+
45
+ def attribute_transforms
46
+ self.class.i_attribute_transforms || {}
47
+ end
48
+
49
+ def attributes
50
+ @attributes ||= attribute_fields | attribute_transforms.map { |k, v| v[:key] || k }
51
+ end
52
+
53
+ def parse
54
+ parse_data
55
+ end
56
+ alias to_h parse
57
+
58
+ private
59
+
60
+ def parse_data
61
+ if object[:data].is_a? Array
62
+ object[:data].map { |d| build_attributes(d) }
63
+ else
64
+ build_attributes(object[:data])
65
+ end
66
+ end
67
+
68
+ def build_attributes(data)
69
+ attributes = data_id({}, data)
70
+ attributes = parse_standard_attributes(attributes, data) if data[:attributes]
71
+ attribute_transforms.each do |key, opts|
72
+ attributes = apply_attribute_transform(attributes, data, key, **opts)
73
+ end
74
+ attributes
75
+ end
76
+
77
+ def data_id(attributes, data)
78
+ if data[:id]
79
+ attributes[:id] = data[:id]
80
+ elsif object[:data][:lid]
81
+ attributes[:lid] = data[:lid]
82
+ end
83
+ attributes
84
+ end
85
+
86
+ def parse_standard_attributes(attributes, data)
87
+ attributes.merge!(data[:attributes].select { |k, _| attribute_fields.include? k })
88
+ end
89
+
90
+ def apply_attribute_transform(attributes, data, key, transform_type:, **opts)
91
+ if transform_type == :attribute
92
+ parse_attribute_transform(attributes, data, key, **opts)
93
+ elsif transform_type == :has_one
94
+ parse_has_one_relationship(attributes, data, key, **opts)
95
+ elsif transform_type == :has_many
96
+ parse_has_many_relationship(attributes, data, key, **opts)
97
+ end
98
+ end
99
+
100
+ def parse_attribute_transform(attributes, data, key, **opts)
101
+ return attributes unless data.dig(:attributes, key)
102
+
103
+ attributes.merge(Hash[opts.fetch(:key) { key }, data[:attributes][key]])
104
+ end
105
+
106
+ def parse_relationships(attributes, data)
107
+ self.class.has_one_relationships.each do |key, opts|
108
+ attributes = has_one_relationship(attributes, data, key, **opts)
109
+ end
110
+ self.class.has_many_relationships.each do |key, opts|
111
+ attributes = has_many_relationship(attributes, data, key, **opts)
112
+ end
113
+ attributes
114
+ end
115
+
116
+ def parse_has_one_relationship(attributes, data, key, deserializer:, **opts)
117
+ resource_data = data.dig(:relationships, key, :data)
118
+ return attributes unless resource_data
119
+
120
+ attribute_data = lookup_relationship(resource_data)
121
+ relationship_data = { data: attribute_data || resource_data }
122
+ attributes[opts.fetch(:key) { key }] = deserializer.new(relationship_data, **options, included: included).parse
123
+ attributes
124
+ end
125
+
126
+ def parse_has_many_relationship(attributes, data, key, deserializer:, **opts)
127
+ array_data = data.dig(:relationships, key, :data)
128
+ return attributes unless array_data
129
+
130
+ attributes[opts.fetch(:key) { key }] = array_data.map do |resource_data|
131
+ attribute_data = lookup_relationship(resource_data)
132
+ relationship_data = { data: attribute_data || resource_data }
133
+ deserializer.new(relationship_data, **options, included: included).parse
134
+ end
135
+ attributes
136
+ end
137
+
138
+ def lookup_relationship(type:, id: nil, lid: nil, **_opts)
139
+ raise ::ArguementError, 'missing keyword: id or lid' unless id || lid
140
+
141
+ included&.find do |x|
142
+ x[:type].eql?(type) &&
143
+ if id
144
+ x[:id].eql?(id)
145
+ elsif lid
146
+ x[:lid].eql?(lid)
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+ require 'fast_jsonapi/object_serializer'
5
+
6
+ module SnFoil
7
+ module JsonapiSerializer
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ include FastJsonapi::ObjectSerializer
12
+
13
+ set_key_transform :dash
14
+ end
15
+ end
16
+ end
data/lib/sn_foil/rails.rb CHANGED
@@ -1,11 +1,22 @@
1
- # frozen_string_literal: true
2
-
3
- require 'sn_foil/rails/railtie'
4
- require 'sn_foil/rails/searcher'
5
- require 'sn_foil/rails/controller/api'
6
-
7
1
  module SnFoil
8
2
  module Rails
9
- # Your code goes here...
3
+ if defined?(Rails)
4
+ require 'sn_foil/rails/engine'
5
+ else
6
+ require 'sn_foil'
7
+ require_relative '/version'
8
+ require_relative '../searcher'
9
+ require_relative '../jsonapi_serializer'
10
+ require_relative '../jsonapi_deserializer'
11
+ require_relative '../controller/concerns/change_controller_concern'
12
+ require_relative '../controller/concerns/create_controller_concern'
13
+ require_relative '../controller/concerns/destroy_controller_concern'
14
+ require_relative '../controller/concerns/index_controller_concern'
15
+ require_relative '../controller/concerns/setup_controller_concern'
16
+ require_relative '../controller/concerns/show_controller_concern'
17
+ require_relative '../controller/concerns/update_controller_concern'
18
+ require_relative '../controller/api'
19
+ require_relative '../controller/base'
20
+ end
10
21
  end
11
22
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/engine'
4
+
5
+ module SnFoil
6
+ module Rails
7
+ class Engine < ::Rails::Engine
8
+ require 'sn_foil'
9
+ require_relative 'version'
10
+ require_relative '../searcher'
11
+ require_relative '../jsonapi_serializer'
12
+ require_relative '../jsonapi_deserializer'
13
+ require_relative '../controller/concerns/change_controller_concern'
14
+ require_relative '../controller/concerns/create_controller_concern'
15
+ require_relative '../controller/concerns/destroy_controller_concern'
16
+ require_relative '../controller/concerns/index_controller_concern'
17
+ require_relative '../controller/concerns/setup_controller_concern'
18
+ require_relative '../controller/concerns/show_controller_concern'
19
+ require_relative '../controller/concerns/update_controller_concern'
20
+ require_relative '../controller/api'
21
+ require_relative '../controller/base'
22
+ end
23
+ end
24
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module SnFoil
4
4
  module Rails
5
- VERSION = '0.1.0'
5
+ VERSION = '0.3.0'
6
6
  end
7
7
  end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+ require 'sn_foil/searcher'
5
+
6
+ module SnFoil
7
+ module Searcher
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ module_eval do
12
+ alias_method :base_search, :search
13
+
14
+ # patch the additional search capabilities into the method
15
+ def search(params = {})
16
+ filtered_scope = base_search(params)
17
+ additional_search(filtered_scope, params)
18
+ end
19
+ end
20
+
21
+ ASC ||= 'ASC'
22
+ DESC ||= 'DESC'
23
+ end
24
+
25
+ class_methods do
26
+ attr_reader :i_include_params, :i_order_method, :i_order_block, :i_order_by_attr, :i_order_by_direction, :i_is_distinct
27
+
28
+ def order(method = nil, &block)
29
+ @i_order_method = method
30
+ @i_order_block = block
31
+ end
32
+
33
+ def order_by(attr, direction = nil)
34
+ @i_order_by_attr = attr
35
+ @i_order_by_direction = direction
36
+ end
37
+
38
+ def distinct(bool = true)
39
+ @i_is_distinct = bool
40
+ end
41
+
42
+ def includes(*array)
43
+ @i_include_params ||= [] # create new array if none exists
44
+ @i_include_params |= array # combine unique elements of both arrays
45
+ end
46
+ end
47
+
48
+ def distinct?
49
+ self.class.i_is_distinct || false
50
+ end
51
+
52
+ def included_params
53
+ self.class.i_include_params
54
+ end
55
+
56
+ def order_by(params = {})
57
+ if params[:order_by].present?
58
+ params[:order_by] = params[:order_by].to_s.underscore
59
+ return params[:order_by].to_sym if model.attribute_names.include?(params[:order_by])
60
+ end
61
+
62
+ self.class.i_order_by_attr || :id
63
+ end
64
+
65
+ def order(params = {})
66
+ if params[:order].present?
67
+ params[:order] = params[:order].to_s.upcase
68
+ return params[:order] if params[:order].eql?(ASC) || params[:order].eql?(DESC)
69
+ end
70
+
71
+ self.class.i_order_by_direction || ASC
72
+ end
73
+
74
+ private
75
+
76
+ def additional_search(filtered_scope, params = {})
77
+ filtered_scope = apply_order(filtered_scope, params)
78
+ filtered_scope = apply_includes(filtered_scope)
79
+ apply_distinct(filtered_scope, params)
80
+ end
81
+
82
+ def apply_includes(filtered_scope)
83
+ return filtered_scope unless included_params
84
+
85
+ filtered_scope.includes(*included_params)
86
+ end
87
+
88
+ def apply_order(filtered_scope, params)
89
+ return apply_default_order(filtered_scope, params) if params[:order_by].blank? && params[:order].blank?
90
+
91
+ filtered_scope.order(order_by(params) => order(params))
92
+ end
93
+
94
+ def apply_default_order(filtered_scope, params)
95
+ return order_method(filtered_scope, params) if order_method?
96
+ return order_block(filtered_scope, params) if order_block?
97
+
98
+ filtered_scope.order(order_by => order)
99
+ end
100
+
101
+ def order_method(filtered_scope, params)
102
+ send(self.class.i_order_method, filtered_scope, params)
103
+ end
104
+
105
+ def order_method?
106
+ self.class.i_order_method.present?
107
+ end
108
+
109
+ def order_block(filtered_scope, params)
110
+ self.class.i_order_block.call(filtered_scope, params)
111
+ end
112
+
113
+ def order_block?
114
+ self.class.i_order_block.present?
115
+ end
116
+
117
+ def apply_distinct(filtered_scope, params)
118
+ return filtered_scope unless distinct? || params[:distinct] == true
119
+
120
+ filtered_scope.distinct
121
+ end
122
+ end
123
+ end
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: snfoil-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew Howes
8
+ - Danny Murphy
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2020-02-07 00:00:00.000000000 Z
12
+ date: 2020-03-02 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: activesupport
@@ -24,6 +25,20 @@ dependencies:
24
25
  - - ">="
25
26
  - !ruby/object:Gem::Version
26
27
  version: '5.1'
28
+ - !ruby/object:Gem::Dependency
29
+ name: fast_jsonapi
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '1.0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '1.0'
27
42
  - !ruby/object:Gem::Dependency
28
43
  name: rails
29
44
  requirement: !ruby/object:Gem::Requirement
@@ -38,6 +53,20 @@ dependencies:
38
53
  - - "~>"
39
54
  - !ruby/object:Gem::Version
40
55
  version: 5.1.7
56
+ - !ruby/object:Gem::Dependency
57
+ name: snfoil
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '0.3'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '0.3'
41
70
  - !ruby/object:Gem::Dependency
42
71
  name: bundler
43
72
  requirement: !ruby/object:Gem::Requirement
@@ -153,26 +182,31 @@ dependencies:
153
182
  description:
154
183
  email:
155
184
  - howeszy@gmail.com
185
+ - dmurph24@gmail.com
156
186
  executables: []
157
187
  extensions: []
158
188
  extra_rdoc_files: []
159
189
  files:
190
+ - CODE_OF_CONDUCT.md
160
191
  - MIT-LICENSE
161
192
  - README.md
162
193
  - Rakefile
194
+ - lib/generators/sn_foil/sn_foil_generator.rb
195
+ - lib/sn_foil/controller/api.rb
196
+ - lib/sn_foil/controller/base.rb
197
+ - lib/sn_foil/controller/concerns/change_controller_concern.rb
198
+ - lib/sn_foil/controller/concerns/create_controller_concern.rb
199
+ - lib/sn_foil/controller/concerns/destroy_controller_concern.rb
200
+ - lib/sn_foil/controller/concerns/index_controller_concern.rb
201
+ - lib/sn_foil/controller/concerns/setup_controller_concern.rb
202
+ - lib/sn_foil/controller/concerns/show_controller_concern.rb
203
+ - lib/sn_foil/controller/concerns/update_controller_concern.rb
204
+ - lib/sn_foil/jsonapi_deserializer.rb
205
+ - lib/sn_foil/jsonapi_serializer.rb
163
206
  - lib/sn_foil/rails.rb
164
- - lib/sn_foil/rails/controller/api.rb
165
- - lib/sn_foil/rails/controller/concerns/change_controller_concern.rb
166
- - lib/sn_foil/rails/controller/concerns/create_controller_concern.rb
167
- - lib/sn_foil/rails/controller/concerns/destroy_controller_concern.rb
168
- - lib/sn_foil/rails/controller/concerns/index_controller_concern.rb
169
- - lib/sn_foil/rails/controller/concerns/setup_controller_concern.rb
170
- - lib/sn_foil/rails/controller/concerns/show_controller_concern.rb
171
- - lib/sn_foil/rails/controller/concerns/update_controller_concern.rb
172
- - lib/sn_foil/rails/railtie.rb
173
- - lib/sn_foil/rails/searcher.rb
207
+ - lib/sn_foil/rails/engine.rb
174
208
  - lib/sn_foil/rails/version.rb
175
- - lib/tasks/sn_foil/rails_tasks.rake
209
+ - lib/sn_foil/searcher.rb
176
210
  homepage: https://github.com/howeszy/snfoil-rails
177
211
  licenses:
178
212
  - MIT