datasource 0.1.1 → 0.2.0

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.
@@ -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: []