active_model_serializers 0.9.0 → 0.9.8

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 (61) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +113 -0
  3. data/README.md +112 -19
  4. data/lib/action_controller/serialization.rb +35 -8
  5. data/lib/action_controller/serialization_test_case.rb +4 -4
  6. data/lib/active_model/array_serializer.rb +13 -10
  7. data/lib/active_model/default_serializer.rb +2 -6
  8. data/lib/active_model/serializable.rb +30 -11
  9. data/lib/active_model/serializable/utils.rb +16 -0
  10. data/lib/active_model/serializer.rb +90 -40
  11. data/lib/active_model/serializer/{associations.rb → association.rb} +8 -52
  12. data/lib/active_model/serializer/association/has_many.rb +39 -0
  13. data/lib/active_model/serializer/association/has_one.rb +25 -0
  14. data/lib/active_model/serializer/generators/serializer/scaffold_controller_generator.rb +1 -1
  15. data/lib/active_model/serializer/railtie.rb +12 -0
  16. data/lib/active_model/serializer/version.rb +1 -1
  17. data/lib/active_model_serializers.rb +1 -1
  18. data/lib/active_model_serializers/model/caching.rb +25 -0
  19. data/test/benchmark/app.rb +60 -0
  20. data/test/benchmark/benchmarking_support.rb +67 -0
  21. data/test/benchmark/bm_active_record.rb +41 -0
  22. data/test/benchmark/setup.rb +75 -0
  23. data/test/benchmark/tmp/miniprofiler/mp_timers_6eqewtfgrhitvq5gqm25 +0 -0
  24. data/test/benchmark/tmp/miniprofiler/mp_timers_8083sx03hu72pxz1a4d0 +0 -0
  25. data/test/benchmark/tmp/miniprofiler/mp_timers_fyz2gsml4z0ph9kpoy1c +0 -0
  26. data/test/benchmark/tmp/miniprofiler/mp_timers_hjry5rc32imd42oxoi48 +0 -0
  27. data/test/benchmark/tmp/miniprofiler/mp_timers_m8fpoz2cvt3g9agz0bs3 +0 -0
  28. data/test/benchmark/tmp/miniprofiler/mp_timers_p92m2drnj1i568u3sta0 +0 -0
  29. data/test/benchmark/tmp/miniprofiler/mp_timers_qg52tpca3uesdfguee9i +0 -0
  30. data/test/benchmark/tmp/miniprofiler/mp_timers_s15t1a6mvxe0z7vjv790 +0 -0
  31. data/test/benchmark/tmp/miniprofiler/mp_timers_x8kal3d17nfds6vp4kcj +0 -0
  32. data/test/benchmark/tmp/miniprofiler/mp_views_127.0.0.1 +0 -0
  33. data/test/fixtures/active_record.rb +4 -0
  34. data/test/fixtures/poro.rb +149 -1
  35. data/test/fixtures/template.html.erb +1 -0
  36. data/test/integration/action_controller/namespaced_serialization_test.rb +105 -0
  37. data/test/integration/action_controller/serialization_test.rb +5 -5
  38. data/test/integration/action_controller/serialization_test_case_test.rb +10 -0
  39. data/test/integration/active_record/active_record_test.rb +19 -2
  40. data/test/test_app.rb +3 -0
  41. data/test/tmp/app/assets/javascripts/accounts.js +2 -0
  42. data/test/tmp/app/assets/stylesheets/accounts.css +4 -0
  43. data/test/tmp/app/controllers/accounts_controller.rb +2 -0
  44. data/test/tmp/app/helpers/accounts_helper.rb +2 -0
  45. data/test/tmp/app/serializers/account_serializer.rb +3 -0
  46. data/test/tmp/config/routes.rb +1 -0
  47. data/test/unit/active_model/array_serializer/options_test.rb +16 -0
  48. data/test/unit/active_model/array_serializer/serialization_test.rb +18 -1
  49. data/test/unit/active_model/serializer/associations/build_serializer_test.rb +15 -0
  50. data/test/unit/active_model/serializer/associations_test.rb +30 -0
  51. data/test/unit/active_model/serializer/attributes_test.rb +16 -0
  52. data/test/unit/active_model/serializer/config_test.rb +3 -0
  53. data/test/unit/active_model/serializer/has_many_polymorphic_test.rb +189 -0
  54. data/test/unit/active_model/serializer/has_many_test.rb +52 -17
  55. data/test/unit/active_model/serializer/has_one_and_has_many_test.rb +27 -0
  56. data/test/unit/active_model/serializer/has_one_polymorphic_test.rb +196 -0
  57. data/test/unit/active_model/serializer/has_one_test.rb +46 -0
  58. data/test/unit/active_model/serializer/options_test.rb +27 -0
  59. data/test/unit/active_model/serializer/url_helpers_test.rb +35 -0
  60. data/test/unit/active_model/serilizable_test.rb +50 -0
  61. metadata +98 -25
@@ -0,0 +1,39 @@
1
+ module ActiveModel
2
+ class Serializer
3
+ class Association
4
+ class HasMany < Association
5
+ def initialize(name, *args)
6
+ super
7
+ @root_key = @embedded_key.to_s
8
+ @key ||= case CONFIG.default_key_type
9
+ when :name then name.to_s.pluralize
10
+ else "#{name.to_s.singularize}_ids"
11
+ end
12
+ end
13
+
14
+ def serializer_class(object, _)
15
+ if use_array_serializer?
16
+ ArraySerializer
17
+ else
18
+ serializer_from_options
19
+ end
20
+ end
21
+
22
+ def options
23
+ if use_array_serializer?
24
+ { each_serializer: serializer_from_options }.merge! super
25
+ else
26
+ super
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def use_array_serializer?
33
+ !serializer_from_options ||
34
+ serializer_from_options && !(serializer_from_options <= ArraySerializer)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,25 @@
1
+ module ActiveModel
2
+ class Serializer
3
+ class Association
4
+ class HasOne < Association
5
+ def initialize(name, *args)
6
+ super
7
+ @root_key = @embedded_key.to_s.pluralize
8
+ @key ||= case CONFIG.default_key_type
9
+ when :name then name.to_s.singularize
10
+ else "#{name}_id"
11
+ end
12
+ end
13
+
14
+ def serializer_class(object, options = {})
15
+ (serializer_from_options unless object.nil?) || serializer_from_object(object, options) || default_serializer
16
+ end
17
+
18
+ def build_serializer(object, options = {})
19
+ options[:_wrap_in_array] = embed_in_root?
20
+ super
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -7,7 +7,7 @@ module Rails
7
7
  if Rails::VERSION::MAJOR >= 4
8
8
  source_root File.expand_path('../templates', __FILE__)
9
9
 
10
- hook_for :serializer, default: true
10
+ hook_for :serializer, default: true, type: :boolean
11
11
  end
12
12
  end
13
13
  end
@@ -6,5 +6,17 @@ module ActiveModel
6
6
  require 'active_model/serializer/generators/serializer/scaffold_controller_generator'
7
7
  require 'active_model/serializer/generators/resource_override'
8
8
  end
9
+
10
+ initializer 'include_routes.active_model_serializer' do |app|
11
+ ActiveSupport.on_load(:active_model_serializers) do
12
+ include app.routes.url_helpers
13
+ end
14
+ end
15
+
16
+ config.to_prepare do
17
+ ActiveModel::Serializer.serializers_cache.clear
18
+ end
9
19
  end
10
20
  end
21
+
22
+ ActiveSupport.run_load_hooks(:active_model_serializers, ActiveModel::Serializer)
@@ -1,5 +1,5 @@
1
1
  module ActiveModel
2
2
  class Serializer
3
- VERSION = '0.9.0'
3
+ VERSION = '0.9.8'.freeze
4
4
  end
5
5
  end
@@ -9,7 +9,7 @@ begin
9
9
  require 'action_controller/serialization'
10
10
  require 'action_controller/serialization_test_case'
11
11
 
12
- ActiveSupport.on_load(:after_initialize) do
12
+ ActiveSupport.on_load(:action_controller) do
13
13
  if ::ActionController::Serialization.enabled
14
14
  ActionController::Base.send(:include, ::ActionController::Serialization)
15
15
  ActionController::TestCase.send(:include, ::ActionController::SerializationAssertions)
@@ -0,0 +1,25 @@
1
+ module ActiveModelSerializers
2
+ class Model
3
+ module Caching
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ attr_writer :updated_at
8
+ attributes :id
9
+ end
10
+
11
+ # Defaults to the downcased model name and updated_at
12
+ def cache_key
13
+ ActiveSupport::Cache.expand_cache_key([
14
+ self.class.model_name.name.downcase,
15
+ "#{id}-#{updated_at.strftime('%Y%m%d%H%M%S%9N')}"
16
+ ].compact)
17
+ end
18
+
19
+ # Defaults to the time the serializer file was modified.
20
+ def updated_at
21
+ defined?(@updated_at) ? @updated_at : File.mtime(__FILE__)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,60 @@
1
+ # https://github.com/rails-api/active_model_serializers/pull/872
2
+ # approx ref 792fb8a9053f8db3c562dae4f40907a582dd1720 to test against
3
+ require 'bundler/setup'
4
+
5
+ require 'rails'
6
+ require 'active_model'
7
+ require 'active_support'
8
+ require 'active_support/json'
9
+ require 'action_controller'
10
+ require 'action_controller/test_case'
11
+ require 'action_controller/railtie'
12
+ abort "Rails application already defined: #{Rails.application.class}" if Rails.application
13
+
14
+ class NullLogger < Logger
15
+ def initialize(*_args)
16
+ end
17
+
18
+ def add(*_args, &_block)
19
+ end
20
+ end
21
+ class BenchmarkLogger < ActiveSupport::Logger
22
+ def initialize
23
+ @file = StringIO.new
24
+ super(@file)
25
+ end
26
+
27
+ def messages
28
+ @file.rewind
29
+ @file.read
30
+ end
31
+ end
32
+ # ref: https://gist.github.com/bf4/8744473
33
+ class BenchmarkApp < Rails::Application
34
+ # Set up production configuration
35
+ config.eager_load = true
36
+ config.cache_classes = true
37
+ # CONFIG: CACHE_ON={on,off}
38
+ config.action_controller.perform_caching = ENV['CACHE_ON'] != 'off'
39
+ config.action_controller.cache_store = ActiveSupport::Cache.lookup_store(:memory_store)
40
+
41
+ config.active_support.test_order = :random
42
+ config.secret_token = 'S' * 30
43
+ config.secret_key_base = 'abc123'
44
+ config.consider_all_requests_local = false
45
+
46
+ # otherwise deadlock occurred
47
+ config.middleware.delete 'Rack::Lock'
48
+
49
+ # to disable log files
50
+ config.logger = NullLogger.new
51
+ config.active_support.deprecation = :log
52
+ config.log_level = :info
53
+ end
54
+
55
+ require 'active_model_serializers'
56
+
57
+ # Initialize app before any serializers are defined, for running across revisions.
58
+ # ref: https://github.com/rails-api/active_model_serializers/pull/1478
59
+ Rails.application.initialize!
60
+
@@ -0,0 +1,67 @@
1
+ require 'benchmark/ips'
2
+ require 'json'
3
+
4
+ # Add benchmarking runner from ruby-bench-suite
5
+ # https://github.com/ruby-bench/ruby-bench-suite/blob/master/rails/benchmarks/support/benchmark_rails.rb
6
+ module Benchmark
7
+ module ActiveModelSerializers
8
+ module TestMethods
9
+ def request(method, path)
10
+ response = Rack::MockRequest.new(BenchmarkApp).send(method, path)
11
+ if response.status.in?([404, 500])
12
+ fail "omg, #{method}, #{path}, '#{response.status}', '#{response.body}'"
13
+ end
14
+ response
15
+ end
16
+ end
17
+
18
+ # extend Benchmark with an `ams` method
19
+ def ams(label = nil, time:, disable_gc: true, warmup: 3, &block)
20
+ fail ArgumentError.new, 'block should be passed' unless block_given?
21
+
22
+ if disable_gc
23
+ GC.disable
24
+ else
25
+ GC.enable
26
+ end
27
+
28
+ report = Benchmark.ips(time, warmup, true) do |x|
29
+ x.report(label) { yield }
30
+ end
31
+
32
+ entry = report.entries.first
33
+
34
+ output = {
35
+ label: label,
36
+ version: ::ActiveModel::Serializer::VERSION.to_s,
37
+ rails_version: ::Rails.version.to_s,
38
+ iterations_per_second: entry.ips,
39
+ iterations_per_second_standard_deviation: entry.error_percentage,
40
+ total_allocated_objects_per_iteration: count_total_allocated_objects(&block)
41
+ }.to_json
42
+
43
+ puts output
44
+ output
45
+ end
46
+
47
+ def count_total_allocated_objects
48
+ if block_given?
49
+ key =
50
+ if RUBY_VERSION < '2.2'
51
+ :total_allocated_object
52
+ else
53
+ :total_allocated_objects
54
+ end
55
+
56
+ before = GC.stat[key]
57
+ yield
58
+ after = GC.stat[key]
59
+ after - before
60
+ else
61
+ -1
62
+ end
63
+ end
64
+ end
65
+
66
+ extend Benchmark::ActiveModelSerializers
67
+ end
@@ -0,0 +1,41 @@
1
+ require_relative './benchmarking_support'
2
+ require_relative './app'
3
+ require_relative './setup'
4
+
5
+ time = 10
6
+ disable_gc = true
7
+
8
+
9
+
10
+ authors_query = Author.preload(:posts).preload(:profile)
11
+ author = authors_query.first
12
+ authors = authors_query.to_a
13
+
14
+
15
+ Benchmark.ams('Single: DefaultSerializer', time: time, disable_gc: disable_gc) do
16
+ ActiveModel::DefaultSerializer.new(author).to_json
17
+ end
18
+
19
+ Benchmark.ams('ArraySerializer', time: time, disable_gc: disable_gc) do
20
+ ActiveModel::ArraySerializer.new(authors).to_json
21
+ end
22
+
23
+ Benchmark.ams('ArraySerializer: each_serializer: DefaultSerializer', time: time, disable_gc: disable_gc) do
24
+ ActiveModel::ArraySerializer.new(authors, each_serializer:ActiveModel::DefaultSerializer).to_json
25
+ end
26
+
27
+ Benchmark.ams('FlatAuthorSerializer', time: time, disable_gc: disable_gc) do
28
+ FlatAuthorSerializer.new(author).to_json
29
+ end
30
+
31
+ Benchmark.ams('ArraySerializer: each_serializer: FlatAuthorSerializer', time: time, disable_gc: disable_gc) do
32
+ ActiveModel::ArraySerializer.new(authors, each_serializer: FlatAuthorSerializer).to_json
33
+ end
34
+
35
+ Benchmark.ams('AuthorWithDefaultRelationshipsSerializer', time: time, disable_gc: disable_gc) do
36
+ AuthorWithDefaultRelationshipsSerializer.new(author).to_json
37
+ end
38
+
39
+ Benchmark.ams('ArraySerializer: each_serializer: AuthorWithDefaultRelationshipsSerializer', time: time, disable_gc: disable_gc) do
40
+ ActiveModel::ArraySerializer.new(authors, each_serializer: AuthorWithDefaultRelationshipsSerializer).to_json
41
+ end
@@ -0,0 +1,75 @@
1
+ ###########################################
2
+ # Setup active record models
3
+ ##########################################
4
+ require 'active_record'
5
+ require 'sqlite3'
6
+
7
+
8
+ # Change the following to reflect your database settings
9
+ ActiveRecord::Base.establish_connection(
10
+ adapter: 'sqlite3',
11
+ database: ':memory:'
12
+ )
13
+
14
+ # Don't show migration output when constructing fake db
15
+ ActiveRecord::Migration.verbose = false
16
+
17
+ ActiveRecord::Schema.define do
18
+ create_table :authors, force: true do |t|
19
+ t.string :name
20
+ end
21
+
22
+ create_table :posts, force: true do |t|
23
+ t.text :body
24
+ t.string :title
25
+ t.references :author
26
+ end
27
+
28
+ create_table :profiles, force: true do |t|
29
+ t.text :project_url
30
+ t.text :bio
31
+ t.date :birthday
32
+ t.references :author
33
+ end
34
+ end
35
+
36
+ class Author < ActiveRecord::Base
37
+ has_one :profile
38
+ has_many :posts
39
+ end
40
+
41
+ class Post < ActiveRecord::Base
42
+ belongs_to :author
43
+ end
44
+
45
+ class Profile < ActiveRecord::Base
46
+ belongs_to :author
47
+ end
48
+
49
+ # Build out the data to serialize
50
+ author = Author.create(name: 'Preston Sego')
51
+ Profile.create(project_url: 'https://github.com/NullVoxPopuli', author: author)
52
+ 50.times do
53
+ Post.create(
54
+ body: 'something about how password restrictions are evil, and less secure, and with the math to prove it.',
55
+ title: 'Your bank is does not know how to do security',
56
+ author: author
57
+ )
58
+ end
59
+
60
+ ActiveModel::Serializer.root = false
61
+ ActiveModel::ArraySerializer.root = false
62
+
63
+ class FlatAuthorSerializer < ActiveModel::Serializer
64
+ attributes :id, :name
65
+ end
66
+
67
+ class AuthorWithDefaultRelationshipsSerializer < ActiveModel::Serializer
68
+ attributes :id, :name
69
+
70
+ has_one :profile
71
+ has_many :posts
72
+ end
73
+
74
+ # For debugging SQL output
75
+ #ActiveRecord::Base.logger = Logger.new(STDERR)
@@ -74,6 +74,10 @@ class ARSectionSerializer < ActiveModel::Serializer
74
74
  attributes 'name'
75
75
  end
76
76
 
77
+ class AREmbeddedSerializer < ActiveModel::Serializer
78
+ has_many :ar_tags, :ar_comments
79
+ end
80
+
77
81
  ARPost.create(title: 'New post',
78
82
  body: 'A body!!!',
79
83
  ar_section: ARSection.create(name: 'ruby')).tap do |post|
@@ -1,11 +1,13 @@
1
1
  class Model
2
- def initialize(hash={})
2
+ def initialize(hash = {})
3
3
  @attributes = hash
4
4
  end
5
5
 
6
6
  def read_attribute_for_serialization(name)
7
7
  if name == :id || name == 'id'
8
8
  object_id
9
+ elsif respond_to?(name)
10
+ send name
9
11
  else
10
12
  @attributes[name]
11
13
  end
@@ -22,9 +24,22 @@ class User < Model
22
24
  end
23
25
  end
24
26
 
27
+ class UserInfo < Model
28
+ def user
29
+ @user ||= User.new(name: 'N1', email: 'E1')
30
+ end
31
+ end
32
+
25
33
  class Profile < Model
26
34
  end
27
35
 
36
+ class Category < Model
37
+ def posts
38
+ @posts ||= [Post.new(title: 'T1', body: 'B1'),
39
+ Post.new(title: 'T2', body: 'B2')]
40
+ end
41
+ end
42
+
28
43
  class Post < Model
29
44
  def comments
30
45
  @comments ||= [Comment.new(content: 'C1'),
@@ -32,12 +47,57 @@ class Post < Model
32
47
  end
33
48
  end
34
49
 
50
+ class SpecialPost < Post
51
+ def special_comment
52
+ @special_comment ||= Comment.new(content: 'special')
53
+ end
54
+ end
55
+
56
+ class Type < Model
57
+ end
58
+
59
+ class SelfReferencingUser < Model
60
+ def type
61
+ @type ||= Type.new(name: 'N1')
62
+ end
63
+ def parent
64
+ @parent ||= SelfReferencingUserParent.new(name: 'N1')
65
+ end
66
+ end
67
+
68
+ class SelfReferencingUserParent < Model
69
+ def type
70
+ @type ||= Type.new(name: 'N2')
71
+ end
72
+ def parent
73
+ end
74
+ end
75
+
35
76
  class Comment < Model
36
77
  end
37
78
 
38
79
  class WebLog < Model
39
80
  end
40
81
 
82
+ class Interview < Model
83
+ def attachment
84
+ @attachment ||= Image.new(url: 'U1')
85
+ end
86
+ end
87
+
88
+ class Mail < Model
89
+ def attachments
90
+ @attachments ||= [Image.new(url: 'U1'),
91
+ Video.new(html: 'H1')]
92
+ end
93
+ end
94
+
95
+ class Image < Model
96
+ end
97
+
98
+ class Video < Model
99
+ end
100
+
41
101
  ###
42
102
  ## Serializers
43
103
  ###
@@ -47,6 +107,26 @@ class UserSerializer < ActiveModel::Serializer
47
107
  has_one :profile
48
108
  end
49
109
 
110
+ class TypeSerializer < ActiveModel::Serializer
111
+ attributes :name
112
+ end
113
+
114
+ class SelfReferencingUserParentSerializer < ActiveModel::Serializer
115
+ attributes :name
116
+ has_one :type, serializer: TypeSerializer, embed: :ids, include: true
117
+ end
118
+
119
+ class SelfReferencingUserSerializer < ActiveModel::Serializer
120
+ attributes :name
121
+
122
+ has_one :type, serializer: TypeSerializer, embed: :ids, include: true
123
+ has_one :parent, serializer: SelfReferencingUserSerializer, embed: :ids, include: true
124
+ end
125
+
126
+ class UserInfoSerializer < ActiveModel::Serializer
127
+ has_one :user, serializer: UserSerializer
128
+ end
129
+
50
130
  class ProfileSerializer < ActiveModel::Serializer
51
131
  def description
52
132
  description = object.read_attribute_for_serialization(:description)
@@ -56,12 +136,31 @@ class ProfileSerializer < ActiveModel::Serializer
56
136
  attributes :name, :description
57
137
  end
58
138
 
139
+ class CategorySerializer < ActiveModel::Serializer
140
+ attributes :name
141
+
142
+ has_many :posts
143
+ end
144
+
59
145
  class PostSerializer < ActiveModel::Serializer
60
146
  attributes :title, :body
61
147
 
148
+ def title
149
+ keyword = serialization_options[:highlight_keyword]
150
+ title = object.read_attribute_for_serialization(:title)
151
+ title = title.gsub(keyword,"'#{keyword}'") if keyword
152
+ title
153
+ end
154
+
62
155
  has_many :comments
63
156
  end
64
157
 
158
+ class SpecialPostSerializer < ActiveModel::Serializer
159
+ attributes :title, :body
160
+ has_many :comments, root: :comments, embed_in_root: true, embed: :ids
161
+ has_one :special_comment, root: :comments, embed_in_root: true, embed: :ids
162
+ end
163
+
65
164
  class CommentSerializer < ActiveModel::Serializer
66
165
  attributes :content
67
166
  end
@@ -73,3 +172,52 @@ end
73
172
  class WebLogLowerCamelSerializer < WebLogSerializer
74
173
  format_keys :lower_camel
75
174
  end
175
+
176
+ class InterviewSerializer < ActiveModel::Serializer
177
+ attributes :text
178
+
179
+ has_one :attachment, polymorphic: true
180
+ end
181
+
182
+ class MailSerializer < ActiveModel::Serializer
183
+ attributes :body
184
+
185
+ has_many :attachments, polymorphic: true
186
+ end
187
+
188
+ class ImageSerializer < ActiveModel::Serializer
189
+ attributes :url
190
+ end
191
+
192
+ class VideoSerializer < ActiveModel::Serializer
193
+ attributes :html
194
+ end
195
+
196
+ class ShortProfileSerializer < ::ProfileSerializer; end
197
+
198
+ module TestNamespace
199
+ class ProfileSerializer < ::ProfileSerializer; end
200
+ class UserSerializer < ::UserSerializer; end
201
+ end
202
+
203
+ ActiveModel::Serializer.setup do |config|
204
+ config.default_key_type = :name
205
+ end
206
+
207
+ class NameKeyUserSerializer < ActiveModel::Serializer
208
+ attributes :name, :email
209
+
210
+ has_one :profile
211
+ end
212
+
213
+ class NameKeyPostSerializer < ActiveModel::Serializer
214
+ attributes :title, :body
215
+
216
+ has_many :comments
217
+ end
218
+
219
+ ActiveModel::Serializer.setup do |config|
220
+ config.default_key_type = nil
221
+ end
222
+
223
+