datasource 0.0.5 → 0.0.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 107812940a2c8fc80ebec710b0ebd63c5f710fd4
4
- data.tar.gz: b0d4b77a9cace8c1f559d39738065cfe69930c55
3
+ metadata.gz: 89a34ecb59d8f4183295c43b4badd33aca5f5e62
4
+ data.tar.gz: b408320273ce49236bd022d7db00c1fce06bdfd5
5
5
  SHA512:
6
- metadata.gz: 960aa77805c5fe4e0a2c1ac658956bf0c2b4d8e81769c96850a012b5e95facac053ba6407f1b8d88ec65749b7ecc813fb6229502302ea4d070873b4483d8113f
7
- data.tar.gz: 22dad36f267317cb360ffc30d035b8efd3074faa3ae801e26bbef76c3b9c9d358f417f8a0dee31cf37ea9a49c92e33afe472a753b2d86276e13319cf72b057e8
6
+ metadata.gz: 0db668df6f4ae71223655a544d59b805e6bdd2694ab540f37dacc7962700d427799f00308d0cdf03a180a7e45f7d3891ff795e73def10c180bf9df5d92942df3
7
+ data.tar.gz: c69ae3fe12467516c7f24d090e7240a398e9e7d341dd82a1c54b459c57dd4fb2f9c796a6180a85e02fc304ea45bbf96361579af3249974df4fd73cc0a02dd997
data/README.md CHANGED
@@ -18,12 +18,12 @@ Run install generator:
18
18
  rails g datasource:install
19
19
  ```
20
20
 
21
- ### ORM support
21
+ #### ORM support
22
22
 
23
23
  - ActiveRecord
24
24
  - Sequel
25
25
 
26
- ### Serializer support
26
+ #### Serializer support
27
27
 
28
28
  - active_model_serializers
29
29
 
@@ -66,18 +66,16 @@ SELECT id, title, user_id FROM posts WHERE id IN (?)
66
66
  ```
67
67
 
68
68
  ### Model Methods / Virtual Attributes
69
- You need to specify which database columns a method depends on to be able to use it.
70
- The method itself can be either in the serializer or in your model, it doesn't matter.
71
-
72
- You can list multiple dependency columns.
69
+ You need to use `computed` in a `datasource_module` block to specify what a method depends on. It can depend on database columns, other computed attributes or loaders.
73
70
 
74
71
  ```ruby
75
72
  class User < ActiveRecord::Base
76
73
  datasource_module do
77
74
  computed :first_name_initial, :first_name
78
- computed :last_name_initial, :last_name
75
+ computed :both_initials, :first_name, :last_name
79
76
  end
80
77
 
78
+ # method can be in model
81
79
  def first_name_initial
82
80
  first_name[0].upcase
83
81
  end
@@ -86,8 +84,9 @@ end
86
84
  class UserSerializer < ActiveModel::Serializer
87
85
  attributes :first_name_initial, :last_name_initial
88
86
 
89
- def last_name_initial
90
- object.last_name[0].upcase
87
+ # method can also be in serializer
88
+ def both_initials
89
+ object.last_name[0].upcase + object.last_name[0].upcase
91
90
  end
92
91
  end
93
92
  ```
@@ -100,15 +99,15 @@ You will be reminded with an exception if you forget to do this.
100
99
 
101
100
  ### Show action
102
101
 
103
- You will probably want to reuse the same preloading rules in your show action.
104
- You just need to call `.for_serializer` on the scope. You can optionally give it
105
- the serializer class as an argument.
102
+ You will probably want to reuse the same preloading logic in your show action.
103
+ You will need to call `for_serializer` on the scope before you call `find`.
104
+ You can optionally give it the serializer class as an argument.
106
105
 
107
106
  ```ruby
108
- class UsersController < ApplicationController
107
+ class PostsController < ApplicationController
109
108
  def show
110
109
  post = Post.for_serializer.find(params[:id])
111
- # also works:
110
+ # more explicit:
112
111
  # post = Post.for_serializer(PostSerializer).find(params[:id])
113
112
 
114
113
  render json: post
@@ -116,9 +115,22 @@ class UsersController < ApplicationController
116
115
  end
117
116
  ```
118
117
 
118
+ You can also use it on an existing record, but doing it this way may result in
119
+ an additional SQL query (for example if you use query attributes).
120
+
121
+ ```ruby
122
+ class UsersController < ApplicationController
123
+ def show
124
+ user = current_user.for_serializer
125
+
126
+ render json: user
127
+ end
128
+ end
129
+ ```
130
+
119
131
  ## Advanced Usage
120
132
 
121
- ### Query attributes
133
+ ### Query attribute
122
134
 
123
135
  You can specify a SQL fragment for `SELECT` and use that as an attribute on your
124
136
  model. As a simple example you can concatenate 2 strings together in SQL:
@@ -141,11 +153,13 @@ end
141
153
  SELECT users.id, (users.first_name || ' ' || users.last_name) AS full_name FROM users
142
154
  ```
143
155
 
144
- ### Loaders
156
+ Note: If you need data from another table, use a join in a loader (see below).
157
+
158
+ ### Loader
145
159
 
146
160
  You might want to have some more complex preloading logic. In that case you can use a loader.
147
- The loader will receive ids of the records, and you need to return a hash with your data.
148
- The key of the hash must be the id of the record for which the data is.
161
+ A loader will receive ids of the records, and needs to return a hash.
162
+ The key of the hash must be the id of the record for which the value is.
149
163
 
150
164
  A loader will only be executed if a computed attribute depends on it. If an attribute depends
151
165
  on multiple loaders, pass an array of loaders like so `computed :attr, loaders: [:loader1, :loader2]`.
@@ -156,7 +170,7 @@ will be nil. However you can use the `default` option for such cases (see below
156
170
  ```ruby
157
171
  class User < ActiveRecord::Base
158
172
  datasource_module do
159
- computed :post_count, loaders: :post_counts
173
+ computed :post_count, loader: :post_counts
160
174
  loader :post_counts, array_to_hash: true, default: 0 do |user_ids|
161
175
  results = Post
162
176
  .where(user_id: user_ids)
@@ -210,3 +224,58 @@ loader :stuff, group_by: "user_id", one: true do |ids|
210
224
  # { 10 => { "title" => "Something", "user_id" => 10 } }
211
225
  end
212
226
  ```
227
+
228
+ ### Loaded
229
+
230
+ Loaded is the same as loader, but it also creates a computed attribute and defines
231
+ a method with the same name on your model.
232
+
233
+ Here is the previous example with `loaded` instead of `loader`:
234
+
235
+ ```ruby
236
+ class User < ActiveRecord::Base
237
+ datasource_module do
238
+ loaded :post_count, array_to_hash: true, default: 0 do |user_ids|
239
+ results = Post
240
+ .where(user_id: user_ids)
241
+ .group(:user_id)
242
+ .pluck("user_id, COUNT(id)")
243
+ end
244
+ end
245
+ end
246
+
247
+ class UserSerializer < ActiveModel::Serializer
248
+ attributes :id, :post_count
249
+ # Note that the User now has a generated post_count method
250
+ end
251
+ ```
252
+
253
+ When using `loaded`, if you already have the method with this name defined in your
254
+ model, datasource will automatically create a 'wrapper' method that will use the
255
+ loaded value if available (when you are using a serializer/datasource), otherwise
256
+ it will fallback to your original method. This way you can still use the same
257
+ method when you are not using a serializer/datasource. For example:
258
+
259
+ ```ruby
260
+ class User < ActiveRecord::Base
261
+ datasource_module do
262
+ loaded :post_count, array_to_hash: true, default: 0 do |user_ids|
263
+ results = Post
264
+ .where(user_id: user_ids)
265
+ .group(:user_id)
266
+ .pluck("user_id, COUNT(id)")
267
+ end
268
+
269
+ def post_count
270
+ posts.count
271
+ end
272
+ end
273
+ end
274
+
275
+ class UserSerializer < ActiveModel::Serializer
276
+ attributes :id, :post_count # <- post_count will be read from loaded_values
277
+ end
278
+
279
+ User.first.post_count # <- your method will be called
280
+
281
+ ```
@@ -20,17 +20,22 @@ module Datasource
20
20
  self
21
21
  end
22
22
 
23
+ def get_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, nil, datasource.adapter)
29
+
30
+ datasource.select(*select)
31
+ end
32
+ datasource
33
+ end
34
+
23
35
  private
24
36
  def exec_queries
25
37
  if @datasource
26
- datasource = @datasource.new(self)
27
- datasource.select(*Array(@datasource_select))
28
- if @datasource_serializer
29
- select = []
30
- Datasource::Base.consumer_adapter.to_datasource_select(select, @datasource.orm_klass, @datasource_serializer, nil, datasource.adapter)
31
-
32
- datasource.select(*select)
33
- end
38
+ datasource = get_datasource
34
39
 
35
40
  @loaded = true
36
41
  @records = datasource.results
@@ -47,6 +52,22 @@ module Datasource
47
52
  attr_accessor :loaded_values
48
53
  end
49
54
 
55
+ def for_serializer(serializer = nil)
56
+ datasource_class = self.class.default_datasource
57
+ pk = datasource_class.primary_key.to_sym
58
+
59
+ scope = self.class
60
+ .with_datasource(datasource_class)
61
+ .for_serializer(serializer).where(pk => send(pk))
62
+
63
+ datasource = scope.get_datasource
64
+ if datasource.can_upgrade?(self)
65
+ datasource.upgrade_records(self).first
66
+ else
67
+ scope.first
68
+ end
69
+ end
70
+
50
71
  module ClassMethods
51
72
  def for_serializer(serializer = nil)
52
73
  scope = if all.respond_to?(:use_datasource_serializer)
@@ -67,7 +88,7 @@ module Datasource
67
88
  end
68
89
 
69
90
  def default_datasource
70
- @default_datasource ||= Class.new(Datasource::From(self))
91
+ @default_datasource ||= Datasource::From(self)
71
92
  end
72
93
 
73
94
  def datasource_module(&block)
@@ -103,6 +124,10 @@ module Datasource
103
124
  scope.loaded?
104
125
  end
105
126
 
127
+ def has_attribute?(record, name)
128
+ record.attributes.key?(name.to_s)
129
+ end
130
+
106
131
  def association_klass(reflection)
107
132
  if reflection.macro == :belongs_to && reflection.options[:polymorphic]
108
133
  fail Datasource::Error, "polymorphic belongs_to not supported, write custom loader"
@@ -111,7 +136,7 @@ module Datasource
111
136
  end
112
137
  end
113
138
 
114
- def preload_association(records, name)
139
+ def load_association(records, name)
115
140
  return if records.empty?
116
141
  return if records.first.association(name.to_sym).loaded?
117
142
  klass = records.first.class
@@ -125,7 +150,7 @@ module Datasource
125
150
  scope = assoc_class.all
126
151
  datasource = datasource_class.new(scope)
127
152
  datasource_select = serializer_class._attributes.dup
128
- Datasource::Base.reflection_select(Adapters::ActiveRecord.association_reflection(klass, name.to_sym), [], datasource_select)
153
+ Datasource::Base.reflection_select(association_reflection(klass, name.to_sym), [], datasource_select)
129
154
  datasource.select(*datasource_select)
130
155
  select_values = datasource.get_select_values
131
156
 
@@ -139,7 +164,7 @@ module Datasource
139
164
 
140
165
  assoc_records = records.flat_map { |record| record.send(name) }.compact
141
166
  serializer_class._associations.each_pair do |assoc_name, options|
142
- preload_association(assoc_records, assoc_name)
167
+ load_association(assoc_records, assoc_name)
143
168
  end
144
169
  datasource.results(assoc_records)
145
170
  end
@@ -161,10 +186,21 @@ module Datasource
161
186
  ds.scope.select(*ds.get_select_values)
162
187
  end
163
188
 
189
+ def upgrade_records(ds, records)
190
+ load_associations(ds, records)
191
+ ds.results(records)
192
+ end
193
+
194
+ def load_associations(ds, records)
195
+ ds.expose_associations.each_pair do |assoc_name, assoc_select|
196
+ load_association(records, assoc_name)
197
+ end
198
+ end
199
+
164
200
  def get_rows(ds)
165
201
  append_select = []
166
202
  ds.expose_associations.each_pair do |assoc_name, assoc_select|
167
- if reflection = Adapters::ActiveRecord.association_reflection(ds.class.orm_klass, assoc_name.to_sym)
203
+ if reflection = association_reflection(ds.class.orm_klass, assoc_name.to_sym)
168
204
  Datasource::Base.reflection_select(reflection, append_select, [])
169
205
  end
170
206
  end
@@ -176,9 +212,7 @@ module Datasource
176
212
  end
177
213
  scope.includes_values = []
178
214
  scope.to_a.tap do |records|
179
- ds.expose_associations.each_pair do |assoc_name, assoc_select|
180
- Adapters::ActiveRecord.preload_association(records, assoc_name)
181
- end
215
+ load_associations(ds, records)
182
216
  end
183
217
  end
184
218
 
@@ -214,7 +248,7 @@ module Datasource
214
248
  Datasource::Adapters::ActiveRecord
215
249
  end
216
250
 
217
- define_method(:primary_key) do
251
+ define_singleton_method(:primary_key) do
218
252
  klass.primary_key.to_sym
219
253
  end
220
254
  end
@@ -14,6 +14,18 @@ module Datasource
14
14
  self
15
15
  end
16
16
 
17
+ def get_datasource
18
+ datasource = @datasource.new(self)
19
+ datasource.select(*Array(@datasource_select))
20
+ if @datasource_serializer
21
+ select = []
22
+ Datasource::Base.consumer_adapter.to_datasource_select(select, @datasource.orm_klass, @datasource_serializer, nil, datasource.adapter)
23
+
24
+ datasource.select(*select)
25
+ end
26
+ datasource
27
+ end
28
+
17
29
  def datasource_select(*args)
18
30
  @datasource_select = Array(@datasource_select) + args
19
31
  self
@@ -21,14 +33,7 @@ module Datasource
21
33
 
22
34
  def each(&block)
23
35
  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, nil, datasource.adapter)
29
-
30
- datasource.select(*select)
31
- end
36
+ datasource = get_datasource
32
37
 
33
38
  datasource.results.each(&block)
34
39
  else
@@ -64,9 +69,25 @@ module Datasource
64
69
  end
65
70
  end
66
71
 
72
+ def for_serializer(serializer = nil)
73
+ datasource_class = self.class.default_datasource
74
+ pk = datasource_class.primary_key.to_sym
75
+
76
+ scope = self.class
77
+ .with_datasource(datasource_class)
78
+ .for_serializer(serializer).where(pk => send(pk))
79
+
80
+ datasource = scope.get_datasource
81
+ if datasource.can_upgrade?(self)
82
+ datasource.upgrade_records(self).first
83
+ else
84
+ scope.first
85
+ end
86
+ end
87
+
67
88
  module ClassMethods
68
89
  def default_datasource
69
- @default_datasource ||= Class.new(Datasource::From(self))
90
+ @default_datasource ||= Datasource::From(self)
70
91
  end
71
92
 
72
93
  def datasource_module(&block)
@@ -113,8 +134,12 @@ module Datasource
113
134
  false
114
135
  end
115
136
 
137
+ def has_attribute?(record, name)
138
+ record.values.key?(name.to_sym)
139
+ end
140
+
116
141
  def get_assoc_eager_options(klass, name, assoc_select, append_select)
117
- if reflection = Adapters::Sequel.association_reflection(klass, name)
142
+ if reflection = association_reflection(klass, name)
118
143
  self_append_select = []
119
144
  Datasource::Base.reflection_select(reflection, append_select, self_append_select)
120
145
  assoc_class = reflection[:klass]
@@ -144,7 +169,12 @@ module Datasource
144
169
  ds.scope.select(*get_sequel_select_values(ds.get_select_values))
145
170
  end
146
171
 
147
- def get_rows(ds)
172
+ def upgrade_records(ds, records)
173
+ get_final_scope(ds).send :post_load, records
174
+ ds.results(records)
175
+ end
176
+
177
+ def get_final_scope(ds)
148
178
  eager = {}
149
179
  append_select = []
150
180
  ds.expose_associations.each_pair do |assoc_name, assoc_select|
@@ -158,7 +188,11 @@ module Datasource
158
188
  end
159
189
  scope
160
190
  .select_append(*get_sequel_select_values(append_select.map { |v| primary_scope_table(ds) + ".#{v}" }))
161
- .eager(eager).all
191
+ .eager(eager)
192
+ end
193
+
194
+ def get_rows(ds)
195
+ get_final_scope(ds).all
162
196
  end
163
197
 
164
198
  def primary_scope_table(ds)
@@ -187,7 +221,7 @@ module Datasource
187
221
  Datasource::Adapters::Sequel
188
222
  end
189
223
 
190
- define_method(:primary_key) do
224
+ define_singleton_method(:primary_key) do
191
225
  klass.primary_key
192
226
  end
193
227
  end
@@ -2,16 +2,23 @@ module Datasource
2
2
  module Attributes
3
3
  class ComputedAttribute
4
4
  class << self
5
- attr_accessor :_depends
5
+ attr_accessor :_depends, :_loader_depends
6
6
 
7
7
  def inherited(base)
8
8
  base._depends = (_depends || {}).dup # TODO: deep dup?
9
+ base._loader_depends = (_loader_depends || []).dup # TODO: deep dup?
9
10
  end
10
11
 
11
12
  def depends(*args)
12
13
  args.each do |dep|
13
14
  _depends.deep_merge!(dep)
14
15
  end
16
+ _depends.delete_if do |key, value|
17
+ if [:loaders, :loader].include?(key.to_sym)
18
+ self._loader_depends += Array(value).map(&:to_sym)
19
+ true
20
+ end
21
+ end
15
22
  end
16
23
  end
17
24
  end
@@ -65,5 +65,22 @@ module Datasource
65
65
  end
66
66
  @_loaders[name.to_sym] = klass
67
67
  end
68
+
69
+ def self.loaded(name, _options = {}, &block)
70
+ loader(name, _options, &block)
71
+ renamed_existing_method = :"#{name}_without_datasource"
72
+ orm_klass.class_eval do
73
+ alias_method renamed_existing_method, name if method_defined?(name)
74
+ fail "#{name} already defined on #{to_s}, would be overridden by datasource loaded method" if method_defined?(name)
75
+ define_method name do |*args, &block|
76
+ if loaded_values || !method_defined?(renamed_existing_method)
77
+ loaded_values[name.to_sym]
78
+ else
79
+ send(renamed_existing_method, *args, &block)
80
+ end
81
+ end
82
+ end
83
+ computed name, loader: name
84
+ end
68
85
  end
69
86
  end
@@ -24,6 +24,10 @@ module Datasource
24
24
  fail Datasource::Error, "Model class not set for #{name}. You should define it:\nclass YourDatasource\n @orm_klass = MyModelClass\nend"
25
25
  end
26
26
 
27
+ def primary_key
28
+ :id
29
+ end
30
+
27
31
  def reflection_select(reflection, parent_select, assoc_select)
28
32
  # append foreign key depending on assoication
29
33
  if reflection[:macro] == :belongs_to
@@ -81,16 +85,14 @@ module Datasource
81
85
  @expose_associations = {}
82
86
  end
83
87
 
84
- def primary_key
85
- :id
86
- end
87
-
88
88
  def select_all
89
89
  @expose_attributes = self.class._attributes.keys.dup
90
90
  end
91
91
 
92
92
  def select(*names)
93
93
  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?" }
94
+ newly_exposed_attributes = []
95
+ missing_attributes = []
94
96
  names.each do |name|
95
97
  if name.kind_of?(Hash)
96
98
  name.each_pair do |assoc_name, assoc_select|
@@ -100,53 +102,80 @@ module Datasource
100
102
  @expose_associations[assoc_name] += Array(assoc_select)
101
103
  @expose_associations[assoc_name].uniq!
102
104
  else
105
+ missing_attributes << assoc_name
103
106
  failure.call(assoc_name)
104
107
  end
105
108
  end
106
109
  else
107
110
  name = name.to_s
108
111
  if self.class._attributes.key?(name)
109
- @expose_attributes.push(name)
112
+ unless @expose_attributes.include?(name)
113
+ @expose_attributes.push(name)
114
+ newly_exposed_attributes.push(name)
115
+ end
110
116
  else
117
+ missing_attributes << name
111
118
  failure.call(name)
112
119
  end
113
120
  end
114
121
  end
115
- @expose_attributes.uniq!
122
+ update_dependencies(newly_exposed_attributes) unless newly_exposed_attributes.empty?
123
+ fail_missing_attributes(missing_attributes) unless missing_attributes.blank?
116
124
  self
117
125
  end
118
126
 
127
+ def fail_missing_attributes(names)
128
+ message = if names.size > 1
129
+ "attributes or associations #{names.join(', ')} don't exist "
130
+ else
131
+ "attribute or association #{names.first} doesn't exist "
132
+ end
133
+ message += "for #{self.class.orm_klass.name}, "
134
+ message += "did you forget to call \"computed :#{name}, <dependencies>\" in your datasource_module?"
135
+ fail Datasource::Error, message
136
+ end
137
+
138
+ def update_dependencies(names)
139
+ scope_table = adapter.primary_scope_table(self)
140
+
141
+ self.class._attributes.values.each do |att|
142
+ next unless names.include?(att[:name])
143
+ next unless att[:klass]
144
+
145
+ if att[:klass].ancestors.include?(Attributes::ComputedAttribute)
146
+ att[:klass]._depends.each_pair do |key, value|
147
+ if key.to_s == scope_table
148
+ select(*value)
149
+ else
150
+ select(key => value)
151
+ end
152
+ end
153
+ elsif att[:klass].ancestors.include?(Attributes::QueryAttribute)
154
+ att[:klass]._depends.each do |name|
155
+ next if name == scope_table
156
+ adapter.ensure_table_join!(self, name, att)
157
+ end
158
+ end
159
+ end
160
+ end
161
+
119
162
  def get_select_values
120
163
  scope_table = adapter.primary_scope_table(self)
164
+
165
+ # SQL select values
121
166
  select_values = Set.new
122
- select_values.add("#{scope_table}.#{primary_key}")
167
+ select_values.add("#{scope_table}.#{self.class.primary_key}")
123
168
 
124
169
  self.class._attributes.values.each do |att|
125
170
  if attribute_exposed?(att[:name])
126
171
  if att[:klass] == nil
127
172
  select_values.add("#{scope_table}.#{att[:name]}")
128
- elsif att[:klass].ancestors.include?(Attributes::ComputedAttribute)
129
- att[:klass]._depends.keys.map(&:to_s).each do |name|
130
- next if name == scope_table
131
- next if name == "loaders"
132
- adapter.ensure_table_join!(self, name, att)
133
- end
134
- att[:klass]._depends.each_pair do |table, names|
135
- next if table.to_sym == :loaders
136
- Array(names).each do |name|
137
- select_values.add("#{table}.#{name}")
138
- end
139
- # TODO: handle depends on virtual attribute
140
- end
141
173
  elsif att[:klass].ancestors.include?(Attributes::QueryAttribute)
142
174
  select_values.add("(#{att[:klass].select_value}) as #{att[:name]}")
143
- att[:klass]._depends.each do |name|
144
- next if name == scope_table
145
- adapter.ensure_table_join!(self, name, att)
146
- end
147
175
  end
148
176
  end
149
177
  end
178
+
150
179
  select_values.to_a
151
180
  end
152
181
 
@@ -154,6 +183,27 @@ module Datasource
154
183
  @expose_attributes.include?(name.to_s)
155
184
  end
156
185
 
186
+ # assume records have all attributes selected (default ORM record)
187
+ def can_upgrade?(records)
188
+ query_attributes = @expose_attributes.select do |name|
189
+ klass = self.class._attributes[name][:klass]
190
+ if klass
191
+ klass.ancestors.include?(Attributes::QueryAttribute)
192
+ end
193
+ end
194
+
195
+ return true if query_attributes.empty?
196
+ Array(records).all? do |record|
197
+ query_attributes.all? do |name|
198
+ adapter.has_attribute?(record, name)
199
+ end
200
+ end
201
+ end
202
+
203
+ def upgrade_records(records)
204
+ adapter.upgrade_records(self, Array(records))
205
+ end
206
+
157
207
  def results(rows = nil)
158
208
  rows ||= adapter.get_rows(self)
159
209
 
@@ -166,23 +216,20 @@ module Datasource
166
216
  next if rows.empty?
167
217
 
168
218
  if att[:klass].ancestors.include?(Attributes::ComputedAttribute)
169
- loaders = att[:klass]._depends[:loaders]
170
- if loaders
171
- Array(loaders).each do |name|
172
- if loader = self.class._loaders[name]
173
- if loaded_values = loader.load(rows.map(&primary_key), rows, @scope)
174
- unless rows.first.loaded_values
175
- rows.each do |row|
176
- row.loaded_values = {}
177
- end
178
- end
219
+ att[:klass]._loader_depends.each do |name|
220
+ if loader = self.class._loaders[name]
221
+ if loaded_values = loader.load(rows.map(&self.class.primary_key), rows, @scope)
222
+ unless rows.first.loaded_values
179
223
  rows.each do |row|
180
- row.loaded_values[name] = loaded_values[row.send(primary_key)] || loader.default_value
224
+ row.loaded_values = {}
181
225
  end
182
226
  end
183
- else
184
- raise Datasource::Error, "loader with name :#{name} could not be found"
227
+ rows.each do |row|
228
+ row.loaded_values[name] = loaded_values[row.send(self.class.primary_key)] || loader.default_value
229
+ end
185
230
  end
231
+ else
232
+ raise Datasource::Error, "loader with name :#{name} could not be found"
186
233
  end
187
234
  end
188
235
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: datasource
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Berdajs