active_loaders 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CHANGES.md +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +464 -0
- data/Rakefile +2 -0
- data/active_loaders.gemspec +30 -0
- data/lib/active_loaders.rb +6 -0
- data/lib/active_loaders/datasource_adapter.rb +216 -0
- data/lib/active_loaders/test.rb +100 -0
- data/lib/active_loaders/version.rb +3 -0
- data/spec/sequel_serializer_spec.rb +65 -0
- data/spec/sequel_skip_select_spec.rb +77 -0
- data/spec/serializer_spec.rb +62 -0
- data/spec/skip_select_spec.rb +74 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/support/db.rb +53 -0
- data/spec/test_methods_spec.rb +57 -0
- metadata +211 -0
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'active_loaders/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "active_loaders"
|
8
|
+
spec.version = ActiveLoaders::VERSION
|
9
|
+
spec.authors = ["Jan Berdajs"]
|
10
|
+
spec.email = ["mrbrdo@gmail.com"]
|
11
|
+
spec.summary = %q{Ruby library to automatically preload data for your Active Model Serializers}
|
12
|
+
spec.homepage = "https://github.com/kundi/active_loaders"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_dependency 'active_model_serializers', '~> 0.9'
|
21
|
+
spec.add_dependency 'datasource', '~> 0.3'
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
spec.add_development_dependency "rspec", "~> 3.2"
|
25
|
+
spec.add_development_dependency 'sqlite3', '~> 1.3'
|
26
|
+
spec.add_development_dependency 'activerecord', '~> 4'
|
27
|
+
spec.add_development_dependency 'pry', '~> 0.9'
|
28
|
+
spec.add_development_dependency 'sequel', '~> 4.17'
|
29
|
+
spec.add_development_dependency 'database_cleaner', '~> 1.3'
|
30
|
+
end
|
@@ -0,0 +1,216 @@
|
|
1
|
+
require "active_model/serializer"
|
2
|
+
require "datasource"
|
3
|
+
|
4
|
+
module ActiveLoaders
|
5
|
+
module Adapters
|
6
|
+
module ActiveModelSerializers
|
7
|
+
module ArraySerializer
|
8
|
+
def initialize_with_loaders(objects, options = {})
|
9
|
+
datasource_class = options.delete(:datasource)
|
10
|
+
adapter = Datasource.orm_adapters.find { |a| a.is_scope?(objects) }
|
11
|
+
if adapter && !adapter.scope_loaded?(objects)
|
12
|
+
scope = begin
|
13
|
+
objects
|
14
|
+
.for_serializer(options[:serializer])
|
15
|
+
.datasource_params(*[options[:loader_params]].compact)
|
16
|
+
rescue NameError
|
17
|
+
if options[:serializer].nil?
|
18
|
+
return initialize_without_loaders(objects, options)
|
19
|
+
else
|
20
|
+
raise
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
if datasource_class
|
25
|
+
scope = scope.with_datasource(datasource_class)
|
26
|
+
end
|
27
|
+
|
28
|
+
records = adapter.scope_to_records(scope)
|
29
|
+
|
30
|
+
# if we are loading an association proxy, we should set the target
|
31
|
+
# especially because AMS will resolve it twice, which would do 2 queries
|
32
|
+
if objects.respond_to?(:proxy_association)
|
33
|
+
objects.proxy_association.target = records
|
34
|
+
end
|
35
|
+
|
36
|
+
initialize_without_loaders(records, options)
|
37
|
+
else
|
38
|
+
initialize_without_loaders(objects, options)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module_function
|
44
|
+
def get_serializer_for(klass, serializer_assoc = nil)
|
45
|
+
serializer = if serializer_assoc
|
46
|
+
if serializer_assoc.kind_of?(Hash)
|
47
|
+
serializer_assoc[:options].try(:[], :serializer)
|
48
|
+
else
|
49
|
+
serializer_assoc.options[:serializer]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
serializer || "#{klass.name}Serializer".constantize
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_datasource_select(result, klass, serializer = nil, serializer_assoc = nil, adapter = nil, datasource = nil)
|
56
|
+
adapter ||= Datasource::Base.default_adapter
|
57
|
+
serializer ||= get_serializer_for(klass, serializer_assoc)
|
58
|
+
if serializer._attributes.respond_to?(:keys) # AMS 0.8
|
59
|
+
result.concat(serializer._attributes.keys)
|
60
|
+
else # AMS 0.9
|
61
|
+
result.concat(serializer._attributes)
|
62
|
+
end
|
63
|
+
result.concat(serializer.loaders_context.select)
|
64
|
+
if serializer.loaders_context.skip_select.empty?
|
65
|
+
result.unshift("*")
|
66
|
+
else
|
67
|
+
datasource_class = if datasource
|
68
|
+
datasource.class
|
69
|
+
else
|
70
|
+
serializer.use_datasource || klass.default_datasource
|
71
|
+
end
|
72
|
+
result.concat(datasource_class._column_attribute_names -
|
73
|
+
serializer.loaders_context.skip_select.map(&:to_s))
|
74
|
+
end
|
75
|
+
result_assocs = serializer.loaders_context.includes.dup
|
76
|
+
result.push(result_assocs)
|
77
|
+
|
78
|
+
serializer._associations.each_pair do |name, serializer_assoc|
|
79
|
+
# TODO: what if assoc is renamed in serializer?
|
80
|
+
reflection = adapter.association_reflection(klass, name.to_sym)
|
81
|
+
assoc_class = reflection[:klass]
|
82
|
+
|
83
|
+
name = name.to_s
|
84
|
+
result_assocs[name] = []
|
85
|
+
to_datasource_select(result_assocs[name], assoc_class, nil, serializer_assoc, adapter)
|
86
|
+
end
|
87
|
+
rescue Exception => ex
|
88
|
+
if ex.is_a?(SystemStackError) || ex.is_a?(Datasource::RecursionError)
|
89
|
+
fail Datasource::RecursionError, "recursive association (involving #{klass.name})"
|
90
|
+
else
|
91
|
+
raise
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
module SerializerClassMethods
|
99
|
+
class SerializerDatasourceContext
|
100
|
+
def initialize(serializer)
|
101
|
+
@serializer = serializer
|
102
|
+
end
|
103
|
+
|
104
|
+
def select(*args)
|
105
|
+
@datasource_select ||= []
|
106
|
+
@datasource_select.concat(args)
|
107
|
+
|
108
|
+
@datasource_select
|
109
|
+
end
|
110
|
+
|
111
|
+
def skip_select(*args)
|
112
|
+
@datasource_skip_select ||= []
|
113
|
+
@datasource_skip_select.concat(args)
|
114
|
+
|
115
|
+
@datasource_skip_select
|
116
|
+
end
|
117
|
+
|
118
|
+
def includes(*args)
|
119
|
+
@datasource_includes ||= {}
|
120
|
+
|
121
|
+
args.each do |arg|
|
122
|
+
@datasource_includes.deep_merge!(datasource_includes_to_select(arg))
|
123
|
+
end
|
124
|
+
|
125
|
+
@datasource_includes
|
126
|
+
end
|
127
|
+
|
128
|
+
def use_datasource(*args)
|
129
|
+
@serializer.use_datasource(*args)
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
def datasource_includes_to_select(arg)
|
134
|
+
if arg.kind_of?(Hash)
|
135
|
+
arg.keys.inject({}) do |memo, key|
|
136
|
+
memo[key.to_sym] = ["*", datasource_includes_to_select(arg[key])]
|
137
|
+
memo
|
138
|
+
end
|
139
|
+
elsif arg.kind_of?(Array)
|
140
|
+
arg.inject({}) do |memo, element|
|
141
|
+
memo.deep_merge!(datasource_includes_to_select(element))
|
142
|
+
end
|
143
|
+
elsif arg.respond_to?(:to_sym)
|
144
|
+
{ arg.to_sym => ["*"] }
|
145
|
+
else
|
146
|
+
fail Datasource::Error, "unknown includes value type #{arg.class}"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def inherited(base)
|
152
|
+
select_values = loaders_context.select.deep_dup
|
153
|
+
skip_select_values = loaders_context.skip_select.deep_dup
|
154
|
+
includes_values = loaders_context.includes.deep_dup
|
155
|
+
base.loaders do
|
156
|
+
select(*select_values)
|
157
|
+
skip_select(*skip_select_values)
|
158
|
+
includes(*includes_values)
|
159
|
+
end
|
160
|
+
base.use_datasource(use_datasource)
|
161
|
+
|
162
|
+
super
|
163
|
+
end
|
164
|
+
|
165
|
+
def loaders_context
|
166
|
+
@loaders_context ||= SerializerDatasourceContext.new(self)
|
167
|
+
end
|
168
|
+
|
169
|
+
def loaders(&block)
|
170
|
+
loaders_context.instance_eval(&block)
|
171
|
+
end
|
172
|
+
|
173
|
+
# required by datasource gem
|
174
|
+
def datasource_adapter
|
175
|
+
ActiveLoaders::Adapters::ActiveModelSerializers
|
176
|
+
end
|
177
|
+
|
178
|
+
# required by datasource gem
|
179
|
+
def use_datasource(*args)
|
180
|
+
@use_datasource = args.first unless args.empty?
|
181
|
+
@use_datasource
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
module SerializerInstanceMethods
|
186
|
+
def initialize(object, options={}, *args)
|
187
|
+
if object && object.respond_to?(:for_serializer)
|
188
|
+
# single record
|
189
|
+
datasource_class = options.delete(:datasource)
|
190
|
+
record = object.for_serializer(self.class, datasource_class) do |scope|
|
191
|
+
scope.datasource_params(*[options[:loader_params]].compact)
|
192
|
+
end
|
193
|
+
super(record, options, *args)
|
194
|
+
else
|
195
|
+
super
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
array_serializer_class = if defined?(ActiveModel::Serializer::ArraySerializer)
|
201
|
+
ActiveModel::Serializer::ArraySerializer
|
202
|
+
else
|
203
|
+
ActiveModel::ArraySerializer
|
204
|
+
end
|
205
|
+
|
206
|
+
array_serializer_class.class_exec do
|
207
|
+
alias_method :initialize_without_loaders, :initialize
|
208
|
+
include ActiveLoaders::Adapters::ActiveModelSerializers::ArraySerializer
|
209
|
+
def initialize(*args)
|
210
|
+
initialize_with_loaders(*args)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
ActiveModel::Serializer.singleton_class.send :prepend, SerializerClassMethods
|
215
|
+
ActiveModel::Serializer.send :prepend, SerializerInstanceMethods
|
216
|
+
Datasource::Base.default_consumer_adapter ||= ActiveLoaders::Adapters::ActiveModelSerializers
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module ActiveLoaders
|
4
|
+
module Test
|
5
|
+
Error = Class.new(StandardError)
|
6
|
+
def test_serializer_queries(serializer_klass, model_klass, ignore_columns: [], skip_columns_check: false, allow_queries_per_record: 0)
|
7
|
+
records = get_all_records(model_klass, serializer_klass)
|
8
|
+
fail "Not enough records to test #{serializer_klass}. Create at least 1 #{model_klass}." unless records.size > 0
|
9
|
+
|
10
|
+
records.each do |record|
|
11
|
+
queries = get_executed_queries do
|
12
|
+
serializer_klass.new(record).as_json
|
13
|
+
end
|
14
|
+
|
15
|
+
unless queries.size == allow_queries_per_record
|
16
|
+
fail Error, "unexpected queries\n\nRecord:\n#{record.inspect}\n\nQueries:\n#{queries.join("\n")}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# just for good measure
|
21
|
+
queries = get_executed_queries do
|
22
|
+
ActiveModel::ArraySerializer.new(records, each_serializer: serializer_klass).as_json
|
23
|
+
end
|
24
|
+
unless queries.size == (records.size * allow_queries_per_record)
|
25
|
+
fail Error, "unexpected queries when using ArraySerializer\n\nModel:\n#{model_klass}\n\nQueries:\n#{queries.join("\n")}"
|
26
|
+
end
|
27
|
+
|
28
|
+
# select values (if supported)
|
29
|
+
# TODO: Sequel?
|
30
|
+
unless skip_columns_check
|
31
|
+
if defined?(ActiveRecord::Base) && model_klass.ancestors.include?(ActiveRecord::Base)
|
32
|
+
if records.first.respond_to?(:accessed_fields)
|
33
|
+
accessed_fields = Set.new
|
34
|
+
records.each { |record| accessed_fields.merge(record.accessed_fields) }
|
35
|
+
|
36
|
+
unaccessed_columns = model_klass.column_names - accessed_fields.to_a - ignore_columns.map(&:to_s)
|
37
|
+
|
38
|
+
unless unaccessed_columns.empty?
|
39
|
+
unaccessed_columns_str = unaccessed_columns.join(", ")
|
40
|
+
unaccessed_columns_syms = unaccessed_columns.map { |c| ":#{c}" }.join(", ")
|
41
|
+
all_unaccessed_columns_syms = (ignore_columns.map(&:to_s) + unaccessed_columns).map { |c| ":#{c}" }.join(", ")
|
42
|
+
fail Error, "unnecessary select for #{model_klass} columns: #{unaccessed_columns_str}\n\nAdd to #{serializer_klass} loaders block:\n skip_select #{unaccessed_columns_syms}\n\nOr ignore this error with:\n test_serializer_queries(#{serializer_klass}, #{model_klass}, ignore_columns: [#{all_unaccessed_columns_syms}])\n\nOr skip this columns check entirely:\n test_serializer_queries(#{serializer_klass}, #{model_klass}, skip_columns_check: true)"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
(@active_loaders_tested_serializers ||= Set.new).add(serializer_klass)
|
49
|
+
end
|
50
|
+
|
51
|
+
def assert_all_serializers_tested(namespace = nil)
|
52
|
+
descendants =
|
53
|
+
ObjectSpace.each_object(Class)
|
54
|
+
.select { |klass| klass < ActiveModel::Serializer }
|
55
|
+
.select { |klass| (namespace.nil? && !klass.name.include?("::")) || klass.name.starts_with?("#{namespace}::") }
|
56
|
+
.reject { |klass| Array(@active_loaders_tested_serializers).include?(klass) }
|
57
|
+
|
58
|
+
unless descendants.empty?
|
59
|
+
fail Error, "serializers not tested: #{descendants.map(&:name).join(", ")}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
def get_all_records(model_klass, serializer_klass)
|
65
|
+
if defined?(ActiveRecord::Base) && model_klass.ancestors.include?(ActiveRecord::Base)
|
66
|
+
model_klass.for_serializer(serializer_klass).to_a
|
67
|
+
elsif defined?(Sequel::Model) && model_klass.ancestors.include?(Sequel::Model)
|
68
|
+
model_klass.for_serializer(serializer_klass).all
|
69
|
+
else
|
70
|
+
fail "Unknown model #{model_klass} of type #{model_klass.superclass}."
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_executed_queries
|
75
|
+
logger_io = StringIO.new
|
76
|
+
logger = Logger.new(logger_io)
|
77
|
+
logger.formatter = ->(severity, datetime, progname, msg) { "#{msg}\n" }
|
78
|
+
if defined?(ActiveRecord::Base)
|
79
|
+
ar_old_logger = ActiveRecord::Base.logger
|
80
|
+
ActiveRecord::Base.logger = logger
|
81
|
+
end
|
82
|
+
if defined?(Sequel::Model)
|
83
|
+
Sequel::Model.db.loggers << logger
|
84
|
+
end
|
85
|
+
|
86
|
+
begin
|
87
|
+
yield
|
88
|
+
ensure
|
89
|
+
if defined?(ActiveRecord::Base)
|
90
|
+
ActiveRecord::Base.logger = ar_old_logger
|
91
|
+
end
|
92
|
+
if defined?(Sequel::Model)
|
93
|
+
Sequel::Model.db.loggers.delete(logger)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
logger_io.string.lines.reject { |line| line.strip == "" }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module SequelSerializerSpec
|
4
|
+
describe "Serializer (Sequel)", :sequel do
|
5
|
+
class Comment < Sequel::Model
|
6
|
+
many_to_one :post
|
7
|
+
end
|
8
|
+
|
9
|
+
class Post < Sequel::Model
|
10
|
+
many_to_one :blog
|
11
|
+
one_to_many :comments
|
12
|
+
|
13
|
+
datasource_module do
|
14
|
+
query :author_name do
|
15
|
+
"posts.author_first_name || ' ' || posts.author_last_name"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Blog < Sequel::Model
|
21
|
+
one_to_many :posts
|
22
|
+
end
|
23
|
+
|
24
|
+
class CommentSerializer < ActiveModel::Serializer
|
25
|
+
attributes :id, :comment
|
26
|
+
end
|
27
|
+
|
28
|
+
class PostSerializer < ActiveModel::Serializer
|
29
|
+
attributes :id, :title, :author_name
|
30
|
+
has_many :comments, each_serializer: CommentSerializer
|
31
|
+
|
32
|
+
def author_name
|
33
|
+
object.values[:author_name]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class BlogSerializer < ActiveModel::Serializer
|
38
|
+
attributes :id, :title
|
39
|
+
|
40
|
+
has_many :posts, each_serializer: PostSerializer
|
41
|
+
end
|
42
|
+
|
43
|
+
it "returns serialized hash" do
|
44
|
+
blog = Blog.create title: "Blog 1"
|
45
|
+
post = Post.create blog_id: blog.id, title: "Post 1", author_first_name: "John", author_last_name: "Doe"
|
46
|
+
Comment.create(post_id: post.id, comment: "Comment 1")
|
47
|
+
post = Post.create blog_id: blog.id, title: "Post 2", author_first_name: "Maria", author_last_name: "Doe"
|
48
|
+
Comment.create(post_id: post.id, comment: "Comment 2")
|
49
|
+
blog = Blog.create title: "Blog 2"
|
50
|
+
|
51
|
+
expected_result = [
|
52
|
+
{:id =>1, :title =>"Blog 1", :posts =>[
|
53
|
+
{:id =>1, :title =>"Post 1", :author_name =>"John Doe", comments: [{:id =>1, :comment =>"Comment 1"}]},
|
54
|
+
{:id =>2, :title =>"Post 2", :author_name =>"Maria Doe", comments: [{:id =>2, :comment =>"Comment 2"}]}
|
55
|
+
]},
|
56
|
+
{:id =>2, :title =>"Blog 2", :posts =>[]}
|
57
|
+
]
|
58
|
+
|
59
|
+
expect_query_count(3) do
|
60
|
+
serializer = ActiveModel::ArraySerializer.new(Blog.where, each_serializer: BlogSerializer)
|
61
|
+
expect(expected_result).to eq(serializer.as_json)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module SequelSkipSelectSpec
|
4
|
+
describe "skip_select (Sequel)", :sequel do
|
5
|
+
class Comment < Sequel::Model
|
6
|
+
many_to_one :post
|
7
|
+
end
|
8
|
+
|
9
|
+
class Post < Sequel::Model
|
10
|
+
many_to_one :blog
|
11
|
+
one_to_many :comments
|
12
|
+
|
13
|
+
datasource_module do
|
14
|
+
query :author_name do
|
15
|
+
"posts.author_first_name || ' ' || posts.author_last_name"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Blog < Sequel::Model
|
21
|
+
one_to_many :posts
|
22
|
+
end
|
23
|
+
|
24
|
+
class CommentSerializer < ActiveModel::Serializer
|
25
|
+
attributes :id, :comment
|
26
|
+
end
|
27
|
+
|
28
|
+
class PostSerializer < ActiveModel::Serializer
|
29
|
+
attributes :id, :title, :author_name
|
30
|
+
has_many :comments, each_serializer: CommentSerializer
|
31
|
+
|
32
|
+
loaders do
|
33
|
+
skip_select :author_first_name, :author_last_name
|
34
|
+
end
|
35
|
+
|
36
|
+
def author_name
|
37
|
+
object.values[:author_name]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class BlogSerializer < ActiveModel::Serializer
|
42
|
+
attributes :id, :title
|
43
|
+
|
44
|
+
has_many :posts, each_serializer: PostSerializer
|
45
|
+
end
|
46
|
+
|
47
|
+
it "returns serialized hash" do
|
48
|
+
blog = Blog.create title: "Blog 1"
|
49
|
+
post = Post.create blog_id: blog.id, title: "Post 1", author_first_name: "John", author_last_name: "Doe"
|
50
|
+
Comment.create(post_id: post.id, comment: "Comment 1")
|
51
|
+
post = Post.create blog_id: blog.id, title: "Post 2", author_first_name: "Maria", author_last_name: "Doe"
|
52
|
+
Comment.create(post_id: post.id, comment: "Comment 2")
|
53
|
+
blog = Blog.create title: "Blog 2"
|
54
|
+
|
55
|
+
expected_result = [
|
56
|
+
{:id =>1, :title =>"Blog 1", :posts =>[
|
57
|
+
{:id =>1, :title =>"Post 1", :author_name =>"John Doe", comments: [{:id =>1, :comment =>"Comment 1"}]},
|
58
|
+
{:id =>2, :title =>"Post 2", :author_name =>"Maria Doe", comments: [{:id =>2, :comment =>"Comment 2"}]}
|
59
|
+
]},
|
60
|
+
{:id =>2, :title =>"Blog 2", :posts =>[]}
|
61
|
+
]
|
62
|
+
|
63
|
+
expect_query_count(3) do |logger|
|
64
|
+
serializer = ActiveModel::ArraySerializer.new(Blog.where, each_serializer: BlogSerializer)
|
65
|
+
expect(expected_result).to eq(serializer.as_json)
|
66
|
+
expect(logger.string.lines[0]).to include("blogs.*")
|
67
|
+
expect(logger.string.lines[1]).to_not include("posts.*")
|
68
|
+
expect(logger.string.lines[1]).to_not include("posts.author_first_name,")
|
69
|
+
expect(logger.string.lines[1]).to_not include("posts.author_last_name,")
|
70
|
+
expect(logger.string.lines[1]).to include("posts.id")
|
71
|
+
expect(logger.string.lines[1]).to include("posts.title")
|
72
|
+
expect(logger.string.lines[1]).to include("posts.blog_id")
|
73
|
+
expect(logger.string.lines[2]).to include("comments.*")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|