active_model_serializers 0.9.3 → 0.9.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +107 -0
- data/README.md +14 -13
- data/lib/action_controller/serialization.rb +9 -1
- data/lib/active_model/array_serializer.rb +4 -8
- data/lib/active_model/default_serializer.rb +2 -6
- data/lib/active_model/serializable.rb +12 -15
- data/lib/active_model/serializer.rb +25 -9
- data/lib/active_model/serializer/association/has_one.rb +1 -1
- data/lib/active_model/serializer/generators/serializer/scaffold_controller_generator.rb +1 -1
- data/lib/active_model/serializer/railtie.rb +4 -0
- data/lib/active_model/serializer/version.rb +1 -1
- data/lib/active_model_serializers/model/caching.rb +25 -0
- data/test/benchmark/app.rb +60 -0
- data/test/benchmark/benchmarking_support.rb +67 -0
- data/test/benchmark/bm_active_record.rb +41 -0
- data/test/benchmark/setup.rb +75 -0
- data/test/benchmark/tmp/miniprofiler/mp_timers_6eqewtfgrhitvq5gqm25 +0 -0
- data/test/benchmark/tmp/miniprofiler/mp_timers_8083sx03hu72pxz1a4d0 +0 -0
- data/test/benchmark/tmp/miniprofiler/mp_timers_fyz2gsml4z0ph9kpoy1c +0 -0
- data/test/benchmark/tmp/miniprofiler/mp_timers_hjry5rc32imd42oxoi48 +0 -0
- data/test/benchmark/tmp/miniprofiler/mp_timers_m8fpoz2cvt3g9agz0bs3 +0 -0
- data/test/benchmark/tmp/miniprofiler/mp_timers_p92m2drnj1i568u3sta0 +0 -0
- data/test/benchmark/tmp/miniprofiler/mp_timers_qg52tpca3uesdfguee9i +0 -0
- data/test/benchmark/tmp/miniprofiler/mp_timers_s15t1a6mvxe0z7vjv790 +0 -0
- data/test/benchmark/tmp/miniprofiler/mp_timers_x8kal3d17nfds6vp4kcj +0 -0
- data/test/benchmark/tmp/miniprofiler/mp_views_127.0.0.1 +0 -0
- data/test/fixtures/active_record.rb +4 -0
- data/test/fixtures/poro.rb +46 -3
- data/test/integration/action_controller/namespaced_serialization_test.rb +10 -1
- data/test/integration/action_controller/serialization_test.rb +5 -5
- data/test/integration/active_record/active_record_test.rb +17 -0
- data/test/tmp/app/assets/javascripts/accounts.js +2 -0
- data/test/tmp/app/assets/stylesheets/accounts.css +4 -0
- data/test/tmp/app/controllers/accounts_controller.rb +2 -0
- data/test/tmp/app/helpers/accounts_helper.rb +2 -0
- data/test/{serializers/tmp → tmp}/app/serializers/account_serializer.rb +0 -0
- data/test/tmp/config/routes.rb +1 -0
- data/test/unit/active_model/array_serializer/options_test.rb +16 -0
- data/test/unit/active_model/array_serializer/serialization_test.rb +17 -0
- data/test/unit/active_model/serializer/associations_test.rb +30 -0
- data/test/unit/active_model/serializer/has_many_test.rb +1 -1
- data/test/unit/active_model/serializer/has_one_test.rb +14 -0
- data/test/unit/active_model/serializer/options_test.rb +8 -0
- data/test/unit/active_model/serilizable_test.rb +50 -0
- metadata +94 -38
@@ -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)
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -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|
|
data/test/fixtures/poro.rb
CHANGED
@@ -49,7 +49,27 @@ end
|
|
49
49
|
|
50
50
|
class SpecialPost < Post
|
51
51
|
def special_comment
|
52
|
-
@
|
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
|
53
73
|
end
|
54
74
|
end
|
55
75
|
|
@@ -87,8 +107,24 @@ class UserSerializer < ActiveModel::Serializer
|
|
87
107
|
has_one :profile
|
88
108
|
end
|
89
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
|
+
|
90
126
|
class UserInfoSerializer < ActiveModel::Serializer
|
91
|
-
has_one :user
|
127
|
+
has_one :user, serializer: UserSerializer
|
92
128
|
end
|
93
129
|
|
94
130
|
class ProfileSerializer < ActiveModel::Serializer
|
@@ -109,6 +145,13 @@ end
|
|
109
145
|
class PostSerializer < ActiveModel::Serializer
|
110
146
|
attributes :title, :body
|
111
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
|
+
|
112
155
|
has_many :comments
|
113
156
|
end
|
114
157
|
|
@@ -169,7 +212,7 @@ end
|
|
169
212
|
|
170
213
|
class NameKeyPostSerializer < ActiveModel::Serializer
|
171
214
|
attributes :title, :body
|
172
|
-
|
215
|
+
|
173
216
|
has_many :comments
|
174
217
|
end
|
175
218
|
|
@@ -19,6 +19,10 @@ module ActionController
|
|
19
19
|
def render_comments
|
20
20
|
render json: [Comment.new(content: 'Comment 1')]
|
21
21
|
end
|
22
|
+
|
23
|
+
def render_hash
|
24
|
+
render json: {message: 'not found'}, status: 404
|
25
|
+
end
|
22
26
|
end
|
23
27
|
|
24
28
|
tests TestNamespace::MyController
|
@@ -42,6 +46,11 @@ module ActionController
|
|
42
46
|
get :render_comments
|
43
47
|
assert_serializer CommentSerializer
|
44
48
|
end
|
49
|
+
|
50
|
+
def test_render_hash_regression
|
51
|
+
get :render_hash
|
52
|
+
assert_equal JSON.parse(response.body), {'message' => 'not found'}
|
53
|
+
end
|
45
54
|
end
|
46
55
|
|
47
56
|
class OptionNamespacedSerializationTest < ActionController::TestCase
|
@@ -93,4 +102,4 @@ module ActionController
|
|
93
102
|
end
|
94
103
|
|
95
104
|
end
|
96
|
-
end
|
105
|
+
end
|
@@ -213,8 +213,8 @@ module ActionController
|
|
213
213
|
class LowerCamelWoRootSerializerTest < ActionController::TestCase
|
214
214
|
class WebLogController < ActionController::Base
|
215
215
|
def render_without_root
|
216
|
-
render json: WebLog.new({name: 'Name 1', display_name: 'Display Name 1'}),
|
217
|
-
root: false,
|
216
|
+
render json: WebLog.new({name: 'Name 1', display_name: 'Display Name 1'}),
|
217
|
+
root: false,
|
218
218
|
serializer: WebLogLowerCamelSerializer
|
219
219
|
end
|
220
220
|
end
|
@@ -231,9 +231,9 @@ module ActionController
|
|
231
231
|
class LowerCamelArrayWoRootSerializerTest < ActionController::TestCase
|
232
232
|
class WebLogController < ActionController::Base
|
233
233
|
def render_array_without_root
|
234
|
-
render json: [WebLog.new({name: 'Name 1', display_name: 'Display Name 1'}),
|
235
|
-
WebLog.new({name: 'Name 2', display_name: 'Display Name 2'})],
|
236
|
-
root: false,
|
234
|
+
render json: [WebLog.new({name: 'Name 1', display_name: 'Display Name 1'}),
|
235
|
+
WebLog.new({name: 'Name 2', display_name: 'Display Name 2'})],
|
236
|
+
root: false,
|
237
237
|
each_serializer: WebLogLowerCamelSerializer
|
238
238
|
end
|
239
239
|
end
|