fields-serializer 0.4.0 → 0.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 026a3b888bceb7ead4ec323dfb6a8f5d32b0530c
4
- data.tar.gz: 1465d275c0d1999835a1b16306c488343654e20d
3
+ metadata.gz: d97826f68957ac11395bfc95787ebc4562a49891
4
+ data.tar.gz: dba35d1851970293eb3fcf3ac8992cb8108bc801
5
5
  SHA512:
6
- metadata.gz: 72bc247fb4b38e878af011e3fa2a96f4e8bac7b57696dc2b48f9375ff462f04e6b49a9f2966f18a78c939a35720ded20e20b18439ef6d3adddedd2e88e8ba4f9
7
- data.tar.gz: 54127c8b3f4e09f5d7c04e01780862ce08de6368ff0dfde49be85f600437022968e0d2ee9167b674acd7e7ceecd51a3ffe734234aa29baf012ba9f32ecce8e5f
6
+ metadata.gz: 429db7023534cb8168d610708cca7705a3ec3127da0d390d2472b989f0645071f63907c3c21fc12990bacd10bea1c0e29c7535f8e774e2f680c4dbb150594818
7
+ data.tar.gz: 2a2b8473e52a57245db3333d18603bd759c094917a484391476c47e49de1ca70424e1ec3d6e739d55a34c659dccbfbca181d8e9646fc53d84597378ffd694569
@@ -17,10 +17,9 @@ module Fields
17
17
  fields = options.delete(:fields)
18
18
  model_class = options.delete(:model_class)
19
19
  if fields.present?
20
- includes = model_class.fields_to_includes(fields)
21
- query = query.includes(*includes)
22
- # options.merge!(include: model_class.fields_to_include(includes).join(","))
23
- options.merge!(include: includes)
20
+ query = query.includes(*model_class.fields_to_includes(fields))
21
+ options.merge!(each_serializer: model_class.fields_serializer(fields))
22
+ options.delete(:include)
24
23
  end
25
24
  render options.merge!(json: query.to_a)
26
25
  end
@@ -1,4 +1,5 @@
1
1
  require 'active_record'
2
+ require "active_model_serializers"
2
3
 
3
4
  module Fields
4
5
  module Serializer
@@ -13,57 +14,52 @@ module Fields
13
14
  #
14
15
  # BoilerPack.fields_to_includes("id,boiler.gas_safe_code") #=> ["boiler"]
15
16
  #
16
- def fields_to_includes(fields)
17
- nested_fields(fields).inject([{}]) do |result, attribute_structure|
18
- if attribute_structure.is_a?(Hash)
19
- result.first.deep_merge!(attribute_structure) { |_, u, v| u == v ? u : [u, v] }
20
- else
21
- result << attribute_structure unless result.first.dig(attribute_structure) || result.include?(attribute_structure)
22
- end
23
- result
24
- end.map(&:presence).compact
17
+ def fields_to_includes(*fields)
18
+ fields_to_tree(fields).to_includes
25
19
  end
26
20
 
27
- # Convert a list of fields (json_api notation) in a list of associations to be
28
- # added to a ActiveRecord Model.includes call
29
- #
30
- # Example:
31
- #
32
- # BoilerPack.fields_to_includes("id,boiler.gas_safe_code") #=> ["boiler"]
33
- #
34
- def fields_to_include(fields, root: nil)
35
- Array(fields).map do |field|
36
- if field.kind_of?(Hash) || field.kind_of?(Array)
37
- field.map { |k, v| fields_to_include(v, root: composite_field(root, k)) }
38
- else
39
- composite_field(root, field)
40
- end
41
- end.flatten
21
+ def fields_serializer(*fields)
22
+ create_serializer_class(fields_to_tree(fields).notation)
42
23
  end
43
24
 
44
- def nested_field(attribute_stack)
45
- parent = attribute_stack.first
46
- return unless association?(parent)
47
- parent_klass = reflections[parent].class_name.constantize
48
- { parent => parent_klass.nested_field(attribute_stack[1..-1]) }.compact.presence || parent
25
+ def create_serializer_class(fields)
26
+ Class.new(ActiveModel::Serializer) do
27
+ Array(fields).each do |field|
28
+ if field.kind_of?(Hash)
29
+ nested_association(field)
30
+ else
31
+ attribute field.to_sym unless association?(field)
32
+ end
33
+ end
34
+ end
49
35
  end
50
36
 
51
37
  private
52
38
 
53
- def array_fields(fields)
54
- Array(fields).map { |str| str.to_s.split(",").map(&:strip) }.flatten
39
+ def fields_to_tree(*fields)
40
+ array_fields(fields.flatten).inject(FieldsTree.new(self), &:merge!)
55
41
  end
56
42
 
57
- def association?(key)
58
- reflections.keys.include?(key)
43
+ # Calls:
44
+ # has_one :user, serializer: new_serializer_class_for_user_fields
45
+ # or
46
+ # belongs_to :user, serializer: new_serializer_class_for_user_fields
47
+ # or
48
+ # has_many :users, serializer: new_serializer_class_for_user_fields
49
+ #
50
+ def nested_association(fields)
51
+ fields.each do |association_name, nested_fields|
52
+ reflection = reflections[association_name]
53
+ send(reflection.macro, association_name.to_sym, serializer: reflection.klass.create_serializer_class(nested_fields))
54
+ end
59
55
  end
60
56
 
61
- def composite_field(*values)
62
- values.compact.join(".")
57
+ def array_fields(fields)
58
+ Array(fields).map { |str| str.to_s.split(",").map(&:strip) }.flatten.sort
63
59
  end
64
60
 
65
- def nested_fields(fields)
66
- array_fields(fields).map { |field| nested_field(field.split(".")) }.compact.uniq
61
+ def association?(key)
62
+ reflections.keys.include?(key)
67
63
  end
68
64
  end
69
65
  end
@@ -0,0 +1,92 @@
1
+ require 'active_record'
2
+ require "active_model_serializers"
3
+
4
+ module Fields
5
+ module Serializer
6
+
7
+ class FieldsTree
8
+ attr_reader :klass, :fields, :associations
9
+
10
+ def initialize(klass)
11
+ @klass = klass
12
+ @fields = []
13
+ @associations = {}
14
+ end
15
+
16
+ def presence
17
+ self if fields.present? || associations.present?
18
+ end
19
+
20
+ def merge!(join_field)
21
+ return self unless join_field.present?
22
+ parent, rest = join_field.to_s.split(".", 2)
23
+ if rest.blank?
24
+ fields << parent if !(existing_field?(parent) || association?(parent))
25
+ else
26
+ existing_association?(parent) ? associations[parent].merge!(rest) : add_association!(parent, rest)
27
+ end
28
+ self
29
+ end
30
+
31
+ def notation
32
+ if fields.present?
33
+ if associations.present?
34
+ fields.dup << associations_to_notation
35
+ else
36
+ fields.one? ? fields.first.dup : fields.dup
37
+ end
38
+ else
39
+ associations_to_notation.presence
40
+ end
41
+ end
42
+
43
+ def to_includes
44
+ to_includes = associations.inject([]) do |result, (k, v)|
45
+ v_includes = v.to_includes
46
+ if v_includes.present?
47
+ new_has_entry = { k => v_includes }
48
+ hash = result.find { |e| e.is_a?(Hash) }
49
+ hash ? hash.merge!(new_has_entry) : (result << new_has_entry)
50
+ result
51
+ else
52
+ result << k
53
+ end
54
+ end.presence
55
+ Array(to_includes).one? ? to_includes.first : to_includes
56
+ end
57
+
58
+ def to_s
59
+ notation.to_s
60
+ end
61
+
62
+ private
63
+
64
+ def add_association!(parent, rest)
65
+ if association?(parent)
66
+ nested_class = klass.reflections[parent].klass
67
+ nested_fields_tree = FieldsTree.new(nested_class).merge!(rest).presence
68
+ new_association = { parent => nested_fields_tree } if nested_fields_tree
69
+ associations.merge!(new_association) if new_association
70
+ end
71
+ end
72
+
73
+ def associations_to_notation
74
+ associations.inject({}) do |result, (k, v)|
75
+ result.merge!(k => v.notation)
76
+ end
77
+ end
78
+
79
+ def existing_association?(value)
80
+ !!associations[value]
81
+ end
82
+
83
+ def existing_field?(value)
84
+ fields.include?(value)
85
+ end
86
+
87
+ def association?(value)
88
+ klass.reflections.keys.include?(value)
89
+ end
90
+ end
91
+ end
92
+ end
@@ -1,5 +1,5 @@
1
1
  module Fields
2
2
  module Serializer
3
- VERSION = "0.4.0"
3
+ VERSION = "0.5.0"
4
4
  end
5
5
  end
@@ -4,7 +4,7 @@ require 'active_support/concern'
4
4
  require "fields/serializer/version"
5
5
  require "fields/serializer/active_record"
6
6
  require "fields/serializer/action_controller"
7
- require "fields/serializer/field_serializer"
7
+ require "fields/serializer/fields_tree"
8
8
 
9
9
  module Fields
10
10
  module Serializer
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fields-serializer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stuart Chinery
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2017-12-19 00:00:00.000000000 Z
13
+ date: 2017-12-20 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: bundler
@@ -145,7 +145,7 @@ files:
145
145
  - lib/fields/serializer.rb
146
146
  - lib/fields/serializer/action_controller.rb
147
147
  - lib/fields/serializer/active_record.rb
148
- - lib/fields/serializer/field_serializer.rb
148
+ - lib/fields/serializer/fields_tree.rb
149
149
  - lib/fields/serializer/version.rb
150
150
  homepage: https://github.com/ltello/fields-serializer
151
151
  licenses:
@@ -1,56 +0,0 @@
1
- require "active_model_serializers"
2
-
3
- # This is a generic serializer intended to return a subset of a model's attributes.
4
- # It can be used with any model but does not currently support associations.
5
- #
6
- # Example usage:
7
- # render json: @region, serializer: FieldSerializer, fields: [:id, :title]
8
- #
9
- # > { "id": "5f19582d-ee28-4e89-9e3a-edc42a8b59e5", "title": "London" }
10
- #
11
- class FieldSerializer < ActiveModel::Serializer
12
- def attributes(*args)
13
- fields = Array(args.first).map { |str| str.to_s.split(",").map(&:strip) }.flatten
14
- adding_id do
15
- merging_attributes do
16
- fields.map { |field| create_attribute_structure(field.split("."), object) }
17
- end
18
- end
19
- end
20
-
21
- private
22
-
23
- def adding_id(&block)
24
- block.call.merge(id: object.id)
25
- end
26
-
27
- def create_attribute_structure(attribute_stack, model)
28
- return model unless model.present?
29
- if model.kind_of?(ActiveRecord::Relation)
30
- collection_attribute_structure(model, attribute_stack)
31
- else
32
- parent = attribute_stack.first
33
- if attribute_stack.count > 1
34
- attribute_structure(model, attribute_stack[1..-1], parent)
35
- else
36
- value_structure(model, parent)
37
- end
38
- end
39
- end
40
-
41
- def attribute_structure(model, attribute_stack, parent)
42
- { parent => create_attribute_structure(attribute_stack, model.send(parent)) }
43
- end
44
-
45
- def collection_attribute_structure(models, attribute_stack)
46
- models.map { |model| create_attribute_structure(attribute_stack, model) }.compact
47
- end
48
-
49
- def merging_attributes(&block)
50
- block.call.inject(:deep_merge!)
51
- end
52
-
53
- def value_structure(model, attribute_name)
54
- { attribute_name => model.send(attribute_name) }
55
- end
56
- end