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.
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