ar_serializer 1.0.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.
@@ -0,0 +1,184 @@
1
+ require 'ar_serializer/error'
2
+
3
+ class ArSerializer::CompositeValue
4
+ def initialize(pairs:, output:)
5
+ @pairs = pairs
6
+ @output = output
7
+ end
8
+
9
+ def ar_serializer_build_sub_calls
10
+ [@output, @pairs]
11
+ end
12
+ end
13
+
14
+ module ArSerializer::ArrayLikeCompositeValue
15
+ def ar_serializer_build_sub_calls
16
+ output = []
17
+ record_elements = []
18
+ each do |record|
19
+ data = {}
20
+ output << data
21
+ record_elements << [record, data]
22
+ end
23
+ [output, record_elements]
24
+ end
25
+ end
26
+
27
+ module ArSerializer::Serializer
28
+ def self.current_namespaces
29
+ Thread.current[:ar_serializer_current_namespaces]
30
+ end
31
+
32
+ def self.with_namespaces(namespaces)
33
+ namespaces_was = Thread.current[:ar_serializer_current_namespaces]
34
+ Thread.current[:ar_serializer_current_namespaces] = namespaces
35
+ yield
36
+ ensure
37
+ Thread.current[:ar_serializer_current_namespaces] = namespaces_was
38
+ end
39
+
40
+ def self.serialize(model, args, context: nil, include_id: false, use: nil)
41
+ with_namespaces use do
42
+ attributes = parse_args(args)[:attributes]
43
+ if model.is_a?(ArSerializer::Serializable)
44
+ output = {}
45
+ _serialize [[model, output]], attributes, context, include_id
46
+ output
47
+ else
48
+ sets = model.to_a.map do |record|
49
+ [record, {}]
50
+ end
51
+ _serialize sets, attributes, context, include_id
52
+ sets.map(&:last)
53
+ end
54
+ end
55
+ end
56
+
57
+ def self._serialize(mixed_value_outputs, attributes, context, include_id, only = nil, except = nil)
58
+ mixed_value_outputs.group_by { |v, _o| v.class }.each do |klass, value_outputs|
59
+ next unless klass.respond_to? :_serializer_field_info
60
+ models = value_outputs.map(&:first)
61
+ value_outputs.each { |value, output| output[:id] = value.id } if include_id && klass.method_defined?(:id)
62
+ if attributes[:*]
63
+ all_keys = klass._serializer_field_keys.map(&:to_sym)
64
+ all_keys &= only.map(&:to_sym) if only
65
+ all_keys -= except.map(&:to_sym) if except
66
+ attributes = all_keys.map { |k| [k, {}] }.to_h.merge attributes
67
+ attributes.delete :*
68
+ end
69
+ attributes.each_key do |name|
70
+ field = klass._serializer_field_info name
71
+ raise ArSerializer::InvalidQuery, "No serializer field `#{name}`#{" namespaces: #{current_namespaces}" if current_namespaces} for #{klass}" unless field
72
+ ActiveRecord::Associations::Preloader.new.preload models, field.includes if field.includes.present?
73
+ end
74
+
75
+ preloader_params = attributes.flat_map do |name, sub_args|
76
+ klass._serializer_field_info(name).preloaders.map do |p|
77
+ [p, sub_args[:params]]
78
+ end
79
+ end
80
+ defaults = klass._serializer_field_info(:defaults)
81
+ if defaults
82
+ preloader_params += defaults.preloaders.map { |p| [p] }
83
+ end
84
+ preloader_values = preloader_params.compact.uniq.map do |key|
85
+ preloader, params = key
86
+ if preloader.arity < 0
87
+ [key, preloader.call(models, context, params || {})]
88
+ else
89
+ [key, preloader.call(*[models, context, params || {}].take(preloader.arity))]
90
+ end
91
+ end.to_h
92
+
93
+ if defaults
94
+ preloadeds = defaults.preloaders.map { |p| preloader_values[[p]] } || []
95
+ value_outputs.each do |value, output|
96
+ data = value.instance_exec(*preloadeds, context, {}, &defaults.data_block)
97
+ output.update data
98
+ end
99
+ end
100
+
101
+ attributes.each do |name, sub_arg|
102
+ params = sub_arg[:params]
103
+ sub_calls = []
104
+ column_name = sub_arg[:column_name] || name
105
+ info = klass._serializer_field_info name
106
+ preloadeds = info.preloaders.map { |p| preloader_values[[p, params]] } || []
107
+ data_block = info.data_block
108
+ value_outputs.each do |value, output|
109
+ child = value.instance_exec(*preloadeds, context, params || {}, &data_block)
110
+ if child.is_a?(Array) && child.all? { |el| el.is_a? ArSerializer::Serializable }
111
+ output[column_name] = child.map do |record|
112
+ data = {}
113
+ sub_calls << [record, data]
114
+ data
115
+ end
116
+ elsif child.respond_to? :ar_serializer_build_sub_calls
117
+ sub_output, record_elements = child.ar_serializer_build_sub_calls
118
+ record_elements.each { |o| sub_calls << o }
119
+ output[column_name] = sub_output
120
+ elsif child.is_a? ArSerializer::CompositeValue
121
+ sub_output, record_elements = child.build
122
+ record_elements.each { |o| sub_calls << o }
123
+ output[column_name] = sub_output
124
+ elsif child.is_a? ArSerializer::Serializable
125
+ data = {}
126
+ sub_calls << [child, data]
127
+ output[column_name] = data
128
+ else
129
+ output[column_name] = child
130
+ end
131
+ end
132
+ next if sub_calls.empty?
133
+ sub_attributes = sub_arg[:attributes] || {}
134
+ info.validate_attributes sub_attributes
135
+ _serialize sub_calls, sub_attributes, context, include_id, info.only, info.except
136
+ end
137
+ end
138
+ end
139
+
140
+ def self.deep_with_indifferent_access params
141
+ case params
142
+ when Array
143
+ params.map { |v| deep_with_indifferent_access v }
144
+ when Hash
145
+ params.transform_keys(&:to_sym).transform_values! do |v|
146
+ deep_with_indifferent_access v
147
+ end
148
+ else
149
+ params
150
+ end
151
+ end
152
+
153
+ def self.parse_args(args, only_attributes: false)
154
+ attributes = {}
155
+ params = nil
156
+ column_name = nil
157
+ (args.is_a?(Array) ? args : [args]).each do |arg|
158
+ if arg.is_a?(Symbol) || arg.is_a?(String)
159
+ attributes[arg.to_sym] = {}
160
+ elsif arg.is_a? Hash
161
+ arg.each do |key, value|
162
+ sym_key = key.to_sym
163
+ if only_attributes
164
+ attributes[sym_key] = value == true ? {} : parse_args(value)
165
+ next
166
+ end
167
+ if sym_key == :as
168
+ column_name = value
169
+ elsif sym_key == :attributes
170
+ attributes.update parse_args(value, only_attributes: true)
171
+ elsif sym_key == :params
172
+ params = deep_with_indifferent_access value
173
+ else
174
+ attributes[sym_key] = value == true ? {} : parse_args(value)
175
+ end
176
+ end
177
+ else
178
+ raise ArSerializer::InvalidQuery, "Arg type missmatch(Symbol, String or Hash): #{arg}"
179
+ end
180
+ end
181
+ return attributes if only_attributes
182
+ { attributes: attributes, column_name: column_name, params: params || {} }
183
+ end
184
+ end
@@ -0,0 +1,58 @@
1
+ require_relative 'graphql'
2
+
3
+ module ArSerializer::TypeScript
4
+ def self.generate_type_definition(*classes)
5
+ types = related_serializer_types classes.flatten
6
+ [
7
+ types.map { |t| data_type_definition t },
8
+ types.map { |t| query_type_definition t }
9
+ ].join "\n"
10
+ end
11
+
12
+ def self.query_type_definition(type)
13
+ field_definitions = type.fields.map do |field|
14
+ association_type = field.type.association_type
15
+ if association_type
16
+ qname = "Type#{association_type.name}Query"
17
+ if field.args.empty?
18
+ "#{field.name}?: true | #{qname} | { as?: string; attributes?: #{qname} }"
19
+ else
20
+ "#{field.name}?: true | #{qname} | { as?: string; params: #{field.args_ts_type}; attributes?: #{qname} }"
21
+ end
22
+ else
23
+ "#{field.name}?: true | { as: string }"
24
+ end
25
+ end
26
+ field_definitions << "'*'?: true"
27
+ query_type_name = "Type#{type.name}Query"
28
+ base_query_type_name = "Type#{type.name}QueryBase"
29
+ <<~TYPE
30
+ export type #{query_type_name} = keyof (#{base_query_type_name}) | Readonly<(keyof (#{base_query_type_name}))[]> | #{base_query_type_name}
31
+ export interface #{base_query_type_name} {
32
+ #{field_definitions.map { |line| " #{line}" }.join("\n")}
33
+ }
34
+ TYPE
35
+ end
36
+
37
+ def self.data_type_definition(type)
38
+ field_definitions = []
39
+ type.fields.each do |field|
40
+ field_definitions << "#{field.name}: #{field.type.ts_type}"
41
+ end
42
+ field_definitions << "_meta?: { name: '#{type.name}'; query: Type#{type.name}QueryBase }"
43
+ <<~TYPE
44
+ export interface Type#{type.name} {
45
+ #{field_definitions.map { |line| " #{line}" }.join("\n")}
46
+ }
47
+ TYPE
48
+ end
49
+
50
+ def self.related_serializer_types(classes)
51
+ types_set = {}
52
+ classes.each do |klass|
53
+ type = ArSerializer::GraphQL::TypeClass.from klass
54
+ type.collect_types types_set
55
+ end
56
+ types_set.keys.grep(ArSerializer::GraphQL::TypeClass).sort_by(&:name)
57
+ end
58
+ end
@@ -0,0 +1,3 @@
1
+ module ArSerializer
2
+ VERSION = '1.0.0'
3
+ end
metadata ADDED
@@ -0,0 +1,160 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ar_serializer
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - tompng
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-05-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: top_n_loader
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: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sqlite3
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: minitest
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: ActiveRecord serializer, avoid N+1
112
+ email:
113
+ - tomoyapenguin@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".travis.yml"
120
+ - Gemfile
121
+ - Gemfile.lock
122
+ - LICENSE.txt
123
+ - README.md
124
+ - Rakefile
125
+ - ar_serializer.gemspec
126
+ - bin/console
127
+ - bin/setup
128
+ - lib/ar_serializer.rb
129
+ - lib/ar_serializer/error.rb
130
+ - lib/ar_serializer/field.rb
131
+ - lib/ar_serializer/graphql.rb
132
+ - lib/ar_serializer/graphql/parser.rb
133
+ - lib/ar_serializer/graphql/types.rb
134
+ - lib/ar_serializer/serializer.rb
135
+ - lib/ar_serializer/type_script.rb
136
+ - lib/ar_serializer/version.rb
137
+ homepage: https://github.com/tompng/ar_serializer
138
+ licenses:
139
+ - MIT
140
+ metadata: {}
141
+ post_install_message:
142
+ rdoc_options: []
143
+ require_paths:
144
+ - lib
145
+ required_ruby_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ required_rubygems_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ requirements: []
156
+ rubygems_version: 3.0.1
157
+ signing_key:
158
+ specification_version: 4
159
+ summary: ActiveRecord serializer, avoid N+1
160
+ test_files: []