active_model_serializers 0.9.0 → 0.9.8

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