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