scoped_serializer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5fbd0dea1bb6ee4232f72241d4ac445b730b7e06
4
+ data.tar.gz: 8a9841173c363ee66e3928e3fd55b0bfac0772d8
5
+ SHA512:
6
+ metadata.gz: 7d625b615545c73c6465a3fa4408ec1d131e1e99e0493299b224353a07dfd779f18451cb2f2c5373536e730c8a7e73fb2e2d2d23d55e4d8bc93ff3b34d246e6b
7
+ data.tar.gz: ead71b87fcc96ae6dd2d173ea1f9519cb2428b1d499da8b2756c98251e4981b9323d80edacb0728a2137ed9bc84fffb15bbe3372dc3cb7ccce2002e10d9ba960
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2014 Booqable
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'ScopedSerializer'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+
19
+
20
+ Bundler::GemHelper.install_tasks
21
+
@@ -0,0 +1,29 @@
1
+ module ScopedSerializer
2
+ class ArraySerializer < BaseSerializer
3
+
4
+ attr_reader :array, :scope, :scope_name, :options
5
+
6
+ def initialize(array, scope_name=:default, options={})
7
+ @array = array
8
+ @scope_name = scope_name
9
+ @options = options || {}
10
+ end
11
+
12
+ def serializable_hash(options={})
13
+ array.collect do |object|
14
+ ScopedSerializer.for(object, @scope_name, @options.merge(:root => false)).as_json
15
+ end
16
+ end
17
+
18
+ def meta
19
+ data = super
20
+
21
+ if @array.respond_to?(:total_count)
22
+ data = {}.merge(data).merge({ :total_count => @array.total_count })
23
+ end
24
+
25
+ data
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,58 @@
1
+ module ScopedSerializer
2
+ class BaseSerializer
3
+
4
+ ##
5
+ # Sets scope and settings based on @options.
6
+ #
7
+ def set_scope(scope)
8
+ if @options[:associations].present? || @options[:attributes].present?
9
+ @scope = scope.dup
10
+ @scope.attributes *@options[:attributes] if @options[:attributes]
11
+ @scope._association [@options[:associations]] if @options[:associations]
12
+ else
13
+ @scope = scope
14
+ end
15
+ end
16
+
17
+ ##
18
+ # Tries to find the default root key.
19
+ #
20
+ # @example
21
+ # default_root_key(User) # => 'user'
22
+ #
23
+ def default_root_key(object_class)
24
+ if object_class.respond_to?(:model_name)
25
+ object_class.model_name.element
26
+ else
27
+ object_class.name
28
+ end
29
+ end
30
+
31
+ ##
32
+ # Returns JSON using {serializable_hash} which must be implemented on a class.
33
+ # Uses the root key from @options when set.
34
+ #
35
+ def as_json(options={})
36
+ options = @options.merge(options)
37
+
38
+ if options[:root]
39
+ { options[:root].to_sym => serializable_hash }.merge(meta_hash)
40
+ else
41
+ serializable_hash
42
+ end
43
+ end
44
+
45
+ def meta_hash
46
+ if meta.present?
47
+ { :meta => meta }
48
+ else
49
+ {}
50
+ end
51
+ end
52
+
53
+ def meta
54
+ @options[:meta] || {}
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,12 @@
1
+ module ScopedSerializer
2
+ class CollectionSerializer < ArraySerializer
3
+
4
+ def initialize(*args)
5
+ super
6
+
7
+ # Configure root element
8
+ @options[:root] = default_root_key(@array.klass).pluralize if @options[:root].nil?
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ module ScopedSerializer
2
+ class DefaultSerializer < BaseSerializer
3
+
4
+ def initialize(resource, scope=:default, options={})
5
+ @resource = resource
6
+ end
7
+
8
+ def as_json(options={})
9
+ @resource.as_json(options)
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,114 @@
1
+ module ScopedSerializer
2
+ class Scope
3
+
4
+ METHODS = [:root, :attributes, :association, :belongs_to, :has_one, :has_many]
5
+
6
+ attr_accessor :name, :attributes, :associations, :options
7
+
8
+ def initialize(name, default=nil, &block)
9
+ @name = name
10
+ @options = {}
11
+ @attributes = []
12
+ @associations = {}
13
+
14
+ # Merge defaults
15
+ merge!(default) if default
16
+
17
+ self.instance_eval &block if block_given?
18
+ end
19
+
20
+ ##
21
+ # Merges data with given scope.
22
+ #
23
+ # @example
24
+ # scope.merge!(another_scope)
25
+ #
26
+ def merge!(scope)
27
+ @options.merge!(scope.options)
28
+
29
+ @attributes += scope.attributes
30
+ @associations.merge!(scope.associations)
31
+
32
+ @attributes.uniq!
33
+
34
+ self
35
+ end
36
+
37
+ ##
38
+ # Defines the root key.
39
+ #
40
+ # @example
41
+ # scope :collection
42
+ # root :reservations
43
+ # end
44
+ #
45
+ def root(key)
46
+ @options.merge!({ :root => key })
47
+ end
48
+
49
+ ##
50
+ # Defines attributes.
51
+ #
52
+ # @example
53
+ # scope :collection
54
+ # attributes :status
55
+ # end
56
+ #
57
+ def attributes(*attrs)
58
+ if attrs.any?
59
+ @attributes += attrs
60
+ @attributes.uniq!
61
+ else
62
+ @attributes
63
+ end
64
+ end
65
+
66
+ ##
67
+ # Defines an association.
68
+ #
69
+ # @example
70
+ # scope :collection
71
+ # association :customer
72
+ # association :posts => :user, :serializer => UserPostSerializer, :root => :user_posts
73
+ # end
74
+ #
75
+ def association(*args)
76
+ _association(args, { :preload => true })
77
+ end
78
+ alias :belongs_to :association
79
+ alias :has_one :association
80
+ alias :has_many :association
81
+
82
+ ##
83
+ # Duplicates scope.
84
+ #
85
+ def dup
86
+ clone = Scope.new(name)
87
+ clone.merge!(self)
88
+ end
89
+
90
+ ##
91
+ # Actually defines the association but without default_options.
92
+ #
93
+ def _association(args, default_options={})
94
+ return if options.nil?
95
+
96
+ options = args.first
97
+
98
+ if options.is_a?(Hash)
99
+ options = {}.merge(options)
100
+ name = options.keys.first
101
+ properties = options.delete(name)
102
+
103
+ @associations[name] = default_options.merge({ :include => properties }).merge(options)
104
+ elsif options.is_a?(Array)
105
+ options.each do |option|
106
+ association option
107
+ end
108
+ else
109
+ @associations[options] = args[1] || {}
110
+ end
111
+ end
112
+
113
+ end
114
+ end
@@ -0,0 +1,192 @@
1
+ module ScopedSerializer
2
+ class Serializer < BaseSerializer
3
+
4
+ class << self
5
+
6
+ attr_accessor :default_scope, :scopes
7
+
8
+ ##
9
+ # Set default values.
10
+ #
11
+ def inherited(base)
12
+ base.scopes = {}
13
+
14
+ if scopes.present?
15
+ # Inheritance from a serializer that has scoped defined
16
+ scopes.each do |name, scope|
17
+ base.scopes[name] = Scope.new(name, scope)
18
+ end
19
+
20
+ base.default_scope = base.find_scope(:default)
21
+ else
22
+ # Nothing to inherit, set defaults
23
+ base.default_scope = Scope.new(:default)
24
+ base.scopes = { :default => base.default_scope }
25
+ end
26
+ end
27
+
28
+ ##
29
+ # Defines a scope. In this scope all scopes methods are available. See {ScopedSerializer::Scope}
30
+ #
31
+ # @example
32
+ # scope :resource do
33
+ # association :notes, :employee
34
+ # end
35
+ #
36
+ def scope(name, &block)
37
+ self.scopes[name] = Scope.new(name, self.default_scope, &block)
38
+ end
39
+
40
+ ##
41
+ # Finds a defined scope by name.
42
+ #
43
+ def find_scope(name)
44
+ self.scopes ||= {}
45
+ self.scopes[name] || self.default_scope || Scope.new(:default)
46
+ end
47
+
48
+ ##
49
+ # Define available default scope methods.
50
+ # These are default values, thus define them on every scope.
51
+ #
52
+ Scope::METHODS.each do |method|
53
+ define_method method do |*args|
54
+ self.scopes.each do |name, scope|
55
+ scope.send(method, *args)
56
+ end
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+ attr_reader :resource, :scope, :options
63
+
64
+ def initialize(resource, scope=:default, options={})
65
+ @resource = resource
66
+ @options = options || {}
67
+
68
+ if options[:scope].present?
69
+ scope = options[:scope]
70
+ end
71
+
72
+ if scope.is_a?(Symbol)
73
+ @scope_name = scope
74
+ @scope = self.class.find_scope(scope)
75
+ else
76
+ @scope_name = scope.name
77
+ @scope = scope
78
+ end
79
+
80
+ set_scope(@scope)
81
+
82
+ # Inherit options from scope
83
+ @options = {}.merge(@scope.options).merge(@options)
84
+
85
+ if @resource
86
+ @options[:root] = default_root_key(@resource.class) unless @options.key?(:root)
87
+ end
88
+ end
89
+
90
+ ##
91
+ # Collects attributes for serialization.
92
+ # Attributes can be overwritten in the serializer.
93
+ #
94
+ # @return [Hash]
95
+ #
96
+ def attributes_hash
97
+ @scope.attributes.collect do |attr|
98
+ [attr, fetch_property(attr)]
99
+ end.to_h
100
+ end
101
+
102
+ ##
103
+ # Collects associations for serialization.
104
+ # Associations can be overwritten in the serializer.
105
+ #
106
+ # @return [Hash]
107
+ #
108
+ def associations_hash
109
+ hash = {}
110
+ @scope.associations.each do |association, options|
111
+ hash.merge!(render_association(association, options))
112
+ end
113
+ hash
114
+ end
115
+
116
+ ##
117
+ # Renders a specific association.
118
+ #
119
+ # @return [Hash]
120
+ #
121
+ # @example
122
+ # render_association(:employee)
123
+ # render_association([:employee, :company])
124
+ # render_association({ :employee => :address })
125
+ #
126
+ def render_association(association_data, options={})
127
+ hash = {}
128
+
129
+ if association_data.is_a?(Hash)
130
+ association_data.each do |association, association_options|
131
+ data = render_association(association, options.merge(:include => association_options))
132
+ hash.merge!(data) if data
133
+ end
134
+ elsif association_data.is_a?(Array)
135
+ association_data.each do |option|
136
+ data = render_association(option)
137
+ hash.merge!(data) if data
138
+ end
139
+ else
140
+ if options[:preload]
141
+ includes = options[:preload] == true ? options[:include] : options[:preload]
142
+ end
143
+
144
+ object = fetch_association(association_data, includes)
145
+ data = ScopedSerializer.for(object, :default, options.merge(:associations => options[:include])).as_json
146
+
147
+ hash.merge!(data) if data
148
+ end
149
+
150
+ hash
151
+ end
152
+
153
+ ##
154
+ # Fetches property from the serializer or resource.
155
+ # This method makes it possible to overwrite defined attributes or associations.
156
+ #
157
+ def fetch_property(property)
158
+ return nil unless property
159
+
160
+ unless respond_to?(property)
161
+ object = @resource.send(property)
162
+ else
163
+ object = send(property)
164
+ end
165
+ end
166
+
167
+ ##
168
+ # Fetches association and eager loads data.
169
+ # Doesn't eager load when includes is empty or when the association has already been loaded.
170
+ #
171
+ # @example
172
+ # fetch_association(:comments, :user)
173
+ #
174
+ def fetch_association(name, includes=nil)
175
+ association = fetch_property(name)
176
+
177
+ if includes.present? && ! @resource.association(name).loaded?
178
+ association.includes(includes)
179
+ else
180
+ association
181
+ end
182
+ end
183
+
184
+ ##
185
+ # The serializable hash returned.
186
+ #
187
+ def serializable_hash(options={})
188
+ {}.merge(attributes_hash).merge(associations_hash)
189
+ end
190
+
191
+ end
192
+ end
@@ -0,0 +1,3 @@
1
+ module ScopedSerializer
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,87 @@
1
+ require 'scoped_serializer/scope'
2
+ require 'scoped_serializer/base_serializer'
3
+ require 'scoped_serializer/serializer'
4
+ require 'scoped_serializer/default_serializer'
5
+ require 'scoped_serializer/array_serializer'
6
+ require 'scoped_serializer/collection_serializer'
7
+
8
+ ##
9
+ # ScopedSerializer takes care of complex and abstract serialization classes.
10
+ # It does this by allowing serialization scopes. For example, you can define a collection and a resource scope.
11
+ # This means you can render a different JSON output based on context (index/show).
12
+ # You can define any scope you want, there are no predefined scopes.
13
+ #
14
+ # ScopedSerializer supports association and automatically eager loads them when needed.
15
+ #
16
+ # @example
17
+ #
18
+ # class OrderSerializer < ScopedSerializer::Serializer
19
+ # attributes :status, :price_in_cents
20
+ #
21
+ # scope :collection do
22
+ # association :customer => :addresses
23
+ # end
24
+ #
25
+ # scope :resource do
26
+ # association :customer
27
+ # association :notes, :employee
28
+ # end
29
+ # end
30
+ #
31
+ # ScopedSerializer.render(@order, :resource)
32
+ # ScopedSerializer.render(Order.order('id ASC'), :collection)
33
+ #
34
+
35
+ module ScopedSerializer
36
+
37
+ class << self
38
+
39
+ ##
40
+ # Renders a given object.
41
+ # Object can be an ActiveRecord object, array or a ActiveRecord collection.
42
+ #
43
+ # @return [Hash]
44
+ #
45
+ def render(object, scope=:default, options={})
46
+ options.merge!({
47
+ :super => true
48
+ })
49
+
50
+ self.for(object, scope, options).as_json
51
+ end
52
+
53
+ ##
54
+ # Returns an instantized serializer for the given object.
55
+ #
56
+ def for(object, scope=:default, options={})
57
+ if object.respond_to?(:each)
58
+ serializer = find_serializer(object)
59
+ else
60
+ serializer = options[:serializer] || find_serializer(object) || DefaultSerializer
61
+ end
62
+
63
+ serializer.new(object, scope, options) if serializer
64
+ end
65
+
66
+ ##
67
+ # Finds serializer based on object's class.
68
+ #
69
+ def find_serializer(object)
70
+ return object.serializer_class if object.respond_to?(:serializer_class)
71
+
72
+ case object
73
+ when ActiveRecord::Relation, ActiveRecord::Associations::CollectionProxy
74
+ CollectionSerializer
75
+ when Array
76
+ ArraySerializer
77
+ else
78
+ find_serializer_by_class(object.class)
79
+ end
80
+ end
81
+
82
+ def find_serializer_by_class(object_class)
83
+ "#{object_class.name}Serializer".safe_constantize
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :scoped_serializer do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: scoped_serializer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Arjen Oosterkamp
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sqlite3
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
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: bundler
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: rake
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: with_model
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
+ description: Scoped serializers for Rails
84
+ email:
85
+ - mail@arjen.me
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - MIT-LICENSE
91
+ - Rakefile
92
+ - lib/scoped_serializer.rb
93
+ - lib/scoped_serializer/array_serializer.rb
94
+ - lib/scoped_serializer/base_serializer.rb
95
+ - lib/scoped_serializer/collection_serializer.rb
96
+ - lib/scoped_serializer/default_serializer.rb
97
+ - lib/scoped_serializer/scope.rb
98
+ - lib/scoped_serializer/serializer.rb
99
+ - lib/scoped_serializer/version.rb
100
+ - lib/tasks/scoped_serializer_tasks.rake
101
+ homepage: https://github.com/booqable/scoped_serializer
102
+ licenses:
103
+ - MIT
104
+ metadata: {}
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ requirements: []
120
+ rubyforge_project:
121
+ rubygems_version: 2.2.2
122
+ signing_key:
123
+ specification_version: 4
124
+ summary: Scoped serializers for Rails
125
+ test_files: []