datasource 0.0.1 → 0.0.2
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 +4 -4
- data/README.md +211 -0
- data/lib/datasource/adapters/active_record.rb +188 -89
- data/lib/datasource/adapters/sequel.rb +200 -0
- data/lib/datasource/attributes/computed_attribute.rb +11 -15
- data/lib/datasource/attributes/loader.rb +65 -0
- data/lib/datasource/attributes/query_attribute.rb +8 -3
- data/lib/datasource/base.rb +144 -51
- data/lib/datasource/consumer_adapters/active_model_serializers.rb +66 -0
- data/lib/datasource/serializer.rb +117 -0
- data/lib/datasource.rb +21 -3
- data/lib/generators/datasource/install_generator.rb +8 -0
- data/lib/generators/datasource/templates/initializer.rb +11 -0
- data/test/active_record_helper.rb +13 -0
- data/test/schema.rb +1 -15
- data/test/test_helper.rb +4 -2
- data/test/test_loader.rb +79 -0
- data/test/test_scope.rb +50 -0
- data/test/test_serializer.rb +52 -0
- metadata +44 -8
- data/lib/datasource/serializer/composite.rb +0 -119
- data/test/test_datasource.rb +0 -47
- data/test/test_serializer_composite.rb +0 -48
@@ -0,0 +1,200 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Datasource
|
4
|
+
module Adapters
|
5
|
+
module Sequel
|
6
|
+
module ScopeExtensions
|
7
|
+
def use_datasource_serializer(value)
|
8
|
+
@datasource_serializer = value
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
def use_datasource(value)
|
13
|
+
@datasource = value
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def datasource_select(*args)
|
18
|
+
@datasource_select = Array(@datasource_select) + args
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def each(&block)
|
23
|
+
if @datasource
|
24
|
+
datasource = @datasource.new(self)
|
25
|
+
datasource.select(*Array(@datasource_select))
|
26
|
+
if @datasource_serializer
|
27
|
+
select = []
|
28
|
+
Datasource::Base.consumer_adapter.to_datasource_select(select, @datasource.orm_klass, @datasource_serializer)
|
29
|
+
|
30
|
+
datasource.select(*select)
|
31
|
+
end
|
32
|
+
|
33
|
+
datasource.results.each(&block)
|
34
|
+
else
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module Model
|
41
|
+
extend ActiveSupport::Concern
|
42
|
+
|
43
|
+
included do
|
44
|
+
attr_accessor :loaded_values
|
45
|
+
|
46
|
+
dataset_module do
|
47
|
+
def for_serializer(serializer = nil)
|
48
|
+
scope = if respond_to?(:use_datasource_serializer)
|
49
|
+
self
|
50
|
+
else
|
51
|
+
self.extend(ScopeExtensions).use_datasource(default_datasource)
|
52
|
+
end
|
53
|
+
scope.use_datasource_serializer(serializer || Datasource::Base.consumer_adapter.get_serializer_for(Adapters::Sequel.scope_to_class(scope)))
|
54
|
+
end
|
55
|
+
|
56
|
+
def with_datasource(datasource = nil)
|
57
|
+
scope = if respond_to?(:use_datasource)
|
58
|
+
self
|
59
|
+
else
|
60
|
+
self.extend(ScopeExtensions)
|
61
|
+
end
|
62
|
+
scope.use_datasource(datasource || default_datasource)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
module ClassMethods
|
68
|
+
def default_datasource
|
69
|
+
@default_datasource ||= Class.new(Datasource::From(self))
|
70
|
+
end
|
71
|
+
|
72
|
+
def datasource_module(&block)
|
73
|
+
default_datasource.instance_exec(&block)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.association_reflection(klass, name)
|
79
|
+
reflection = klass.association_reflections[name]
|
80
|
+
|
81
|
+
macro = case reflection[:type]
|
82
|
+
when :many_to_one then :belongs_to
|
83
|
+
when :one_to_many then :has_many
|
84
|
+
when :one_to_one then :has_one
|
85
|
+
else
|
86
|
+
fail Datasource::Error, "unimplemented association type #{reflection[:type]} - TODO"
|
87
|
+
end
|
88
|
+
{
|
89
|
+
klass: reflection[:cache][:class] || reflection[:class_name].constantize,
|
90
|
+
macro: macro,
|
91
|
+
foreign_key: reflection[:key].try!(:to_s)
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.get_table_name(klass)
|
96
|
+
klass.table_name
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.is_scope?(obj)
|
100
|
+
obj.kind_of?(::Sequel::Dataset)
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.scope_to_class(scope)
|
104
|
+
if scope.row_proc && scope.row_proc.ancestors.include?(::Sequel::Model)
|
105
|
+
scope.row_proc
|
106
|
+
else
|
107
|
+
fail Datasource::Error, "unable to determine model for scope"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def to_query(scope)
|
112
|
+
scope.sql
|
113
|
+
end
|
114
|
+
|
115
|
+
def select_scope
|
116
|
+
@scope.select(*get_sequel_select_values)
|
117
|
+
end
|
118
|
+
|
119
|
+
def get_rows
|
120
|
+
eager = {}
|
121
|
+
append_select = []
|
122
|
+
@expose_associations.each_pair do |assoc_name, assoc_select|
|
123
|
+
eager.merge!(
|
124
|
+
get_assoc_eager_options(self.class.orm_klass, assoc_name.to_sym, assoc_select, append_select))
|
125
|
+
end
|
126
|
+
# TODO: remove/disable datasource on scope if present
|
127
|
+
scope = select_scope
|
128
|
+
if scope.respond_to?(:use_datasource)
|
129
|
+
scope = scope.clone.use_datasource(nil)
|
130
|
+
end
|
131
|
+
scope
|
132
|
+
.select_append(*get_sequel_select_values(append_select.map { |v| primary_scope_table(@scope) + ".#{v}" }))
|
133
|
+
.eager(eager).all
|
134
|
+
end
|
135
|
+
|
136
|
+
def get_assoc_eager_options(klass, name, assoc_select, append_select)
|
137
|
+
if reflection = Adapters::Sequel.association_reflection(klass, name)
|
138
|
+
self_append_select = []
|
139
|
+
Datasource::Base.reflection_select(reflection, append_select, self_append_select)
|
140
|
+
assoc_class = reflection[:klass]
|
141
|
+
|
142
|
+
datasource_class = assoc_class.default_datasource
|
143
|
+
|
144
|
+
{
|
145
|
+
name => ->(ds) {
|
146
|
+
ds.with_datasource(datasource_class)
|
147
|
+
.datasource_select(*(self_append_select + assoc_select))
|
148
|
+
}
|
149
|
+
}
|
150
|
+
else
|
151
|
+
{}
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def get_sequel_select_values(values = nil)
|
156
|
+
(values || get_select_values).map { |str| ::Sequel.lit(str) }
|
157
|
+
end
|
158
|
+
|
159
|
+
def primary_scope_table(scope)
|
160
|
+
scope.first_source_alias.to_s
|
161
|
+
end
|
162
|
+
|
163
|
+
def ensure_table_join!(name, att)
|
164
|
+
join_value = Hash(@scope.opts[:join]).find do |value|
|
165
|
+
(value.table_alias || value.table).to_s == att[:name]
|
166
|
+
end
|
167
|
+
fail Datasource::Error, "given scope does not join on #{name}, but it is required by #{att[:name]}" unless join_value
|
168
|
+
end
|
169
|
+
|
170
|
+
module DatasourceGenerator
|
171
|
+
def From(klass)
|
172
|
+
if klass.ancestors.include?(::Sequel::Model)
|
173
|
+
Class.new(Datasource::Base) do
|
174
|
+
attributes *klass.columns
|
175
|
+
associations *klass.associations
|
176
|
+
|
177
|
+
define_singleton_method(:orm_klass) do
|
178
|
+
klass
|
179
|
+
end
|
180
|
+
|
181
|
+
define_method(:primary_key) do
|
182
|
+
klass.primary_key
|
183
|
+
end
|
184
|
+
end
|
185
|
+
else
|
186
|
+
super if defined?(super)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
extend Adapters::Sequel::DatasourceGenerator
|
194
|
+
end
|
195
|
+
|
196
|
+
if not(::Sequel::Model.respond_to?(:datasource_module))
|
197
|
+
class ::Sequel::Model
|
198
|
+
include Datasource::Adapters::Sequel::Model
|
199
|
+
end
|
200
|
+
end
|
@@ -11,30 +11,26 @@ module Datasource
|
|
11
11
|
def depends(*args)
|
12
12
|
args.each do |dep|
|
13
13
|
_depends.deep_merge!(dep)
|
14
|
-
dep.values.each do |names|
|
15
|
-
Array(names).each do |name|
|
16
|
-
define_method(name) do
|
17
|
-
@depend_values[name.to_s]
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
14
|
end
|
22
15
|
end
|
23
16
|
end
|
24
|
-
|
25
|
-
def initialize(depend_values)
|
26
|
-
@depend_values = depend_values
|
27
|
-
end
|
28
17
|
end
|
29
18
|
end
|
30
19
|
|
31
20
|
class Datasource::Base
|
32
|
-
|
33
|
-
|
34
|
-
|
21
|
+
private
|
22
|
+
def self.computed(name, *_deps)
|
23
|
+
deps = _deps.select { |dep| dep.kind_of?(Hash) }
|
24
|
+
_deps.reject! { |dep| dep.kind_of?(Hash) }
|
25
|
+
unless _deps.empty?
|
26
|
+
self_key = adapter.get_table_name(orm_klass)
|
27
|
+
deps.push(self_key => _deps)
|
28
|
+
end
|
35
29
|
|
36
|
-
|
30
|
+
klass = Class.new(Attributes::ComputedAttribute) do
|
31
|
+
depends *deps
|
37
32
|
end
|
33
|
+
|
38
34
|
attribute name, klass
|
39
35
|
end
|
40
36
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Datasource
|
2
|
+
module Attributes
|
3
|
+
class Loader
|
4
|
+
class << self
|
5
|
+
attr_accessor :_options
|
6
|
+
attr_accessor :_load_proc
|
7
|
+
|
8
|
+
def inherited(base)
|
9
|
+
base._options = (_options || {}).dup
|
10
|
+
end
|
11
|
+
|
12
|
+
def options(hash)
|
13
|
+
self._options.merge!(hash)
|
14
|
+
end
|
15
|
+
|
16
|
+
def load(*args, &block)
|
17
|
+
args = args.slice(0, _load_proc.arity) if _load_proc.arity >= 0
|
18
|
+
results = _load_proc.call(*args, &block)
|
19
|
+
|
20
|
+
if _options[:group_by]
|
21
|
+
results = Array(results)
|
22
|
+
send_args = if results.first && results.first.kind_of?(Hash)
|
23
|
+
[:[]]
|
24
|
+
else
|
25
|
+
[]
|
26
|
+
end
|
27
|
+
|
28
|
+
if _options[:one]
|
29
|
+
results.inject({}) do |hash, r|
|
30
|
+
key = r.send(*send_args, _options[:group_by])
|
31
|
+
hash[key] = r
|
32
|
+
hash
|
33
|
+
end
|
34
|
+
else
|
35
|
+
results.inject({}) do |hash, r|
|
36
|
+
key = r.send(*send_args, _options[:group_by])
|
37
|
+
(hash[key] ||= []).push(r)
|
38
|
+
hash
|
39
|
+
end
|
40
|
+
end
|
41
|
+
elsif _options[:array_to_hash]
|
42
|
+
Array(results).inject({}) do |hash, r|
|
43
|
+
hash[r[0]] = r[1]
|
44
|
+
hash
|
45
|
+
end
|
46
|
+
else
|
47
|
+
results
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Datasource::Base
|
55
|
+
private
|
56
|
+
def self.loader(name, _options = {}, &block)
|
57
|
+
klass = Class.new(Attributes::Loader) do
|
58
|
+
# depends deps
|
59
|
+
options(_options)
|
60
|
+
self._load_proc = block
|
61
|
+
end
|
62
|
+
@_loaders[name.to_sym] = klass
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -16,11 +16,16 @@ module Datasource
|
|
16
16
|
end
|
17
17
|
|
18
18
|
class Datasource::Base
|
19
|
-
|
19
|
+
private
|
20
|
+
def self.query(name, deps = nil, value = nil, &block)
|
20
21
|
klass = Class.new(Attributes::QueryAttribute) do
|
21
|
-
depends deps
|
22
|
+
depends deps if deps
|
22
23
|
|
23
|
-
|
24
|
+
if block
|
25
|
+
define_singleton_method(:select_value, &block)
|
26
|
+
else
|
27
|
+
define_singleton_method(:select_value) { value }
|
28
|
+
end
|
24
29
|
end
|
25
30
|
attribute name, klass
|
26
31
|
end
|
data/lib/datasource/base.rb
CHANGED
@@ -1,96 +1,189 @@
|
|
1
1
|
module Datasource
|
2
2
|
class Base
|
3
3
|
class << self
|
4
|
-
attr_accessor :_attributes, :
|
5
|
-
|
4
|
+
attr_accessor :_attributes, :_associations, :_update_scope, :_loaders
|
5
|
+
attr_writer :orm_klass
|
6
6
|
|
7
7
|
def inherited(base)
|
8
|
-
base._attributes = (_attributes ||
|
9
|
-
|
10
|
-
|
8
|
+
base._attributes = (_attributes || {}).dup
|
9
|
+
base._associations = (_associations || {}).dup
|
10
|
+
base._loaders = (_loaders || {}).dup
|
11
|
+
self.send :include, adapter
|
11
12
|
end
|
12
13
|
|
14
|
+
def adapter
|
15
|
+
@adapter ||= begin
|
16
|
+
Datasource::Adapters.const_get(Datasource::Adapters.constants.first)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def consumer_adapter
|
21
|
+
@consumer_adapter = Datasource::ConsumerAdapters::ActiveModelSerializers
|
22
|
+
end
|
23
|
+
|
24
|
+
def orm_klass
|
25
|
+
fail Datasource::Error, "Model class not set for #{name}. You should define it:\nclass YourDatasource\n @orm_klass = MyModelClass\nend"
|
26
|
+
end
|
27
|
+
|
28
|
+
def reflection_select(reflection, parent_select, assoc_select)
|
29
|
+
# append foreign key depending on assoication
|
30
|
+
if reflection[:macro] == :belongs_to
|
31
|
+
parent_select.push(reflection[:foreign_key])
|
32
|
+
elsif [:has_many, :has_one].include?(reflection[:macro])
|
33
|
+
assoc_select.push(reflection[:foreign_key])
|
34
|
+
else
|
35
|
+
fail Datasource::Error, "unsupported association type #{reflection[:macro]} - TODO"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
13
40
|
def attributes(*attrs)
|
14
41
|
attrs.each { |name| attribute(name) }
|
15
42
|
end
|
16
43
|
|
44
|
+
def associations(*assocs)
|
45
|
+
assocs.each { |name| association(name) }
|
46
|
+
end
|
47
|
+
|
48
|
+
def association(name)
|
49
|
+
@_associations[name.to_s] = true
|
50
|
+
end
|
51
|
+
|
17
52
|
def attribute(name, klass = nil)
|
18
|
-
|
53
|
+
att = { name: name.to_s, klass: klass }
|
54
|
+
@_attributes[att[:name]] = att
|
19
55
|
end
|
20
56
|
|
21
|
-
def
|
22
|
-
|
57
|
+
def update_scope(&block)
|
58
|
+
# TODO: careful about scope module extension, to_a infinite recursion
|
59
|
+
@_update_scope = block
|
60
|
+
end
|
61
|
+
|
62
|
+
def group_by_column(column, rows, remove_column = false)
|
63
|
+
rows.inject({}) do |map, row|
|
64
|
+
map[row[column]] = row
|
65
|
+
row.delete(column) if remove_column
|
66
|
+
map
|
67
|
+
end
|
23
68
|
end
|
24
69
|
end
|
25
70
|
|
26
71
|
def initialize(scope)
|
27
|
-
@scope =
|
72
|
+
@scope =
|
73
|
+
if self.class._update_scope
|
74
|
+
self.class._update_scope.call(scope)
|
75
|
+
else
|
76
|
+
scope
|
77
|
+
end
|
28
78
|
@expose_attributes = []
|
29
|
-
@
|
79
|
+
@expose_associations = {}
|
80
|
+
end
|
81
|
+
|
82
|
+
def primary_key
|
83
|
+
:id
|
84
|
+
end
|
85
|
+
|
86
|
+
def select_all
|
87
|
+
@expose_attributes = self.class._attributes.keys.dup
|
30
88
|
end
|
31
89
|
|
32
90
|
def select(*names)
|
33
|
-
|
91
|
+
failure = ->(name) { fail Datasource::Error, "attribute or association #{name} doesn't exist for #{self.class.orm_klass.name}, did you forget to call \"computed :#{name}, <dependencies>\" in your datasource_module?" }
|
92
|
+
names.each do |name|
|
34
93
|
if name.kind_of?(Hash)
|
35
|
-
|
36
|
-
|
37
|
-
|
94
|
+
name.each_pair do |assoc_name, assoc_select|
|
95
|
+
assoc_name = assoc_name.to_s
|
96
|
+
if self.class._associations.key?(assoc_name)
|
97
|
+
@expose_associations[assoc_name] ||= []
|
98
|
+
@expose_associations[assoc_name] += Array(assoc_select)
|
99
|
+
@expose_associations[assoc_name].uniq!
|
100
|
+
else
|
101
|
+
failure.call(assoc_name)
|
102
|
+
end
|
38
103
|
end
|
39
|
-
name.keys
|
40
104
|
else
|
41
|
-
name
|
105
|
+
name = name.to_s
|
106
|
+
if self.class._attributes.key?(name)
|
107
|
+
@expose_attributes.push(name)
|
108
|
+
else
|
109
|
+
failure.call(name)
|
110
|
+
end
|
42
111
|
end
|
43
112
|
end
|
44
|
-
@expose_attributes
|
113
|
+
@expose_attributes.uniq!
|
45
114
|
self
|
46
115
|
end
|
47
116
|
|
48
|
-
def
|
49
|
-
@
|
50
|
-
|
117
|
+
def get_select_values
|
118
|
+
scope_table = primary_scope_table(@scope)
|
119
|
+
select_values = Set.new
|
120
|
+
select_values.add("#{scope_table}.#{primary_key}")
|
51
121
|
|
52
|
-
|
53
|
-
|
122
|
+
self.class._attributes.values.each do |att|
|
123
|
+
if attribute_exposed?(att[:name])
|
124
|
+
if att[:klass] == nil
|
125
|
+
select_values.add("#{scope_table}.#{att[:name]}")
|
126
|
+
elsif att[:klass].ancestors.include?(Attributes::ComputedAttribute)
|
127
|
+
att[:klass]._depends.keys.map(&:to_s).each do |name|
|
128
|
+
next if name == scope_table
|
129
|
+
next if name == "loaders"
|
130
|
+
ensure_table_join!(name, att)
|
131
|
+
end
|
132
|
+
att[:klass]._depends.each_pair do |table, names|
|
133
|
+
next if table.to_sym == :loaders
|
134
|
+
Array(names).each do |name|
|
135
|
+
select_values.add("#{table}.#{name}")
|
136
|
+
end
|
137
|
+
# TODO: handle depends on virtual attribute
|
138
|
+
end
|
139
|
+
elsif att[:klass].ancestors.include?(Attributes::QueryAttribute)
|
140
|
+
select_values.add("(#{att[:klass].select_value}) as #{att[:name]}")
|
141
|
+
att[:klass]._depends.each do |name|
|
142
|
+
next if name == scope_table
|
143
|
+
ensure_table_join!(name, att)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
select_values.to_a
|
54
149
|
end
|
55
150
|
|
56
|
-
def
|
57
|
-
|
58
|
-
|
59
|
-
attribute_map = self.class._attributes.inject({}) do |hash, att|
|
60
|
-
hash[att[:name]] = att
|
61
|
-
hash
|
62
|
-
end
|
151
|
+
def attribute_exposed?(name)
|
152
|
+
@expose_attributes.include?(name.to_s)
|
153
|
+
end
|
63
154
|
|
64
|
-
|
65
|
-
|
155
|
+
def results(rows = nil)
|
156
|
+
rows ||= get_rows
|
66
157
|
|
67
158
|
@expose_attributes.each do |name|
|
68
|
-
att =
|
159
|
+
att = self.class._attributes[name]
|
160
|
+
fail Datasource::Error, "attribute #{name} doesn't exist for #{self.class.orm_klass.name}, did you forget to call \"computed :#{name}, <dependencies>\" in your datasource_module?" unless att
|
69
161
|
klass = att[:klass]
|
70
162
|
next unless klass
|
71
163
|
|
72
|
-
if
|
73
|
-
computed_expose_attributes.push(att)
|
74
|
-
elsif klass.ancestors.include?(Base)
|
75
|
-
datasources[att] =
|
76
|
-
included_datasource_rows(att, @datasource_data[att[:name]], rows)
|
77
|
-
end
|
78
|
-
end
|
164
|
+
next if rows.empty?
|
79
165
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
166
|
+
if att[:klass].ancestors.include?(Attributes::ComputedAttribute)
|
167
|
+
loaders = att[:klass]._depends[:loaders]
|
168
|
+
if loaders
|
169
|
+
Array(loaders).each do |name|
|
170
|
+
if loader = self.class._loaders[name]
|
171
|
+
if loaded_values = loader.load(rows.map(&primary_key), rows, @scope)
|
172
|
+
unless rows.first.loaded_values
|
173
|
+
rows.each do |row|
|
174
|
+
row.loaded_values = {}
|
175
|
+
end
|
176
|
+
end
|
177
|
+
rows.each do |row|
|
178
|
+
row.loaded_values[name] = loaded_values[row.send(primary_key)]
|
179
|
+
end
|
180
|
+
end
|
181
|
+
else
|
182
|
+
raise Datasource::Error, "loader with name :#{name} could not be found"
|
183
|
+
end
|
184
|
+
end
|
86
185
|
end
|
87
186
|
end
|
88
|
-
datasources.each_pair do |att, rows|
|
89
|
-
row[att[:name]] = Array(rows[row[att[:id_key]]])
|
90
|
-
end
|
91
|
-
row.delete_if do |key, value|
|
92
|
-
!attribute_exposed?(key)
|
93
|
-
end
|
94
187
|
end
|
95
188
|
|
96
189
|
rows
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "active_model/serializer"
|
2
|
+
|
3
|
+
module Datasource
|
4
|
+
module ConsumerAdapters
|
5
|
+
module ActiveModelSerializers
|
6
|
+
superclass = if defined?(ActiveModel::Serializer::ArraySerializer)
|
7
|
+
ActiveModel::Serializer::ArraySerializer
|
8
|
+
else
|
9
|
+
ActiveModel::ArraySerializer
|
10
|
+
end
|
11
|
+
class ArraySerializer < superclass
|
12
|
+
def initialize(objects, options = {})
|
13
|
+
datasource_class = options.delete(:datasource)
|
14
|
+
adapter = Datasource::Base.adapter
|
15
|
+
if adapter.is_scope?(objects)
|
16
|
+
datasource_class ||= adapter.scope_to_class(objects).default_datasource
|
17
|
+
|
18
|
+
records = objects
|
19
|
+
.with_datasource(datasource_class)
|
20
|
+
.for_serializer(options[:serializer]).all.to_a # all needed for Sequel eager loading
|
21
|
+
|
22
|
+
super(records, options)
|
23
|
+
else
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module_function
|
30
|
+
def get_serializer_for(klass, serializer_assoc = nil)
|
31
|
+
serializer = if serializer_assoc
|
32
|
+
if serializer_assoc.kind_of?(Hash)
|
33
|
+
serializer_assoc[:options].try(:[], :serializer)
|
34
|
+
else
|
35
|
+
serializer_assoc.options[:serializer]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
serializer || "#{klass.name}Serializer".constantize
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_datasource_select(result, klass, serializer = nil, serializer_assoc = nil)
|
42
|
+
serializer ||= get_serializer_for(klass, serializer_assoc)
|
43
|
+
result.concat(serializer._attributes)
|
44
|
+
result_assocs = {}
|
45
|
+
result.push(result_assocs)
|
46
|
+
|
47
|
+
serializer._associations.each_pair do |name, serializer_assoc|
|
48
|
+
# TODO: what if assoc is renamed in serializer?
|
49
|
+
reflection = Datasource::Base.adapter.association_reflection(klass, name.to_sym)
|
50
|
+
assoc_class = reflection[:klass]
|
51
|
+
|
52
|
+
name = name.to_s
|
53
|
+
result_assocs[name] = []
|
54
|
+
to_datasource_select(result_assocs[name], assoc_class, nil, serializer_assoc)
|
55
|
+
end
|
56
|
+
rescue Exception => ex
|
57
|
+
if ex.is_a?(SystemStackError) || ex.is_a?(Datasource::RecursionError)
|
58
|
+
fail Datasource::RecursionError, "recursive association (involving #{klass.name})"
|
59
|
+
else
|
60
|
+
raise
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
ArrayAMS = ConsumerAdapters::ActiveModelSerializers::ArraySerializer
|
66
|
+
end
|