active_model_serializers 0.9.3 → 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 (46) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +107 -0
  3. data/README.md +14 -13
  4. data/lib/action_controller/serialization.rb +9 -1
  5. data/lib/active_model/array_serializer.rb +4 -8
  6. data/lib/active_model/default_serializer.rb +2 -6
  7. data/lib/active_model/serializable.rb +12 -15
  8. data/lib/active_model/serializer.rb +25 -9
  9. data/lib/active_model/serializer/association/has_one.rb +1 -1
  10. data/lib/active_model/serializer/generators/serializer/scaffold_controller_generator.rb +1 -1
  11. data/lib/active_model/serializer/railtie.rb +4 -0
  12. data/lib/active_model/serializer/version.rb +1 -1
  13. data/lib/active_model_serializers/model/caching.rb +25 -0
  14. data/test/benchmark/app.rb +60 -0
  15. data/test/benchmark/benchmarking_support.rb +67 -0
  16. data/test/benchmark/bm_active_record.rb +41 -0
  17. data/test/benchmark/setup.rb +75 -0
  18. data/test/benchmark/tmp/miniprofiler/mp_timers_6eqewtfgrhitvq5gqm25 +0 -0
  19. data/test/benchmark/tmp/miniprofiler/mp_timers_8083sx03hu72pxz1a4d0 +0 -0
  20. data/test/benchmark/tmp/miniprofiler/mp_timers_fyz2gsml4z0ph9kpoy1c +0 -0
  21. data/test/benchmark/tmp/miniprofiler/mp_timers_hjry5rc32imd42oxoi48 +0 -0
  22. data/test/benchmark/tmp/miniprofiler/mp_timers_m8fpoz2cvt3g9agz0bs3 +0 -0
  23. data/test/benchmark/tmp/miniprofiler/mp_timers_p92m2drnj1i568u3sta0 +0 -0
  24. data/test/benchmark/tmp/miniprofiler/mp_timers_qg52tpca3uesdfguee9i +0 -0
  25. data/test/benchmark/tmp/miniprofiler/mp_timers_s15t1a6mvxe0z7vjv790 +0 -0
  26. data/test/benchmark/tmp/miniprofiler/mp_timers_x8kal3d17nfds6vp4kcj +0 -0
  27. data/test/benchmark/tmp/miniprofiler/mp_views_127.0.0.1 +0 -0
  28. data/test/fixtures/active_record.rb +4 -0
  29. data/test/fixtures/poro.rb +46 -3
  30. data/test/integration/action_controller/namespaced_serialization_test.rb +10 -1
  31. data/test/integration/action_controller/serialization_test.rb +5 -5
  32. data/test/integration/active_record/active_record_test.rb +17 -0
  33. data/test/tmp/app/assets/javascripts/accounts.js +2 -0
  34. data/test/tmp/app/assets/stylesheets/accounts.css +4 -0
  35. data/test/tmp/app/controllers/accounts_controller.rb +2 -0
  36. data/test/tmp/app/helpers/accounts_helper.rb +2 -0
  37. data/test/{serializers/tmp → tmp}/app/serializers/account_serializer.rb +0 -0
  38. data/test/tmp/config/routes.rb +1 -0
  39. data/test/unit/active_model/array_serializer/options_test.rb +16 -0
  40. data/test/unit/active_model/array_serializer/serialization_test.rb +17 -0
  41. data/test/unit/active_model/serializer/associations_test.rb +30 -0
  42. data/test/unit/active_model/serializer/has_many_test.rb +1 -1
  43. data/test/unit/active_model/serializer/has_one_test.rb +14 -0
  44. data/test/unit/active_model/serializer/options_test.rb +8 -0
  45. data/test/unit/active_model/serilizable_test.rb +50 -0
  46. metadata +94 -38
@@ -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
@@ -12,6 +12,10 @@ module ActiveModel
12
12
  include app.routes.url_helpers
13
13
  end
14
14
  end
15
+
16
+ config.to_prepare do
17
+ ActiveModel::Serializer.serializers_cache.clear
18
+ end
15
19
  end
16
20
  end
17
21
 
@@ -1,5 +1,5 @@
1
1
  module ActiveModel
2
2
  class Serializer
3
- VERSION = '0.9.3'
3
+ VERSION = '0.9.8'.freeze
4
4
  end
5
5
  end
@@ -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|
@@ -49,7 +49,27 @@ end
49
49
 
50
50
  class SpecialPost < Post
51
51
  def special_comment
52
- @speical_comment ||= Comment.new(content: 'special')
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