fast_serializer_ruby 0.2.1 → 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,7 +3,11 @@
3
3
  module FastSerializer
4
4
  module JsonModel
5
5
  class Array < Relationship
6
- def serialize(resources, params = {}, context = nil)
6
+ # @param resource [Object]
7
+ # @param params [Hash]
8
+ # @param context [Hash]
9
+ # @return [Array]
10
+ def serialize(resources, params, context)
7
11
  return if resources.nil?
8
12
 
9
13
  if @serializer_klass
@@ -13,7 +17,7 @@ module FastSerializer
13
17
  end
14
18
  end
15
19
 
16
- def included?(_resources, _params = {}, context = nil)
20
+ def included?(*)
17
21
  true
18
22
  end
19
23
  end
@@ -3,32 +3,126 @@
3
3
  module FastSerializer
4
4
  module JsonModel
5
5
  class Attribute < Node
6
- def serialize(resource, params = {}, context = nil)
7
- context ||= self
6
+ attr_accessor :mixin,
7
+ :method_name,
8
+ :method_arity,
9
+ :cond,
10
+ :cond_arity,
11
+ :cond_method_name,
12
+ :injected
8
13
 
9
- if method.is_a?(Proc)
14
+ def initialize(*)
15
+ super
10
16
 
11
- if method.arity.abs == 1
12
- context.instance_exec(resource, &method)
13
- else
14
- context.instance_exec(resource, params, &method)
15
- end
17
+ @mixin = nil
18
+ @method_name = nil
19
+ @injected = false
20
+ @cond_method_name = nil
21
+ @cond = nil
22
+ @cond = @opts[:if] || @opts[:unless] || @cond
16
23
 
17
- else
18
- resource.public_send(method)
19
- end
24
+ init_with_proc if method.is_a?(Proc)
25
+ init_with_cond if !cond.nil? && cond.is_a?(Proc)
26
+ end
27
+
28
+ def injectable?
29
+ !mixin.nil?
20
30
  end
21
31
 
22
- def included?(resource, params, context = nil)
23
- return true if @opts[:if].nil? && @opts[:unless].nil?
32
+ def inject(context)
33
+ context.include(mixin)
34
+ self.injected = true
35
+ end
36
+
37
+ # @param resource [Object]
38
+ # @param params [Hash]
39
+ # @param context [Hash]
40
+ # @return [Object]
41
+ def serialize(resource, params, context)
42
+ can_execute_on_mixin = injected && !method_name.nil? && !context.nil?
24
43
 
25
- cond = @opts[:if] || @opts[:unless]
44
+ val = if can_execute_on_mixin
45
+ call_method_on_context(context, method_name, method_arity, resource, params)
46
+ elsif method.is_a?(Proc)
47
+ call_proc_binding_to_context(context, method, method_arity, resource, params)
48
+ else
49
+ resource.public_send(method)
50
+ end
51
+
52
+ val.freeze
53
+
54
+ val
55
+ end
56
+
57
+ # @param resource [Object]
58
+ # @param params [Hash]
59
+ # @param context [Hash]
60
+ # @return [Boolean]
61
+ def included?(resource, params, context)
62
+ return true if cond.nil?
63
+
64
+ can_execute_on_mixin = injected && !cond_method_name.nil? && !context.nil?
65
+
66
+ res = if can_execute_on_mixin
67
+ call_method_on_context(context, cond_method_name, cond_arity, resource, params)
68
+ elsif cond.is_a?(Proc)
69
+ call_proc_binding_to_context(context, cond, cond_arity, resource, params)
70
+ else
71
+ context.public_send(cond)
72
+ end
26
73
 
27
- res = context.instance_exec(resource, params, &cond)
28
74
  res = !res unless @opts[:unless].nil?
29
75
 
30
76
  res
31
77
  end
78
+
79
+ private
80
+
81
+ def init_with_cond
82
+ @cond_method_name = "__#{key}_cond__"
83
+ @cond_arity = cond.arity.abs
84
+ @mixin ||= Module.new
85
+
86
+ if RUBY_VERSION <= '2.5.0'
87
+ @mixin.redefine_method @cond_method_name, &cond
88
+ else
89
+ @mixin.define_method @cond_method_name, &cond
90
+ end
91
+ end
92
+
93
+ def init_with_proc
94
+ @method_name = "__#{key}__"
95
+ @method_arity = method.arity.abs
96
+ @mixin = Module.new
97
+
98
+ if RUBY_VERSION <= '2.5.0'
99
+ @mixin.redefine_method @method_name, &method
100
+ else
101
+ @mixin.define_method @method_name, &method
102
+ end
103
+ end
104
+
105
+ def call_proc_binding_to_context(context, prc, arity, resource, params)
106
+ case arity
107
+ when 1
108
+ context.instance_exec(resource, &prc)
109
+ when 2
110
+ context.instance_exec(resource, params, &prc)
111
+ when 0
112
+ context.instance_exec(&prc)
113
+ end
114
+ end
115
+
116
+ def call_method_on_context(context, method_name, arity, resource, params)
117
+ case arity
118
+ when 0
119
+ context.public_send(method_name)
120
+ when 1
121
+ context.public_send(method_name, resource)
122
+ when 2
123
+ context.public_send(method_name, resource, params)
124
+ end
125
+ end
32
126
  end
33
127
  end
34
128
  end
@@ -3,16 +3,18 @@
3
3
  module FastSerializer
4
4
  module JsonModel
5
5
  class HasManyRelationship < Relationship
6
- def serialize(resource, params = {}, context = nil)
6
+ # @param resource [Object]
7
+ # @param params [Hash]
8
+ # @return [Array<Hash>]
9
+ def serialize(resource, params, _context)
7
10
  collection = resource.public_send(method)
8
11
  return if collection.nil?
9
12
 
10
13
  if @serializer_klass
11
14
  @serializer_klass.new(collection, params).serializable_hash
12
15
  elsif @schema
13
- collection.map { |resource| @schema.serialize_resource(resource, params) }
16
+ collection.map { |entry| @schema.serialize_resource(entry, params) }
14
17
  end
15
-
16
18
  end
17
19
  end
18
20
  end
@@ -3,7 +3,10 @@
3
3
  module FastSerializer
4
4
  module JsonModel
5
5
  class HasOneRelationship < Relationship
6
- def serialize(resource, params = {}, context = nil)
6
+ # @param resource [Object]
7
+ # @param params [Hash]
8
+ # @return [Hash]
9
+ def serialize(resource, params, _)
7
10
  relation = resource.public_send(method)
8
11
 
9
12
  if @serializer_klass
@@ -5,17 +5,25 @@ module FastSerializer
5
5
  class Node
6
6
  attr_accessor :key, :method, :context
7
7
 
8
+ # @param key [String]
9
+ # @param method [String]
10
+ # @param opts [Hash]
8
11
  def initialize(key: nil, method: nil, opts: {}, **_)
9
- @key = key
12
+ @key = key&.to_sym
10
13
  @method = method || key
11
14
  @opts = opts || {}
12
15
  end
13
16
 
14
- def serialize(_resource, _params = {}, context = nil)
17
+ # @return [Boolean]
18
+ def injectable?
19
+ false
20
+ end
21
+
22
+ def serialize(_resource, _params, _context = nil)
15
23
  raise NotImplementedError
16
24
  end
17
25
 
18
- def included?(_resource, _params = {}, context = nil)
26
+ def included?(_resource, _params, _context = nil)
19
27
  raise NotImplementedError
20
28
  end
21
29
  end
@@ -5,27 +5,37 @@ module FastSerializer
5
5
  class Object < Node
6
6
  attr_accessor :attributes
7
7
 
8
- def initialize(*args)
8
+ def initialize(args = {})
9
9
  super
10
10
  @attributes = {}
11
11
  end
12
12
 
13
+ # @param attribute [FastSerializer::JsonModel::Node]
13
14
  def add_attribute(attribute)
14
15
  attributes[attribute.key] = attribute
15
16
  end
16
17
 
17
- def serialize(resource, params = {}, context = nil)
18
+ # @param resource [Object]
19
+ # @param params [Hash]
20
+ # @param context [Hash]
21
+ # @return [Hash]
22
+ def serialize(resource, params, context)
18
23
  return if resource.nil?
19
24
 
20
- attributes.values.each_with_object({}) do |attribute, res|
21
- next res unless attribute.included?(resource, params, context)
25
+ result = {}
26
+
27
+ attributes.each do |_, attribute|
28
+ next unless attribute.included?(resource, params, context)
22
29
 
23
30
  val = attribute.serialize(resource, params, context)
24
- res[attribute.key] = val
31
+ result[attribute.key] = val
25
32
  end
33
+
34
+ result
26
35
  end
27
36
 
28
- def included?(_resource, _params = {}, context = nil)
37
+ # @return [Boolean]
38
+ def included?(*)
29
39
  true
30
40
  end
31
41
  end
@@ -5,22 +5,49 @@ module FastSerializer
5
5
  class Relationship < Attribute
6
6
  attr_accessor :serialization_schema
7
7
 
8
- def initialize(key: nil, method: nil, opts: {}, serializer: nil, schema: nil)
8
+ # @param serializer [FastSerializer::Schema::Mixin]
9
+ # @param schema [FastSerializer::Schema]
10
+ def initialize(serializer: nil, schema: nil, **)
9
11
  super
12
+
10
13
  @serializer_klass = serializer
11
14
  @schema = schema
12
15
 
13
- raise ArgumentError, "must provide serializer or schema" if @serializer_klass.nil? && @schema.nil?
16
+ if @serializer_klass.nil? && @schema.nil?
17
+ raise ArgumentError, 'must provide serializer or schema'
18
+ end
14
19
  end
15
20
 
21
+ # @param resource [Object]
22
+ # @param params [Hash]
23
+ # @param context [Hash]
24
+ # @return [Boolean]
16
25
  def included?(resource, params, context)
17
- super(resource, params) && include_relation?(params)
26
+ super && include_relation?(params)
18
27
  end
19
28
 
29
+ # @param params [Hash]
30
+ # @return [Boolean]
20
31
  def include_relation?(params)
32
+ include?(params) && !exclude?(params)
33
+ end
34
+
35
+ # @param params [Hash]
36
+ # @return [Boolean]
37
+ def exclude?(params)
38
+ return false if params[:exclude].nil?
39
+ return false if params[:exclude].empty?
40
+
41
+ params[:exclude_index].key?(key)
42
+ end
43
+
44
+ # @param params [Hash]
45
+ # @return [Boolean]
46
+ def include?(params)
21
47
  return true if params[:include].nil?
48
+ return false if params[:include].empty?
22
49
 
23
- params[:include].include?(key)
50
+ params[:include_index].key?(key)
24
51
  end
25
52
  end
26
53
  end
@@ -3,91 +3,125 @@
3
3
  require 'forwardable'
4
4
 
5
5
  module FastSerializer
6
-
7
6
  class Schema
8
- attr_accessor :_root, :serialization_schema, :params
9
-
10
- def initialize(params = {})
11
- @root = nil
12
- @serialization_schema = JsonModel::Object.new
13
- @params = (params || {}).symbolize_keys
14
- @params[:self] = self
15
-
16
- if @params[:include]
17
- if @params[:include].any?
18
- @params[:include] = @params[:include].map(&:to_sym)
19
- end
20
- end
7
+ attr_reader :_root, :serialization_schema, :params, :strict
8
+
9
+ def initialize(params = {}, root = nil, strict = nil)
10
+ @root = root
11
+ @strict = strict || FastSerializer.config.strict
12
+ @serialization_schema = FastSerializer::JsonModel::Object.new
13
+ @params = FastSerializer::Utils.symbolize_keys(params || {})
14
+ @params[:self] = self
15
+ @params[:include_index] = {}
16
+ @params[:exclude_index] = {}
17
+
18
+ self.include = @params.delete(:include)
19
+ self.exclude = @params.delete(:exclude)
20
+ end
21
+
22
+ def include=(list)
23
+ return unless list
24
+ return if list.empty?
25
+
26
+ @params[:include] = list.map(&:to_sym)
27
+ @params[:include_index] = @params[:include].map { |key| [key, nil] }.to_h
21
28
  end
22
29
 
23
- # @param [Array] attribute_names
30
+ def exclude=(list)
31
+ return unless list
32
+ return if list.empty?
33
+
34
+ @params[:exclude] = list.map(&:to_sym)
35
+ @params[:exclude_index] = @params[:exclude].map { |key| [key, nil] }.to_h
36
+ end
37
+
38
+ # Defines a list of attributes for serialization
39
+ #
40
+ # @param attribute_names [Array<String, Symbol>] a list of attributes to serialize
41
+ # each of these attributes value is fetched calling a corresponding method from a resource instance
42
+ # passed to the serializer
24
43
  def attributes(*attribute_names)
25
44
  attribute_names.each do |attribute_name|
26
- serialization_schema.add_attribute JsonModel::Attribute.new(
27
- key: attribute_name,
28
- method: attribute_name
45
+ serialization_schema.add_attribute(
46
+ JsonModel::Attribute.new(key: attribute_name, method: attribute_name)
29
47
  )
30
48
  end
31
49
  end
32
50
 
33
- # @param [String] attribute_name
34
- # @param [Hash] opts - attribute options
35
- # @param [Proc] block - result is used as the attribute value
51
+ # Defines an attribute for serialization
52
+ #
53
+ # @param attribute_name [String, Symbol] an attribute name
54
+ # @param opts [Hash] attribute options
55
+ # @option opts [Proc] :if conditional clause. accepts a proc/lambda which has to return a boolean
56
+ # @option opts [Proc] :unless (see opts:if)
57
+ # @param block [Proc] result is used as the attribute value
58
+ #
36
59
  def attribute(attribute_name, opts = {}, &block)
37
- serialization_schema.add_attribute JsonModel::Attribute.new(
38
- key: attribute_name,
39
- method: block,
40
- opts: opts
60
+ serialization_schema.add_attribute(
61
+ JsonModel::Attribute.new(
62
+ key: attribute_name,
63
+ method: block,
64
+ opts: opts
65
+ )
41
66
  )
42
67
  end
43
68
 
44
- # @param [String] attribute_name
45
- # @param [Hash] opts - attribute options
69
+ # Defines an attribute for serialization
70
+ #
71
+ # @param attribute_name [String, Symbol] an attribute name
72
+ # @param opts [Hash] attribute options
73
+ # @option opts [Proc] :if conditional clause. accepts a proc/lambda which has to return a boolean
74
+ # @option opts [Proc] :unless (see opts:if)
75
+ # @option opts [FastSerializer::Schema::Mixin, nil] :serializer a serializer class with injected module or a inherited class
76
+ # @option opts [FastSerializer::Schema] :schema
77
+ #
46
78
  def has_one(attribute_name, opts = {})
47
79
  serialization_schema.add_attribute JsonModel::HasOneRelationship.new(
48
- key: opts.delete(:key) || attribute_name,
49
- method: opts.delete(:method) || attribute_name,
50
- opts: opts,
51
- schema: opts.delete(:schema),
80
+ key: opts.delete(:key) || attribute_name,
81
+ method: opts.delete(:method) || attribute_name,
82
+ opts: opts,
83
+ schema: opts.delete(:schema),
52
84
  serializer: opts.delete(:serializer)
53
85
  )
54
86
  end
55
87
 
56
88
  alias belongs_to has_one
57
89
 
58
- # @param [String] attribute_name
59
- # @param [Hash] opts - attribute options
90
+ # @param attribute_name [String]
91
+ # @param opts [Hash] attribute options
60
92
  def has_many(attribute_name, opts = {})
61
- serialization_schema.add_attribute JsonModel::HasManyRelationship.new(
62
- key: opts.delete(:key) || attribute_name,
63
- method: opts.delete(:method) || attribute_name,
64
- opts: opts,
65
- schema: opts.delete(:schema),
66
- serializer: opts.delete(:serializer),
93
+ serialization_schema.add_attribute(
94
+ JsonModel::HasManyRelationship.new(
95
+ key: opts.delete(:key) || attribute_name,
96
+ method: opts.delete(:method) || attribute_name,
97
+ opts: opts,
98
+ schema: opts.delete(:schema),
99
+ serializer: opts.delete(:serializer)
100
+ )
67
101
  )
68
102
  end
69
103
 
70
104
  # @param [String] attribute_name
71
105
  # @param [Hash] opts - attribute options
72
106
  def list(attribute_name, opts = {})
73
- serialization_schema.add_attribute JsonModel::Array.new(
74
- key: attribute_name,
75
- method: attribute_name,
76
- opts: opts,
77
- schema: opts.delete(:schema),
78
- serializer: opts.delete(:serializer)
107
+ serialization_schema.add_attribute(
108
+ JsonModel::Array.new(
109
+ key: attribute_name,
110
+ method: attribute_name,
111
+ opts: opts,
112
+ schema: opts.delete(:schema),
113
+ serializer: opts.delete(:serializer)
114
+ )
79
115
  )
80
116
  end
81
117
 
82
118
  # @param [String] root_key - a key under which serialization result is nested
83
119
  def root(root_key)
84
- self._root = root_key
120
+ @_root = root_key
85
121
  end
86
122
 
87
123
  def deep_copy
88
- schema = FastSerializer::Schema.new
89
- schema.params = params
90
- schema._root = _root
124
+ schema = FastSerializer::Schema.new(params, _root, strict)
91
125
 
92
126
  serialization_schema.attributes.each do |key, attribute|
93
127
  schema.serialization_schema.attributes[key] = attribute
@@ -97,20 +131,33 @@ module FastSerializer
97
131
  end
98
132
 
99
133
  def serialize_resource(resource, params = {}, context = self)
100
- _params_dup = self.params.merge(params).symbolize_keys
134
+ Utils.ref_merge(self.params, params)
135
+ _params_dup = FastSerializer::Utils.symbolize_keys(self.params)
101
136
  meta = _params_dup.delete(:meta)
102
137
 
103
- is_collection = resource.respond_to?(:size) && !resource.respond_to?(:each_pair)
138
+ is_collection = if _params_dup.key?(:is_collection)
139
+ _params_dup.delete(:is_collection)
140
+ params.delete(:is_collection)
141
+ else
142
+ resource.respond_to?(:each) && !resource.respond_to?(:each_pair)
143
+ end
144
+
145
+ root = (_root || _params_dup.delete(:root))
146
+
147
+ res = if is_collection
104
148
 
105
- _serialization_schema = if is_collection
106
- JsonModel::Array.new(schema: serialization_schema)
107
- else
108
- serialization_schema
109
- end
149
+ if !context.is_a?(self.class)
150
+ # need to bind context
151
+ resource.map { |entry| context.class.new(entry, _params_dup).serializable_hash }
152
+ else
153
+ JsonModel::Array.new(schema: serialization_schema).serialize(resource, _params_dup, context)
154
+ end
110
155
 
111
- res = _serialization_schema.serialize(resource, _params_dup, context)
112
- root = (_root || _params_dup.delete(:root))
113
- res = { root => res } if root && !root.empty?
156
+ else
157
+ serialization_schema.serialize(resource, _params_dup, context)
158
+ end
159
+
160
+ res = { root => res } if root && !root.empty?
114
161
 
115
162
  res[:meta] = meta if res.is_a?(Hash) && meta
116
163
 
@@ -118,52 +165,7 @@ module FastSerializer
118
165
  end
119
166
 
120
167
  def serialize_resource_to_json(resource, params = {}, context = self)
121
- FastSerializer.config.coder.dump(serialize_resource(resource, params))
122
- end
123
-
124
- module Mixin
125
-
126
- module ClassMethods
127
- attr_accessor :__schema__
128
-
129
- def inherited(subclass)
130
- subclass.__schema__ = self.__schema__.deep_copy
131
- end
132
-
133
- def method_missing(method, *args, &block)
134
- if __schema__.respond_to?(method)
135
- __schema__.public_send(method, *args, &block)
136
- else
137
- super
138
- end
139
- end
140
- end
141
-
142
- module InstanceMethods
143
- attr_accessor :resource, :params
144
-
145
- def initialize(resource, params = {})
146
- self.resource = resource
147
- self.params = params || {}
148
- end
149
-
150
- def serializable_hash
151
- self.class.__schema__.serialize_resource(resource, params, self)
152
- end
153
-
154
- def serialized_json
155
- self.class.__schema__.serialize_resource_to_json(resource, params, self)
156
- end
157
-
158
- alias as_json serializable_hash
159
- alias to_json serialized_json
160
- end
161
-
162
- def self.included(base)
163
- base.extend ClassMethods
164
- base.include InstanceMethods
165
- base.__schema__ = FastSerializer::Schema.new
166
- end
168
+ FastSerializer.config.coder.dump(serialize_resource(resource, params, context))
167
169
  end
168
170
  end
169
171
  end