scoped_serializer 0.0.1

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 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: []