cm-active_model_serializers 0.10.0.rc1.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.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.travis.yml +26 -0
  4. data/CHANGELOG.md +8 -0
  5. data/CONTRIBUTING.md +31 -0
  6. data/Gemfile +17 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +326 -0
  9. data/Rakefile +12 -0
  10. data/cm-active_model_serializers.gemspec +26 -0
  11. data/lib/action_controller/serialization.rb +62 -0
  12. data/lib/active_model/serializer.rb +261 -0
  13. data/lib/active_model/serializer/adapter.rb +87 -0
  14. data/lib/active_model/serializer/adapter/fragment_cache.rb +78 -0
  15. data/lib/active_model/serializer/adapter/json.rb +52 -0
  16. data/lib/active_model/serializer/adapter/json/fragment_cache.rb +15 -0
  17. data/lib/active_model/serializer/adapter/json_api.rb +152 -0
  18. data/lib/active_model/serializer/adapter/json_api/fragment_cache.rb +22 -0
  19. data/lib/active_model/serializer/adapter/null.rb +11 -0
  20. data/lib/active_model/serializer/array_serializer.rb +32 -0
  21. data/lib/active_model/serializer/configuration.rb +13 -0
  22. data/lib/active_model/serializer/fieldset.rb +40 -0
  23. data/lib/active_model/serializer/version.rb +5 -0
  24. data/lib/active_model_serializers.rb +18 -0
  25. data/lib/generators/serializer/USAGE +6 -0
  26. data/lib/generators/serializer/serializer_generator.rb +37 -0
  27. data/lib/generators/serializer/templates/serializer.rb +8 -0
  28. data/test/action_controller/adapter_selector_test.rb +51 -0
  29. data/test/action_controller/explicit_serializer_test.rb +110 -0
  30. data/test/action_controller/json_api_linked_test.rb +173 -0
  31. data/test/action_controller/serialization_scope_name_test.rb +63 -0
  32. data/test/action_controller/serialization_test.rb +365 -0
  33. data/test/adapter/fragment_cache_test.rb +27 -0
  34. data/test/adapter/json/belongs_to_test.rb +48 -0
  35. data/test/adapter/json/collection_test.rb +73 -0
  36. data/test/adapter/json/has_many_test.rb +36 -0
  37. data/test/adapter/json_api/belongs_to_test.rb +147 -0
  38. data/test/adapter/json_api/collection_test.rb +89 -0
  39. data/test/adapter/json_api/has_many_embed_ids_test.rb +45 -0
  40. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +98 -0
  41. data/test/adapter/json_api/has_many_test.rb +106 -0
  42. data/test/adapter/json_api/has_one_test.rb +59 -0
  43. data/test/adapter/json_api/linked_test.rb +257 -0
  44. data/test/adapter/json_test.rb +34 -0
  45. data/test/adapter/null_test.rb +25 -0
  46. data/test/adapter_test.rb +43 -0
  47. data/test/array_serializer_test.rb +43 -0
  48. data/test/fixtures/poro.rb +213 -0
  49. data/test/serializers/adapter_for_test.rb +50 -0
  50. data/test/serializers/associations_test.rb +127 -0
  51. data/test/serializers/attribute_test.rb +38 -0
  52. data/test/serializers/attributes_test.rb +63 -0
  53. data/test/serializers/cache_test.rb +128 -0
  54. data/test/serializers/configuration_test.rb +15 -0
  55. data/test/serializers/fieldset_test.rb +26 -0
  56. data/test/serializers/generators_test.rb +59 -0
  57. data/test/serializers/meta_test.rb +78 -0
  58. data/test/serializers/options_test.rb +21 -0
  59. data/test/serializers/serializer_for_test.rb +65 -0
  60. data/test/serializers/urls_test.rb +26 -0
  61. data/test/test_helper.rb +38 -0
  62. metadata +195 -0
@@ -0,0 +1,12 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "test"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ t.ruby_opts = ['-r./test/test_helper.rb']
9
+ t.verbose = true
10
+ end
11
+
12
+ task :default => :test
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'active_model/serializer/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "cm-active_model_serializers"
8
+ spec.version = ActiveModel::Serializer::VERSION
9
+ spec.authors = ["Steve Klabnik"]
10
+ spec.email = ["steve@steveklabnik.com"]
11
+ spec.summary = %q{Conventions-based JSON generation for Rails.}
12
+ spec.description = %q{ActiveModel::Serializers allows you to generate your JSON in an object-oriented and convention-driven manner.}
13
+ spec.homepage = "https://github.com/rails-api/active_model_serializers"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "activemodel", ">= 4.0"
22
+
23
+ spec.add_development_dependency "rails", ">= 4.0"
24
+ spec.add_development_dependency "bundler", "~> 1.6"
25
+ spec.add_development_dependency "rake"
26
+ end
@@ -0,0 +1,62 @@
1
+ require 'active_support/core_ext/class/attribute'
2
+
3
+ module ActionController
4
+ module Serialization
5
+ extend ActiveSupport::Concern
6
+
7
+ include ActionController::Renderers
8
+
9
+ ADAPTER_OPTION_KEYS = [:include, :fields, :root, :adapter]
10
+
11
+ included do
12
+ class_attribute :_serialization_scope
13
+ self._serialization_scope = :current_user
14
+ end
15
+
16
+ def serialization_scope
17
+ send(_serialization_scope) if _serialization_scope &&
18
+ respond_to?(_serialization_scope, true)
19
+ end
20
+
21
+ def get_serializer(resource)
22
+ @_serializer ||= @_serializer_opts.delete(:serializer)
23
+ @_serializer ||= ActiveModel::Serializer.serializer_for(resource)
24
+
25
+ if @_serializer_opts.key?(:each_serializer)
26
+ @_serializer_opts[:serializer] = @_serializer_opts.delete(:each_serializer)
27
+ end
28
+
29
+ @_serializer
30
+ end
31
+
32
+ def use_adapter?
33
+ !(@_adapter_opts.key?(:adapter) && !@_adapter_opts[:adapter])
34
+ end
35
+
36
+ [:_render_option_json, :_render_with_renderer_json].each do |renderer_method|
37
+ define_method renderer_method do |resource, options|
38
+ @_adapter_opts, @_serializer_opts =
39
+ options.partition { |k, _| ADAPTER_OPTION_KEYS.include? k }.map { |h| Hash[h] }
40
+
41
+ if use_adapter? && (serializer = get_serializer(resource))
42
+
43
+ @_serializer_opts[:scope] ||= serialization_scope
44
+ @_serializer_opts[:scope_name] = _serialization_scope
45
+
46
+ # omg hax
47
+ object = serializer.new(resource, @_serializer_opts)
48
+ adapter = ActiveModel::Serializer::Adapter.create(object, @_adapter_opts)
49
+ super(adapter, options)
50
+ else
51
+ super(resource, options)
52
+ end
53
+ end
54
+ end
55
+
56
+ module ClassMethods
57
+ def serialization_scope(scope)
58
+ self._serialization_scope = scope
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,261 @@
1
+ require 'thread_safe'
2
+
3
+ module ActiveModel
4
+ class Serializer
5
+ extend ActiveSupport::Autoload
6
+ autoload :Configuration
7
+ autoload :ArraySerializer
8
+ autoload :Adapter
9
+ include Configuration
10
+
11
+ class << self
12
+ attr_accessor :_attributes
13
+ attr_accessor :_attributes_keys
14
+ attr_accessor :_associations
15
+ attr_accessor :_urls
16
+ attr_accessor :_cache
17
+ attr_accessor :_fragmented
18
+ attr_accessor :_cache_key
19
+ attr_accessor :_cache_only
20
+ attr_accessor :_cache_except
21
+ attr_accessor :_cache_options
22
+ end
23
+
24
+ def self.inherited(base)
25
+ base._attributes = self._attributes.try(:dup) || []
26
+ base._attributes_keys = self._attributes_keys.try(:dup) || {}
27
+ base._associations = self._associations.try(:dup) || {}
28
+ base._urls = []
29
+ end
30
+
31
+ def self.attributes(*attrs)
32
+ attrs = attrs.first if attrs.first.class == Array
33
+ @_attributes.concat attrs
34
+ @_attributes.uniq!
35
+
36
+ attrs.each do |attr|
37
+ define_method attr do
38
+ object && object.read_attribute_for_serialization(attr)
39
+ end unless method_defined?(attr) || _fragmented.respond_to?(attr)
40
+ end
41
+ end
42
+
43
+ def self.attribute(attr, options = {})
44
+ key = options.fetch(:key, attr)
45
+ @_attributes_keys[attr] = {key: key} if key != attr
46
+ @_attributes << key unless @_attributes.include?(key)
47
+ define_method key do
48
+ object.read_attribute_for_serialization(attr)
49
+ end unless method_defined?(key) || _fragmented.respond_to?(attr)
50
+ end
51
+
52
+ def self.fragmented(serializer)
53
+ @_fragmented = serializer
54
+ end
55
+
56
+ # Enables a serializer to be automatically cached
57
+ def self.cache(options = {})
58
+ @_cache = ActionController::Base.cache_store if Rails.configuration.action_controller.perform_caching
59
+ @_cache_key = options.delete(:key)
60
+ @_cache_only = options.delete(:only)
61
+ @_cache_except = options.delete(:except)
62
+ @_cache_options = (options.empty?) ? nil : options
63
+ end
64
+
65
+ # Defines an association in the object should be rendered.
66
+ #
67
+ # The serializer object should implement the association name
68
+ # as a method which should return an array when invoked. If a method
69
+ # with the association name does not exist, the association name is
70
+ # dispatched to the serialized object.
71
+ def self.has_many(*attrs)
72
+ associate(:has_many, attrs)
73
+ end
74
+
75
+ # Defines an association in the object that should be rendered.
76
+ #
77
+ # The serializer object should implement the association name
78
+ # as a method which should return an object when invoked. If a method
79
+ # with the association name does not exist, the association name is
80
+ # dispatched to the serialized object.
81
+ def self.belongs_to(*attrs)
82
+ associate(:belongs_to, attrs)
83
+ end
84
+
85
+ # Defines an association in the object should be rendered.
86
+ #
87
+ # The serializer object should implement the association name
88
+ # as a method which should return an object when invoked. If a method
89
+ # with the association name does not exist, the association name is
90
+ # dispatched to the serialized object.
91
+ def self.has_one(*attrs)
92
+ associate(:has_one, attrs)
93
+ end
94
+
95
+ def self.associate(type, attrs) #:nodoc:
96
+ options = attrs.extract_options!
97
+ self._associations = _associations.dup
98
+
99
+ attrs.each do |attr|
100
+ unless method_defined?(attr)
101
+ define_method attr do
102
+ object.send attr
103
+ end
104
+ end
105
+
106
+ self._associations[attr] = {type: type, association_options: options}
107
+ end
108
+ end
109
+
110
+ def self.url(attr)
111
+ @_urls.push attr
112
+ end
113
+
114
+ def self.urls(*attrs)
115
+ @_urls.concat attrs
116
+ end
117
+
118
+ def self.serializer_for(resource, options = {})
119
+ if resource.respond_to?(:serializer_class)
120
+ resource.serializer_class
121
+ elsif resource.respond_to?(:to_ary)
122
+ config.array_serializer
123
+ else
124
+ options
125
+ .fetch(:association_options, {})
126
+ .fetch(:serializer, get_serializer_for(resource.class))
127
+ end
128
+ end
129
+
130
+ def self.adapter
131
+ adapter_class = case config.adapter
132
+ when Symbol
133
+ ActiveModel::Serializer::Adapter.adapter_class(config.adapter)
134
+ when Class
135
+ config.adapter
136
+ end
137
+ unless adapter_class
138
+ valid_adapters = Adapter.constants.map { |klass| ":#{klass.to_s.downcase}" }
139
+ raise ArgumentError, "Unknown adapter: #{config.adapter}. Valid adapters are: #{valid_adapters}"
140
+ end
141
+
142
+ adapter_class
143
+ end
144
+
145
+ def self._root
146
+ @@root ||= false
147
+ end
148
+
149
+ def self._root=(root)
150
+ @@root = root
151
+ end
152
+
153
+ def self.root_name
154
+ name.demodulize.underscore.sub(/_serializer$/, '') if name
155
+ end
156
+
157
+ attr_accessor :object, :root, :meta, :meta_key, :scope
158
+
159
+ def initialize(object, options = {})
160
+ @object = object
161
+ @options = options
162
+ @root = options[:root] || (self.class._root ? self.class.root_name : false)
163
+ @meta = options[:meta]
164
+ @meta_key = options[:meta_key]
165
+ @scope = options[:scope]
166
+
167
+ scope_name = options[:scope_name]
168
+ if scope_name && !respond_to?(scope_name)
169
+ self.class.class_eval do
170
+ define_method scope_name, lambda { scope }
171
+ end
172
+ end
173
+ end
174
+
175
+ def json_key
176
+ if root == true || root.nil?
177
+ self.class.root_name
178
+ else
179
+ root
180
+ end
181
+ end
182
+
183
+ def id
184
+ object.id if object
185
+ end
186
+
187
+ def type
188
+ object.class.to_s.demodulize.underscore.pluralize
189
+ end
190
+
191
+ def attributes(options = {})
192
+ attributes =
193
+ if options[:fields]
194
+ self.class._attributes & options[:fields]
195
+ else
196
+ self.class._attributes.dup
197
+ end
198
+
199
+ attributes += options[:required_fields] if options[:required_fields]
200
+
201
+ attributes.each_with_object({}) do |name, hash|
202
+ unless self.class._fragmented
203
+ hash[name] = send(name)
204
+ else
205
+ hash[name] = self.class._fragmented.public_send(name)
206
+ end
207
+ end
208
+ end
209
+
210
+ def each_association(&block)
211
+ self.class._associations.dup.each do |name, association_options|
212
+ next unless object
213
+ association_value = send(name)
214
+
215
+ serializer_class = ActiveModel::Serializer.serializer_for(association_value, association_options)
216
+
217
+ if serializer_class
218
+ serializer = serializer_class.new(
219
+ association_value,
220
+ options.merge(serializer_from_options(association_options))
221
+ )
222
+ elsif !association_value.nil? && !association_value.instance_of?(Object)
223
+ association_options[:association_options][:virtual_value] = association_value
224
+ end
225
+
226
+ if block_given?
227
+ block.call(name, serializer, association_options[:association_options])
228
+ end
229
+ end
230
+ end
231
+
232
+ def serializer_from_options(options)
233
+ opts = {}
234
+ serializer = options.fetch(:association_options, {}).fetch(:serializer, nil)
235
+ opts[:serializer] = serializer if serializer
236
+ opts
237
+ end
238
+
239
+ def self.serializers_cache
240
+ @serializers_cache ||= ThreadSafe::Cache.new
241
+ end
242
+
243
+ private
244
+
245
+ attr_reader :options
246
+
247
+ def self.get_serializer_for(klass)
248
+ serializers_cache.fetch_or_store(klass) do
249
+ serializer_class_name = "#{klass.name}Serializer"
250
+ serializer_class = serializer_class_name.safe_constantize
251
+
252
+ if serializer_class
253
+ serializer_class
254
+ elsif klass.superclass
255
+ get_serializer_for(klass.superclass)
256
+ end
257
+ end
258
+ end
259
+
260
+ end
261
+ end
@@ -0,0 +1,87 @@
1
+ require 'active_model/serializer/adapter/fragment_cache'
2
+
3
+ module ActiveModel
4
+ class Serializer
5
+ class Adapter
6
+ extend ActiveSupport::Autoload
7
+ autoload :Json
8
+ autoload :Null
9
+ autoload :JsonApi
10
+
11
+ attr_reader :serializer
12
+
13
+ def initialize(serializer, options = {})
14
+ @serializer = serializer
15
+ @options = options
16
+ end
17
+
18
+ def serializable_hash(options = {})
19
+ raise NotImplementedError, 'This is an abstract method. Should be implemented at the concrete adapter.'
20
+ end
21
+
22
+ def as_json(options = {})
23
+ hash = serializable_hash(options)
24
+ include_meta(hash)
25
+ end
26
+
27
+ def self.create(resource, options = {})
28
+ override = options.delete(:adapter)
29
+ klass = override ? adapter_class(override) : ActiveModel::Serializer.adapter
30
+ klass.new(resource, options)
31
+ end
32
+
33
+ def self.adapter_class(adapter)
34
+ "ActiveModel::Serializer::Adapter::#{adapter.to_s.classify}".safe_constantize
35
+ end
36
+
37
+ def fragment_cache(*args)
38
+ raise NotImplementedError, 'This is an abstract method. Should be implemented at the concrete adapter.'
39
+ end
40
+
41
+ private
42
+
43
+ def cache_check(serializer)
44
+ @cached_serializer = serializer
45
+ @klass = @cached_serializer.class
46
+ if is_cached?
47
+ @klass._cache.fetch(cache_key, @klass._cache_options) do
48
+ yield
49
+ end
50
+ elsif is_fragment_cached?
51
+ FragmentCache.new(self, @cached_serializer, @options, @root).fetch
52
+ else
53
+ yield
54
+ end
55
+ end
56
+
57
+ def is_cached?
58
+ @klass._cache && !@klass._cache_only && !@klass._cache_except
59
+ end
60
+
61
+ def is_fragment_cached?
62
+ @klass._cache_only && !@klass._cache_except || !@klass._cache_only && @klass._cache_except
63
+ end
64
+
65
+ def cache_key
66
+ (@klass._cache_key) ? "#{@klass._cache_key}/#{@cached_serializer.object.id}-#{@cached_serializer.object.updated_at}" : @cached_serializer.object.cache_key
67
+ end
68
+
69
+ def meta
70
+ serializer.meta if serializer.respond_to?(:meta)
71
+ end
72
+
73
+ def meta_key
74
+ serializer.meta_key || "meta"
75
+ end
76
+
77
+ def root
78
+ serializer.json_key
79
+ end
80
+
81
+ def include_meta(json)
82
+ json[meta_key] = meta if meta && root
83
+ json
84
+ end
85
+ end
86
+ end
87
+ end