datasource 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,35 +4,43 @@ module Datasource
4
4
  module Adapters
5
5
  module Sequel
6
6
  module ScopeExtensions
7
- def use_datasource_serializer(value)
8
- @datasource_serializer = value
7
+ def self.extended(mod)
8
+ mod.instance_exec do
9
+ @datasource_info ||= { select: [], params: [] }
10
+ end
11
+ end
12
+
13
+ def datasource_set(hash)
14
+ @datasource_info.merge!(hash)
15
+ self
16
+ end
17
+
18
+ def datasource_select(*args)
19
+ @datasource_info[:select] += args
9
20
  self
10
21
  end
11
22
 
12
- def use_datasource(value)
13
- @datasource = value
23
+ def datasource_params(*args)
24
+ @datasource_info[:params] += args
14
25
  self
15
26
  end
16
27
 
17
28
  def get_datasource
18
- datasource = @datasource.new(self)
19
- datasource.select(*Array(@datasource_select))
20
- if @datasource_serializer
29
+ klass = @datasource_info[:datasource_class]
30
+ datasource = klass.new(self)
31
+ datasource.select(*Array(@datasource_info[:select]))
32
+ datasource.params(*@datasource_info[:params])
33
+ if @datasource_info[:serializer_class]
21
34
  select = []
22
- Datasource::Base.consumer_adapter.to_datasource_select(select, @datasource.orm_klass, @datasource_serializer, nil, datasource.adapter)
35
+ Datasource::Base.consumer_adapter.to_datasource_select(select, klass.orm_klass, @datasource_info[:serializer_class], nil, datasource.adapter)
23
36
 
24
37
  datasource.select(*select)
25
38
  end
26
39
  datasource
27
40
  end
28
41
 
29
- def datasource_select(*args)
30
- @datasource_select = Array(@datasource_select) + args
31
- self
32
- end
33
-
34
42
  def each(&block)
35
- if @datasource
43
+ if @datasource_info[:datasource_class]
36
44
  datasource = get_datasource
37
45
 
38
46
  datasource.results.each(&block)
@@ -46,25 +54,31 @@ module Datasource
46
54
  extend ActiveSupport::Concern
47
55
 
48
56
  included do
49
- attr_accessor :loaded_values
57
+ attr_accessor :_datasource_loaded, :_datasource_instance
50
58
 
51
59
  dataset_module do
52
- def for_serializer(serializer = nil)
53
- scope = if respond_to?(:use_datasource_serializer)
54
- self
55
- else
56
- self.extend(ScopeExtensions).use_datasource(Adapters::Sequel.scope_to_class(self).default_datasource)
57
- end
58
- scope.use_datasource_serializer(serializer || Datasource::Base.consumer_adapter.get_serializer_for(Adapters::Sequel.scope_to_class(scope)))
60
+ def for_serializer(serializer_class = nil)
61
+ scope = scope_with_datasource_ext
62
+ serializer_class ||=
63
+ Datasource::Base.consumer_adapter
64
+ .get_serializer_for(Adapters::Sequel.scope_to_class(scope))
65
+ scope.datasource_set(serializer_class: serializer_class)
66
+ end
67
+
68
+ def with_datasource(datasource_class = nil)
69
+ scope_with_datasource_ext(datasource_class)
59
70
  end
60
71
 
61
- def with_datasource(datasource = nil)
62
- scope = if respond_to?(:use_datasource)
72
+ private
73
+ def scope_with_datasource_ext(datasource_class = nil)
74
+ if respond_to?(:datasource_set)
63
75
  self
64
76
  else
77
+ datasource_class ||= Adapters::Sequel.scope_to_class(self).default_datasource
78
+
65
79
  self.extend(ScopeExtensions)
80
+ .datasource_set(datasource_class: datasource_class)
66
81
  end
67
- scope.use_datasource(datasource || default_datasource)
68
82
  end
69
83
  end
70
84
  end
@@ -75,7 +89,10 @@ module Datasource
75
89
 
76
90
  scope = self.class
77
91
  .with_datasource(datasource_class)
78
- .for_serializer(serializer).where(pk => send(pk))
92
+ .for_serializer(serializer)
93
+ .where(pk => send(pk))
94
+
95
+ scope = yield(scope) if block_given?
79
96
 
80
97
  datasource = scope.get_datasource
81
98
  if datasource.can_upgrade?(self)
@@ -134,11 +151,15 @@ module Datasource
134
151
  false
135
152
  end
136
153
 
154
+ def scope_to_records(scope)
155
+ scope.all
156
+ end
157
+
137
158
  def has_attribute?(record, name)
138
159
  record.values.key?(name.to_sym)
139
160
  end
140
161
 
141
- def get_assoc_eager_options(klass, name, assoc_select, append_select)
162
+ def get_assoc_eager_options(klass, name, assoc_select, append_select, params)
142
163
  if reflection = association_reflection(klass, name)
143
164
  self_append_select = []
144
165
  Datasource::Base.reflection_select(reflection, append_select, self_append_select)
@@ -150,6 +171,7 @@ module Datasource
150
171
  name => ->(ds) {
151
172
  ds.with_datasource(datasource_class)
152
173
  .datasource_select(*(assoc_select + self_append_select))
174
+ .datasource_params(params)
153
175
  }
154
176
  }
155
177
  else
@@ -170,6 +192,7 @@ module Datasource
170
192
  end
171
193
 
172
194
  def upgrade_records(ds, records)
195
+ Datasource.logger.debug { "Upgrading records #{records.map(&:class).map(&:name).join(', ')}" }
173
196
  get_final_scope(ds).send :post_load, records
174
197
  ds.results(records)
175
198
  end
@@ -179,12 +202,12 @@ module Datasource
179
202
  append_select = []
180
203
  ds.expose_associations.each_pair do |assoc_name, assoc_select|
181
204
  eager.merge!(
182
- get_assoc_eager_options(ds.class.orm_klass, assoc_name.to_sym, assoc_select, append_select))
205
+ get_assoc_eager_options(ds.class.orm_klass, assoc_name.to_sym, assoc_select, append_select, ds.params))
183
206
  end
184
207
  # TODO: remove/disable datasource on scope if present
185
208
  scope = select_scope(ds)
186
- if scope.respond_to?(:use_datasource)
187
- scope = scope.clone.use_datasource(nil)
209
+ if scope.respond_to?(:datasource_set)
210
+ scope = scope.clone.datasource_set(datasource_class: nil)
188
211
  end
189
212
  scope
190
213
  .select_append(*get_sequel_select_values(append_select.map { |v| primary_scope_table(ds) + ".#{v}" }))
@@ -0,0 +1,101 @@
1
+ module Datasource
2
+ module Attributes
3
+ class Loaded
4
+ class << self
5
+ attr_accessor :_options
6
+
7
+ def inherited(base)
8
+ base._options = (_options || {}).dup
9
+ end
10
+
11
+ def options(hash)
12
+ self._options.merge!(hash)
13
+ end
14
+
15
+ def default_value
16
+ self._options[:default]
17
+ end
18
+
19
+ def load(collection_context)
20
+ loader = collection_context.method(_options[:source])
21
+ args = [collection_context].slice(0, loader.arity) if loader.arity >= 0
22
+ results = loader.call(*args)
23
+
24
+ if _options[:group_by]
25
+ results = Array(results)
26
+ send_args = if results.first && results.first.kind_of?(Hash)
27
+ [:[]]
28
+ else
29
+ []
30
+ end
31
+
32
+ if _options[:one]
33
+ results.inject(empty_hash) do |hash, r|
34
+ key = r.send(*send_args, _options[:group_by])
35
+ hash[key] = r
36
+ hash
37
+ end
38
+ else
39
+ results.inject(empty_hash) do |hash, r|
40
+ key = r.send(*send_args, _options[:group_by])
41
+ (hash[key] ||= []).push(r)
42
+ hash
43
+ end
44
+ end
45
+ elsif _options[:from] == :array
46
+ Array(results).inject(empty_hash) do |hash, r|
47
+ hash[r[0]] = r[1]
48
+ hash
49
+ end
50
+ else
51
+ set_hash_default(Hash(results))
52
+ end
53
+ end
54
+
55
+ private
56
+ def set_hash_default(hash)
57
+ hash.default = default_value
58
+ hash
59
+ end
60
+
61
+ def empty_hash
62
+ set_hash_default(Hash.new)
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ class Datasource::Base
69
+ private
70
+ def self.loaded(name, _options = {}, &block)
71
+ name = name.to_sym
72
+ datasource_class = self
73
+ loader_class = Class.new(Attributes::Loaded) do
74
+ options(_options.reverse_merge(source: :"load_#{name}"))
75
+ end
76
+ @_loaders[name] = loader_class
77
+ @_loader_order << name
78
+
79
+ method_module = Module.new do
80
+ define_method name do |*args, &block|
81
+ if _datasource_loaded
82
+ if _datasource_loaded.key?(name)
83
+ _datasource_loaded[name]
84
+ else
85
+ fail Datasource::Error, "loader #{name} called but was not selected"
86
+ end
87
+ elsif defined?(super)
88
+ super(*args, &block)
89
+ else
90
+ method_missing(name, *args, &block)
91
+ end
92
+ end
93
+ end
94
+
95
+ orm_klass.class_eval do
96
+ prepend method_module
97
+ end
98
+ computed name, loader: name
99
+ end
100
+ end
101
+ end
@@ -1,13 +1,15 @@
1
1
  module Datasource
2
2
  class Base
3
3
  class << self
4
- attr_accessor :_attributes, :_associations, :_update_scope, :_loaders
4
+ attr_accessor :_attributes, :_associations, :_update_scope, :_loaders, :_loader_order, :_collection_context
5
5
  attr_writer :orm_klass
6
6
 
7
7
  def inherited(base)
8
8
  base._attributes = (_attributes || {}).dup
9
9
  base._associations = (_associations || {}).dup
10
10
  base._loaders = (_loaders || {}).dup
11
+ base._loader_order = (_loader_order || []).dup
12
+ base._collection_context = Class.new(_collection_context || CollectionContext)
11
13
  end
12
14
 
13
15
  def default_adapter
@@ -39,6 +41,10 @@ module Datasource
39
41
  end
40
42
  end
41
43
 
44
+ def collection(&block)
45
+ _collection_context.class_exec(&block)
46
+ end
47
+
42
48
  private
43
49
  def attributes(*attrs)
44
50
  attrs.each { |name| attribute(name) }
@@ -84,6 +90,21 @@ module Datasource
84
90
  @expose_attributes = []
85
91
  @expose_associations = {}
86
92
  @select_all_columns = false
93
+ @params = {}
94
+ end
95
+
96
+ def params(*args)
97
+ args.each do |arg|
98
+ if arg.kind_of?(Hash)
99
+ @params.deep_merge!(arg.symbolize_keys)
100
+ elsif arg.is_a?(Symbol)
101
+ @params.merge!(arg => true)
102
+ else
103
+ fail Datasource::Error, "unknown parameter type #{arg.class}"
104
+ end
105
+ end
106
+
107
+ @params
87
108
  end
88
109
 
89
110
  def select_all_columns
@@ -230,39 +251,79 @@ module Datasource
230
251
  adapter.upgrade_records(self, Array(records))
231
252
  end
232
253
 
233
- def results(rows = nil)
234
- rows ||= adapter.get_rows(self)
254
+ def get_collection_context(rows)
255
+ self.class._collection_context.new(@scope, rows, self, @params)
256
+ end
235
257
 
236
- unless rows.empty?
237
- @expose_attributes.each do |name|
238
- att = self.class._attributes[name]
239
- klass = att[:klass]
240
- next unless klass
241
-
242
- if att[:klass].ancestors.include?(Attributes::ComputedAttribute)
243
- att[:klass]._loader_depends.each do |name|
244
- if loader = self.class._loaders[name]
245
- if loaded_values = loader.load(rows.map(&self.class.primary_key), rows, @scope)
246
- unless rows.first.loaded_values
247
- rows.each do |row|
248
- row.loaded_values = {}
249
- end
250
- end
251
- rows.each do |row|
252
- key = row.send(self.class.primary_key)
253
- if loaded_values.key?(key)
254
- row.loaded_values[name] = loaded_values[key]
255
- elsif loader.default_value
256
- row.loaded_values[name] = loader.default_value
257
- end
258
- end
259
- end
260
- else
261
- raise Datasource::Error, "loader with name :#{name} could not be found"
258
+ def get_exposed_loaders
259
+ @expose_attributes
260
+ .map { |name|
261
+ self.class._attributes[name]
262
+ }.select { |att|
263
+ att[:klass] && att[:klass].ancestors.include?(Attributes::ComputedAttribute)
264
+ }.flat_map { |att|
265
+ att[:klass]._loader_depends
266
+ }.uniq
267
+ .sort_by { |loader_name|
268
+ self.class._loader_order.index(loader_name)
269
+ }
270
+ end
271
+
272
+ def run_loaders(rows)
273
+ return if rows.empty?
274
+
275
+ # check if loaders have already been ran
276
+ if rows.first._datasource_loaded
277
+ # not frequent, so we can afford to check all rows
278
+ check_list = get_exposed_loaders
279
+ run_list = []
280
+ rows.each do |row|
281
+ if row._datasource_loaded
282
+ check_list.delete_if do |name|
283
+ if !row._datasource_loaded.key?(name)
284
+ run_list << name
285
+ true
262
286
  end
263
287
  end
288
+ break if check_list.empty?
289
+ else
290
+ run_list.concat(check_list)
291
+ break
264
292
  end
265
293
  end
294
+ else
295
+ # most frequent case - loaders haven't been ran
296
+ run_list = get_exposed_loaders
297
+ end
298
+
299
+ get_collection_context(rows).tap do |collection_context|
300
+ run_list.each do |loader_name|
301
+ loader =
302
+ self.class._loaders[loader_name] or
303
+ fail Datasource::Error, "loader with name :#{loader_name} could not be found"
304
+ Datasource.logger.info { "Running loader #{loader_name} for #{rows.first.try!(:class)}" }
305
+ collection_context.loaded_values[loader_name] = loader.load(collection_context)
306
+ end
307
+ end
308
+ end
309
+
310
+ def set_row_loaded_values(collection_context, row)
311
+ row._datasource_loaded ||= {}
312
+
313
+ primary_key = row.send(self.class.primary_key)
314
+ collection_context.loaded_values.each_pair do |name, values|
315
+ row._datasource_loaded[name] = values[primary_key]
316
+ end
317
+ end
318
+
319
+ def results(rows = nil)
320
+ rows ||= adapter.get_rows(self)
321
+
322
+ collection_context = run_loaders(rows)
323
+
324
+ rows.each do |row|
325
+ row._datasource_instance = self
326
+ set_row_loaded_values(collection_context, row) if collection_context
266
327
  end
267
328
 
268
329
  rows
@@ -0,0 +1,32 @@
1
+ module Datasource
2
+ class CollectionContext
3
+ attr_reader :scope, :all_models, :datasource, :datasource_class, :params, :loaded_values
4
+
5
+ def initialize(scope, collection, datasource, params)
6
+ @scope = scope
7
+ @all_models = collection
8
+ @datasource = datasource
9
+ @datasource_class = datasource.class
10
+ @params = params
11
+ @loaded_values = {}
12
+ end
13
+
14
+ def models
15
+ return @models if @models
16
+
17
+ @model_ids = []
18
+ @models = all_models.select do |model|
19
+ id = model.send(@datasource_class.primary_key)
20
+ @model_ids << id
21
+ id
22
+ end
23
+ end
24
+
25
+ def model_ids
26
+ return @model_ids if @model_ids
27
+ models
28
+ @model_ids
29
+ end
30
+ alias_method :ids, :model_ids
31
+ end
32
+ end
@@ -10,9 +10,20 @@ module Datasource
10
10
  if adapter && !adapter.scope_loaded?(objects)
11
11
  datasource_class ||= adapter.scope_to_class(objects).default_datasource
12
12
 
13
- records = objects
13
+ scope = begin
14
+ objects
14
15
  .with_datasource(datasource_class)
15
- .for_serializer(options[:serializer]).all.to_a # all needed for Sequel eager loading
16
+ .for_serializer(options[:serializer])
17
+ .datasource_params(*[options[:datasource_params]].compact)
18
+ rescue NameError
19
+ if options[:serializer].nil?
20
+ return initialize_without_datasource(objects, options)
21
+ else
22
+ raise
23
+ end
24
+ end
25
+
26
+ records = adapter.scope_to_records(scope)
16
27
 
17
28
  initialize_without_datasource(records, options)
18
29
  else
@@ -37,8 +48,13 @@ module Datasource
37
48
  adapter ||= Datasource::Base.default_adapter
38
49
  serializer ||= get_serializer_for(klass, serializer_assoc)
39
50
  result.unshift("*") if Datasource.config.simple_mode
40
- result.concat(serializer._attributes)
41
- result_assocs = {}
51
+ if serializer._attributes.respond_to?(:keys) # AMS 0.8
52
+ result.concat(serializer._attributes.keys)
53
+ else # AMS 0.9
54
+ result.concat(serializer._attributes)
55
+ end
56
+ result.concat(serializer.datasource_select)
57
+ result_assocs = serializer.datasource_includes.dup
42
58
  result.push(result_assocs)
43
59
 
44
60
  serializer._associations.each_pair do |name, serializer_assoc|
@@ -74,3 +90,49 @@ array_serializer_class.class_exec do
74
90
  initialize_with_datasource(*args)
75
91
  end
76
92
  end
93
+
94
+ module SerializerClassMethods
95
+ def inherited(base)
96
+ base.datasource_select(*datasource_select.deep_dup)
97
+ base.datasource_includes(*datasource_includes.deep_dup)
98
+
99
+ super
100
+ end
101
+
102
+ def datasource_select(*args)
103
+ @datasource_select ||= []
104
+ @datasource_select.concat(args)
105
+
106
+ @datasource_select
107
+ end
108
+
109
+ def datasource_includes(*args)
110
+ @datasource_includes ||= {}
111
+
112
+ args.each do |arg|
113
+ @datasource_includes.deep_merge!(datasource_includes_to_select(arg))
114
+ end
115
+
116
+ @datasource_includes
117
+ end
118
+
119
+ private
120
+ def datasource_includes_to_select(arg)
121
+ if arg.kind_of?(Hash)
122
+ arg.keys.inject({}) do |memo, key|
123
+ memo[key.to_sym] = ["*", datasource_includes_to_select(arg[key])]
124
+ memo
125
+ end
126
+ elsif arg.kind_of?(Array)
127
+ arg.inject({}) do |memo, element|
128
+ memo.deep_merge!(datasource_includes_to_select(element))
129
+ end
130
+ elsif arg.respond_to?(:to_sym)
131
+ { arg.to_sym => ["*"] }
132
+ else
133
+ fail Datasource::Error, "unknown includes value type #{arg.class}"
134
+ end
135
+ end
136
+ end
137
+
138
+ ActiveModel::Serializer.singleton_class.send :prepend, SerializerClassMethods
data/lib/datasource.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require 'datasource/configuration'
2
2
  module Datasource
3
+ mattr_accessor :logger
4
+
3
5
  Error = Class.new(StandardError)
4
6
  RecursionError = Class.new(StandardError)
5
7
  include Configuration
@@ -13,12 +15,17 @@ module Datasource
13
15
  }
14
16
 
15
17
  module_function
16
- def load(*adapters)
17
- unless adapters.empty?
18
- warn "[DEPRECATION] passing adapters to Datasource.load is deprecated. Use Datasource.setup instead."
19
- config.adapters = adapters
18
+ def setup
19
+ self.logger ||= Logger.new(STDOUT).tap do |logger|
20
+ logger.level = Logger::WARN
21
+ logger.formatter = proc do |severity, datetime, progname, msg|
22
+ "[Datasource][#{severity}] - #{msg}\n"
23
+ end
24
+ logger
20
25
  end
21
26
 
27
+ yield(config)
28
+
22
29
  config.adapters.each do |adapter|
23
30
  adapter = AdapterPaths[adapter]
24
31
  adapter = AdapterPaths[adapter] if adapter.is_a?(Symbol)
@@ -26,11 +33,6 @@ module_function
26
33
  end
27
34
  end
28
35
 
29
- def setup
30
- yield(config)
31
- load
32
- end
33
-
34
36
  def orm_adapters
35
37
  @orm_adapters ||= begin
36
38
  Datasource::Adapters.constants.map { |name| Datasource::Adapters.const_get(name) }
@@ -38,10 +40,9 @@ module_function
38
40
  end
39
41
  end
40
42
 
43
+ require 'datasource/collection_context'
41
44
  require 'datasource/base'
42
45
 
43
46
  require 'datasource/attributes/computed_attribute'
44
47
  require 'datasource/attributes/query_attribute'
45
- require 'datasource/attributes/loader'
46
-
47
- require 'datasource/serializer'
48
+ require 'datasource/attributes/loaded'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: datasource
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Berdajs
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-11 00:00:00.000000000 Z
11
+ date: 2015-02-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: active_model_serializers
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0.9'
19
+ version: '0.8'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '0.9'
26
+ version: '0.8'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activesupport
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -136,15 +136,15 @@ files:
136
136
  - lib/datasource/adapters/active_record.rb
137
137
  - lib/datasource/adapters/sequel.rb
138
138
  - lib/datasource/attributes/computed_attribute.rb
139
- - lib/datasource/attributes/loader.rb
139
+ - lib/datasource/attributes/loaded.rb
140
140
  - lib/datasource/attributes/query_attribute.rb
141
141
  - lib/datasource/base.rb
142
+ - lib/datasource/collection_context.rb
142
143
  - lib/datasource/configuration.rb
143
144
  - lib/datasource/consumer_adapters/active_model_serializers.rb
144
- - lib/datasource/serializer.rb
145
145
  - lib/generators/datasource/install_generator.rb
146
146
  - lib/generators/datasource/templates/initializer.rb
147
- homepage: https://github.com/mrbrdo/datasource
147
+ homepage: https://github.com/kundi/datasource
148
148
  licenses:
149
149
  - MIT
150
150
  metadata: {}
@@ -164,8 +164,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
164
164
  version: '0'
165
165
  requirements: []
166
166
  rubyforge_project:
167
- rubygems_version: 2.2.2
167
+ rubygems_version: 2.4.5
168
168
  signing_key:
169
169
  specification_version: 4
170
- summary: Ruby library to automatically preload records for your serializers
170
+ summary: Ruby library to automatically preload data for your serializers
171
171
  test_files: []