fars 0.0.1 → 0.0.2

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.
@@ -7,225 +7,69 @@
7
7
  # - create new instance, call to_json
8
8
  # - create new instance, add objects to serialize one by one.
9
9
  # This lets you reuse it without determining known data
10
- class Fars::BaseModelSerializer
10
+ #
11
+ class Fars::BaseModelSerializer < Fars::BaseObjectSerializer
11
12
  class << self
12
- def attributes(*attrs)
13
- @attributes = attrs.map(&:to_sym)
14
- end
15
-
16
- def all_attributes
17
- @attributes
18
- end
19
-
20
- # Returns {Object} of class, that corresponds to model,
21
- # this class meant to serialize
22
- def model
23
- @klass ||= get_model
24
- end
25
-
26
- # Returns {Symbol} instance hash root key, as an underscored Model name
27
- def root_key
28
- model.to_s.underscore.to_sym
29
- end
30
-
31
- # Returns {String} capitalized API version
32
- def api_version
33
- namespace_array = name.split('::')
34
- namespace_array.size > 1 ? namespace_array[0] : nil
35
- end
36
-
37
- # Returns {Array} with names of Model relations. Consists of Symbols.
38
- # Filtrated by #all_attributes
39
- def model_relations
40
- @model_relations ||= get_model_relations
41
- end
42
-
43
- # Returns {Array} with names of Model methods. Consists of Symbols
13
+ ##
14
+ # Returns: {Array} with names of Model methods. Consists of Symbols
44
15
  # Filtrated by #all_attributes
16
+ #
45
17
  def model_methods
46
- @model_methods ||= get_model_methods
18
+ @model_methods ||= ((model.attribute_names.map(&:to_sym) | model.instance_methods) - model_relations - serializer_methods) & all_attributes
47
19
  end
48
20
 
49
- # Returns {Array} with names of this serializer instance methods. Consists of Symbols
21
+ ##
22
+ # Returns: {Array} with names of Model relations. Consists of Symbols.
50
23
  # Filtrated by #all_attributes
51
- def serializer_methods
52
- @serializer_methods ||= get_serializer_methods
53
- end
54
-
55
- def serializer_for_relation(name, _api_version=nil)
56
- serializers_cache[name] ||= resolve_serializer(name, _api_version)
57
- end
58
-
59
- private
60
-
61
- def serializers_cache
62
- @serializers_cache ||= {}
63
- end
64
-
65
- def resolve_serializer(relation_name, _api_version=nil)
66
- (
67
- api_prefix +
68
- model.reflect_on_all_associations.
69
- find { |assoc| assoc.name == relation_name }.
70
- class_name.pluralize +
71
- 'Serializer'
72
- ).constantize
73
- end
74
-
75
- def get_model
76
- (self.to_s.match /#{api_prefix}(\w+)Serializer/)[1].singularize.constantize
77
- end
78
-
79
- def get_model_relations
80
- (model.reflect_on_all_associations.map { |r| r.name.to_sym } - serializer_methods) & all_attributes
81
- end
82
-
83
- def get_model_methods
84
- ((model.attribute_names.map(&:to_sym) | model.instance_methods) - model_relations - serializer_methods) & all_attributes
85
- end
86
-
87
- def get_serializer_methods
88
- self.instance_methods & all_attributes
89
- end
90
-
91
- # Returns {String} prefix for serializer class name using API version
92
- def api_prefix
93
- api_version ? api_version + '::' : ''
24
+ #
25
+ def model_relations
26
+ @model_relations ||= (model.reflect_on_all_associations.map { |r| r.name.to_sym } - serializer_methods) & all_attributes
94
27
  end
95
28
  end
96
29
 
97
- # Initialize new instance
98
- #
99
- # Params:
100
- # - object {Object} to serialize
101
- # - opts {Hash} of options:
102
- # - fields {Array} of attributes to serialize. Can be {NilClass}.
103
- # If so - will use all available.
104
- # - scope {Object} context of request. Usually current user
105
- # or current ability. Can be passed as a {Proc}. If so -
106
- # evaluated only when actually called.
107
- # - :add_metadata {Boolean} if to add a node '_metadata'
108
- # - :root_key {Symbol} overwrites the default one from serializer's Class
109
- def initialize(object, opts={})
110
- @object = object
111
- @scope = opts[:scope]
112
- @fields = opts[:fields]
113
- @add_metadata = opts.fetch(:add_metadata, true)
114
- @root_key = opts.fetch(:root_key, self.class.root_key)
115
-
116
- @serialized_klass = self.class.model # this goes first
117
- @all_attributes = self.class.all_attributes
118
- @object_relations = self.class.model_relations
119
- @object_attributes = self.class.model_methods
120
- @serializer_methods = self.class.serializer_methods
121
- end
122
-
123
- def with_object(object)
124
- @object = object
125
- self
126
- end
127
-
128
30
  def as_json
129
31
  # as we can re-use one class of serializer for
130
32
  # many objects, we need to re-evaluate list
131
33
  # of available_attributes for each of them
132
34
  all_attrs = available_attributes
133
35
  item = {}
134
-
135
- (requested_object_attributes & all_attrs).each do |attr|
136
- item[attr] = object.send(attr)
36
+ (requested_model_methods & all_attrs).each do |attr|
37
+ item[attr] = object.public_send(attr)
137
38
  end
138
-
139
39
  (requested_serializer_methods & all_attrs).each do |meth|
140
- item[meth] = self.send(meth)
40
+ item[meth] = self.public_send(meth)
141
41
  end
142
-
143
- (requested_object_relations & all_attrs).each do |rel|
42
+ (requested_model_relations & all_attrs).each do |rel|
144
43
  item[rel] = serialize_relation(rel)
145
44
  end
146
-
147
- hash = {root_key => item}
45
+ hash = { root_key => item }
148
46
  hash[:_metadata] = meta if add_metadata?
149
47
  hash
150
48
  end
151
49
 
152
- def to_json
153
- MultiJson.dump(as_json)
154
- end
155
-
156
- protected
157
-
158
- # TODO: Document
159
- def serialize_relation(relation_name, relation=nil)
160
- klass = self.class.serializer_for_relation(relation_name)
161
- relation ||= object.send(relation_name)
162
- klass.new(relation,
163
- root_key: false,
164
- scope: @scope,
165
- add_metadata: add_metadata?).as_json
166
- end
167
-
168
50
  private
169
51
 
170
- # Things we get from options
171
- attr_reader :object, :fields, :root_key
172
-
173
- # Utility things
174
- attr_reader :serialized_klass
52
+ delegate :model_relations, :model_methods, to: :'self.class'
175
53
 
176
- # Sets of attributes/methods/relations
177
- attr_reader :all_attributes, :object_attributes, :serializer_methods, :object_relations
178
-
179
- # List of attributes requested to be shown.
180
- # This is frequently done by :fields HTTP request
181
- # parameter
182
- def requested_attributes
183
- @requested_attributes ||= get_requested_attributes
184
- end
185
-
186
- def scope
187
- if @scope.is_a? Proc
188
- @scope = @scope.call
189
- else
190
- @scope
191
- end
192
- end
193
-
194
- def add_metadata?
195
- @add_metadata
196
- end
197
-
198
- def requested_object_attributes
199
- @requested_object_attributes ||= object_attributes & requested_attributes
54
+ def requested_model_methods
55
+ @requested_model_methods ||= model_methods & requested_attributes
200
56
  end
201
57
 
202
- def requested_serializer_methods
203
- @requested_serializer_methods ||= serializer_methods & requested_attributes
58
+ def requested_model_relations
59
+ @requested_model_relations ||= model_relations & requested_attributes
204
60
  end
205
61
 
206
- def requested_object_relations
207
- @requested_object_relations ||= object_relations & requested_attributes
208
- end
209
-
210
- # List of attributes available to be shown
211
- # by current security context.
62
+ ##
63
+ # Serializes object's relation
212
64
  #
213
- # Can be re-defined in children classes
214
- def available_attributes
215
- all_attributes
216
- end
217
-
218
- def get_requested_attributes
219
- case fields
220
- when NilClass then all_attributes
221
- when Array then fields.map(&:to_sym) | [:id]
222
- when Symbol then [fields.to_sym]
223
- when String then [fields]
224
- end
225
- end
226
-
227
- # Returns {String} - API version is got by instance class
228
- def api_version
229
- self.class.api_version
65
+ # Returns: {Hash}
66
+ #
67
+ def serialize_relation(relation_name, relation = nil)
68
+ relation ||= object.public_send(relation_name)
69
+ ::Fars::BaseCollectionSerializer.new(relation,
70
+ root_key: false,
71
+ scope: @scope,
72
+ add_metadata: add_metadata?,
73
+ api_version: api_version).as_json
230
74
  end
231
75
  end
@@ -0,0 +1,147 @@
1
+ ##
2
+ # Class: BaseObjectSerializer
3
+ #
4
+ class Fars::BaseObjectSerializer
5
+ class << self
6
+ def attributes(*attrs)
7
+ @attributes = attrs.map(&:to_sym)
8
+ end
9
+
10
+ def all_attributes
11
+ @attributes
12
+ end
13
+
14
+ ##
15
+ # Returns: {Class} of serialized model (can be nil)
16
+ #
17
+ def model
18
+ @model ||= begin
19
+ @class_name ||= self.to_s.sub(/^V\d+::/, '').sub(/Serializer$/, '')
20
+ @class_name.constantize if Object.const_defined?(@class_name)
21
+ end
22
+ end
23
+ attr_writer :model
24
+
25
+ ##
26
+ # Returns: {Symbol} instance hash root key, as an underscored Model name
27
+ #
28
+ def root_key
29
+ @root_key ||= (model ? model.to_s.underscore.to_sym : nil)
30
+ end
31
+ attr_writer :root_key
32
+
33
+ ##
34
+ # Returns: {String} capitalized API version (can be nil)
35
+ #
36
+ def api_version
37
+ namespace_array = name.split('::')
38
+ namespace_array.size > 1 ? namespace_array[0] : nil
39
+ end
40
+
41
+ ##
42
+ # Returns: {Array} with names of this serializer instance methods. Consists of Symbols
43
+ # Filtrated by #all_attributes
44
+ #
45
+ def serializer_methods
46
+ @serializer_methods ||= self.instance_methods & all_attributes
47
+ end
48
+ end
49
+
50
+ ##
51
+ # Initialize new instance
52
+ #
53
+ # Params:
54
+ # - object {ActiveRecord::Base} or any {Object} to serialize
55
+ # - opts {Hash} of options:
56
+ # - fields {Array} of attributes to serialize. Can be {NilClass}.
57
+ # If so - will use all available.
58
+ # - scope {Object} context of request. Usually current user
59
+ # or current ability. Can be passed as a {Proc}. If so -
60
+ # evaluated only when actually called.
61
+ # - :add_metadata {Boolean} if to add a node '_metadata'
62
+ # - :root_key {Symbol} overwrites the default one from serializer's Class
63
+ #
64
+ def initialize(object, opts = {})
65
+ @object = object
66
+ @scope = opts[:scope]
67
+ @fields = opts[:fields]
68
+ @add_metadata = opts.fetch(:add_metadata, self.respond_to?(:meta))
69
+ @root_key = opts.fetch(:root_key, self.class.root_key)
70
+ @api_version = opts.fetch(:api_version, self.class.api_version)
71
+ @params = opts[:params] || {}
72
+ end
73
+
74
+ def with_object(object)
75
+ @object = object
76
+ self
77
+ end
78
+
79
+ def as_json
80
+ item = {}
81
+ ((requested_attributes - requested_serializer_methods) & available_attributes).each do |m|
82
+ next if m == :id && !object.respond_to?(:id)
83
+ item[m] = object.public_send(m)
84
+ end
85
+ (requested_serializer_methods & available_attributes).each do |m|
86
+ item[m] = self.public_send(m)
87
+ end
88
+ hash = { root_key => item }
89
+ hash[:_metadata] = meta if add_metadata?
90
+ hash
91
+ end
92
+
93
+ def to_json
94
+ MultiJson.dump(as_json)
95
+ end
96
+
97
+ def call(obj)
98
+ with_object(obj).as_json
99
+ end
100
+
101
+ private
102
+
103
+ # Things we get from options
104
+ attr_reader :object, :fields, :root_key, :api_version, :params
105
+
106
+ def scope
107
+ if @scope.is_a? Proc
108
+ @scope = @scope.call
109
+ else
110
+ @scope
111
+ end
112
+ end
113
+
114
+ def add_metadata?
115
+ @add_metadata
116
+ end
117
+
118
+ delegate :all_attributes, :serializer_methods, to: :'self.class'
119
+
120
+ def requested_serializer_methods
121
+ @requested_serializer_methods ||= serializer_methods & requested_attributes
122
+ end
123
+
124
+ ##
125
+ # List of attributes requested to be shown.
126
+ # This is frequently done by :fields HTTP request
127
+ # parameter
128
+ #
129
+ def requested_attributes
130
+ @requested_attributes ||= begin
131
+ case fields
132
+ when NilClass then all_attributes
133
+ when Array then fields.map(&:to_sym) | [:id]
134
+ when Symbol then [fields]
135
+ when String then [fields.to_sym]
136
+ end
137
+ end
138
+ end
139
+
140
+ ##
141
+ # List of attributes available to be shown
142
+ # by current security context.
143
+ #
144
+ # Can be re-defined in inherited class
145
+ #
146
+ alias_method :available_attributes, :all_attributes
147
+ end
@@ -0,0 +1,7 @@
1
+ module Fars::ModelSerializable
2
+ def serialize(opts = {})
3
+ api_prefix = opts[:api_version] + '::' if opts[:api_version]
4
+ serializer_class = (opts[:serializer] || "#{api_prefix}#{self.class.base_class}Serializer").constantize
5
+ serializer_class.new(self, opts).to_json
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module Fars::RelationSerializable
2
+ def serialize(opts = {}, &block)
3
+ Fars::BaseCollectionSerializer.new(self, opts, &block).to_json
4
+ end
5
+ end
@@ -1,3 +1,3 @@
1
1
  module Fars
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'ActiveRecord::Base#serialize' do
4
+ subject { Master.create(id: 1, name: 'Object1', data: '123') }
5
+
6
+ specify 'with default options without metadata' do
7
+ json_data = { master: { id: 1, name: 'Object1', data: '123', slaves: [] } }.to_json
8
+ subject.serialize(add_metadata: false).should == json_data
9
+ end
10
+
11
+ specify 'with custom item serializer class' do
12
+ json_data = { father: { id: 1, name: 'Object1', number: 14, slaves: [] } }.to_json
13
+ subject.serialize(serializer: 'AnotherMasterSerializer').should == json_data
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'ActiveRecord::Relation#serialize' do
4
+ before(:each) do
5
+ 1.upto(2) { |i| Master.create(id: i, name: "Object#{i}", data: '123') }
6
+ 1.upto(3) { |i| Slave.create(id: i, master_id: (i % 2) + 1, name: "Slave #{i}") }
7
+ end
8
+ subject { Master.where('1 = 1').order(:id) }
9
+
10
+ specify 'add metadata for collection' do
11
+ json_data = { masters: [{ master: { name: 'Object1' } }, { master: { name: 'Object2' } }],
12
+ _metadata: { order: :id } }.to_json
13
+
14
+ subject.serialize(fields: :name, add_metadata: false, metadata: { order: :id }).should == json_data
15
+ end
16
+ end