cm-active_model_serializers 0.10.0.rc1.1

Sign up to get free protection for your applications and to get access to all the features.
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