datasource 0.0.6 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 89a34ecb59d8f4183295c43b4badd33aca5f5e62
4
- data.tar.gz: b408320273ce49236bd022d7db00c1fce06bdfd5
3
+ metadata.gz: 50befc547d1d02c68a29f7a73b5a75ae3038ee16
4
+ data.tar.gz: a0866c2dfbfa920696d27d2f83ef4252088c5fe7
5
5
  SHA512:
6
- metadata.gz: 0db668df6f4ae71223655a544d59b805e6bdd2694ab540f37dacc7962700d427799f00308d0cdf03a180a7e45f7d3891ff795e73def10c180bf9df5d92942df3
7
- data.tar.gz: c69ae3fe12467516c7f24d090e7240a398e9e7d341dd82a1c54b459c57dd4fb2f9c796a6180a85e02fc304ea45bbf96361579af3249974df4fd73cc0a02dd997
6
+ metadata.gz: 1d628e4fc124ece6b97cd373438009a27b37d78f13bc18fbef0fcdc410d1c47fdff43f174feef8f06de694141271950c05449c1da8644c8cf3d150fb22edfcd0
7
+ data.tar.gz: f334b8022a8db68d276d22168aaf4e296de0dd9a496c6df22a0a59323a87a2332f0141fa69a4806430f6e9312cfdf983ec560154c7cc07f0eb7d479858a68b56
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # Datasource
2
2
 
3
+ **Make sure you are reading the README corresponding to the version your are using**
4
+
3
5
  Automatically preload your ORM records for your serializer.
4
6
 
5
- ## Install
7
+ #### Install
6
8
 
7
9
  Add to Gemfile
8
10
 
@@ -10,9 +12,12 @@ Add to Gemfile
10
12
  gem 'datasource'
11
13
  ```
12
14
 
13
- And `bundle install`.
15
+ ```
16
+ bundle install
17
+ rails g datasource:install
18
+ ```
14
19
 
15
- Run install generator:
20
+ #### Upgrade
16
21
 
17
22
  ```
18
23
  rails g datasource:install
@@ -27,25 +32,18 @@ rails g datasource:install
27
32
 
28
33
  - active_model_serializers
29
34
 
30
- ## Basic Usage
31
-
32
- ### Attributes
33
- You don't have to do anything special.
34
-
35
- ```ruby
36
- class UserSerializer < ActiveModel::Serializer
37
- attributes :id, :email
38
- end
39
- ```
40
-
41
- But you get an optimized query for free:
35
+ ## Simple Mode
42
36
 
43
- ```sql
44
- SELECT id, email FROM users
45
- ```
37
+ Datasource is configured to run in Simple mode by default, which makes it easier
38
+ to start with, but disables some advanced optimizations. See
39
+ [Advanced mode](https://github.com/mrbrdo/datasource/wiki/Advanced-mode) for more
40
+ information after you understand Simple mode.
46
41
 
47
42
  ### Associations
48
- You don't have to do anything special.
43
+
44
+ The most noticable magic effect of using Datasource in Simple mode (Advanced mode
45
+ has other benefits) is that associations will automatically be preloaded using a
46
+ single query.
49
47
 
50
48
  ```ruby
51
49
  class PostSerializer < ActiveModel::Serializer
@@ -57,45 +55,13 @@ class UserSerializer < ActiveModel::Serializer
57
55
  has_many :posts
58
56
  end
59
57
  ```
60
-
61
- But you get automatic association preloading ("includes") with optimized queries for free:
62
-
63
58
  ```sql
64
- SELECT id FROM users
65
- SELECT id, title, user_id FROM posts WHERE id IN (?)
59
+ SELECT users.* FROM users
60
+ SELECT posts.* FROM posts WHERE id IN (?)
66
61
  ```
67
62
 
68
- ### Model Methods / Virtual Attributes
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.
70
-
71
- ```ruby
72
- class User < ActiveRecord::Base
73
- datasource_module do
74
- computed :first_name_initial, :first_name
75
- computed :both_initials, :first_name, :last_name
76
- end
77
-
78
- # method can be in model
79
- def first_name_initial
80
- first_name[0].upcase
81
- end
82
- end
83
-
84
- class UserSerializer < ActiveModel::Serializer
85
- attributes :first_name_initial, :last_name_initial
86
-
87
- # method can also be in serializer
88
- def both_initials
89
- object.last_name[0].upcase + object.last_name[0].upcase
90
- end
91
- end
92
- ```
93
-
94
- ```sql
95
- SELECT first_name, last_name FROM users
96
- ```
97
-
98
- You will be reminded with an exception if you forget to do this.
63
+ This means you **do not** need to call `includes` yourself. It will be done
64
+ automatically by Datasource.
99
65
 
100
66
  ### Show action
101
67
 
@@ -128,8 +94,6 @@ class UsersController < ApplicationController
128
94
  end
129
95
  ```
130
96
 
131
- ## Advanced Usage
132
-
133
97
  ### Query attribute
134
98
 
135
99
  You can specify a SQL fragment for `SELECT` and use that as an attribute on your
@@ -150,7 +114,7 @@ end
150
114
  ```
151
115
 
152
116
  ```sql
153
- SELECT users.id, (users.first_name || ' ' || users.last_name) AS full_name FROM users
117
+ SELECT users.*, (users.first_name || ' ' || users.last_name) AS full_name FROM users
154
118
  ```
155
119
 
156
120
  Note: If you need data from another table, use a join in a loader (see below).
@@ -161,8 +125,13 @@ You might want to have some more complex preloading logic. In that case you can
161
125
  A loader will receive ids of the records, and needs to return a hash.
162
126
  The key of the hash must be the id of the record for which the value is.
163
127
 
164
- A loader will only be executed if a computed attribute depends on it. If an attribute depends
165
- on multiple loaders, pass an array of loaders like so `computed :attr, loaders: [:loader1, :loader2]`.
128
+ A loader will only be executed if a computed attribute depends on it. See
129
+ [Advanced mode](https://github.com/mrbrdo/datasource/wiki/Advanced-mode) for
130
+ information about computed attributes (but this works the same way in Simple mode).
131
+ A more simple alternative to loader which doesn't require computed attributes is to use
132
+ [Loaded](#loaded).
133
+ If an attribute depends on multiple loaders, pass an array of loaders like
134
+ so `computed :attr, loaders: [:loader1, :loader2]`.
166
135
 
167
136
  Be careful that if your hash does not contain a value for the object ID, the loaded value
168
137
  will be nil. However you can use the `default` option for such cases (see below example).
@@ -191,7 +160,7 @@ end
191
160
  ```
192
161
 
193
162
  ```sql
194
- SELECT users.id FROM users
163
+ SELECT users.* FROM users
195
164
  SELECT user_id, COUNT(id) FROM posts WHERE user_id IN (?)
196
165
  ```
197
166
 
@@ -227,8 +196,8 @@ end
227
196
 
228
197
  ### Loaded
229
198
 
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.
199
+ Loaded is the same as loader, but it automatically creates a computed attribute
200
+ and defines a method with the same name on your model.
232
201
 
233
202
  Here is the previous example with `loaded` instead of `loader`:
234
203
 
@@ -265,10 +234,10 @@ class User < ActiveRecord::Base
265
234
  .group(:user_id)
266
235
  .pluck("user_id, COUNT(id)")
267
236
  end
237
+ end
268
238
 
269
- def post_count
270
- posts.count
271
- end
239
+ def post_count
240
+ posts.count
272
241
  end
273
242
  end
274
243
 
@@ -279,3 +248,17 @@ end
279
248
  User.first.post_count # <- your method will be called
280
249
 
281
250
  ```
251
+
252
+ ## Getting Help
253
+
254
+ If you find a bug, please report an [Issue](https://github.com/mrbrdo/datasource/issues/new).
255
+
256
+ If you have a question, you can also open an Issue.
257
+
258
+ ## Contributing
259
+
260
+ 1. Fork it ( https://github.com/mrbrdo/datasource/fork )
261
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
262
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
263
+ 4. Push to the branch (`git push origin my-new-feature`)
264
+ 5. Create a new Pull Request
@@ -136,22 +136,20 @@ module Datasource
136
136
  end
137
137
  end
138
138
 
139
- def load_association(records, name)
139
+ def load_association(records, name, assoc_select)
140
140
  return if records.empty?
141
141
  return if records.first.association(name.to_sym).loaded?
142
142
  klass = records.first.class
143
143
  if reflection = klass.reflections[name.to_sym]
144
144
  assoc_class = association_klass(reflection)
145
145
  datasource_class = assoc_class.default_datasource
146
- # TODO: extract serializer_class from parent serializer association
147
- serializer_class = Datasource::Base.consumer_adapter.get_serializer_for(assoc_class)
148
146
 
149
- # TODO: can we make it use datasource scope (with_serializer)? like Sequel
150
147
  scope = assoc_class.all
151
148
  datasource = datasource_class.new(scope)
152
- datasource_select = serializer_class._attributes.dup
153
- Datasource::Base.reflection_select(association_reflection(klass, name.to_sym), [], datasource_select)
154
- datasource.select(*datasource_select)
149
+ assoc_select_attributes = assoc_select.reject { |att| att.kind_of?(Hash) }
150
+ assoc_select_associations = assoc_select.select { |att| att.kind_of?(Hash) }
151
+ Datasource::Base.reflection_select(association_reflection(klass, name.to_sym), [], assoc_select_attributes)
152
+ datasource.select(*assoc_select_attributes)
155
153
  select_values = datasource.get_select_values
156
154
 
157
155
  begin
@@ -163,8 +161,10 @@ module Datasource
163
161
  end
164
162
 
165
163
  assoc_records = records.flat_map { |record| record.send(name) }.compact
166
- serializer_class._associations.each_pair do |assoc_name, options|
167
- load_association(assoc_records, assoc_name)
164
+ assoc_select_associations.each do |assocs|
165
+ assocs.each_pair do |assoc_name, assoc_select|
166
+ load_association(assoc_records, assoc_name, assoc_select)
167
+ end
168
168
  end
169
169
  datasource.results(assoc_records)
170
170
  end
@@ -193,7 +193,7 @@ module Datasource
193
193
 
194
194
  def load_associations(ds, records)
195
195
  ds.expose_associations.each_pair do |assoc_name, assoc_select|
196
- load_association(records, assoc_name)
196
+ load_association(records, assoc_name, assoc_select)
197
197
  end
198
198
  end
199
199
 
@@ -149,7 +149,7 @@ module Datasource
149
149
  {
150
150
  name => ->(ds) {
151
151
  ds.with_datasource(datasource_class)
152
- .datasource_select(*(self_append_select + assoc_select))
152
+ .datasource_select(*(assoc_select + self_append_select))
153
153
  }
154
154
  }
155
155
  else
@@ -83,14 +83,29 @@ module Datasource
83
83
  end
84
84
  @expose_attributes = []
85
85
  @expose_associations = {}
86
+ @select_all_columns = false
87
+ end
88
+
89
+ def select_all_columns
90
+ column_attributes = self.class._attributes.values.select do |att|
91
+ att[:klass].nil?
92
+ end
93
+ columns = column_attributes.map { |att| att[:name] }
94
+ select(*columns)
95
+ @select_all_columns = true
96
+
97
+ columns
86
98
  end
87
99
 
88
100
  def select_all
89
- @expose_attributes = self.class._attributes.keys.dup
101
+ attributes = self.class._attributes.keys
102
+ select(*attributes)
103
+ @select_all_columns = true
104
+
105
+ attributes
90
106
  end
91
107
 
92
108
  def select(*names)
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
109
  newly_exposed_attributes = []
95
110
  missing_attributes = []
96
111
  names.each do |name|
@@ -99,23 +114,23 @@ module Datasource
99
114
  assoc_name = assoc_name.to_s
100
115
  if self.class._associations.key?(assoc_name)
101
116
  @expose_associations[assoc_name] ||= []
102
- @expose_associations[assoc_name] += Array(assoc_select)
117
+ @expose_associations[assoc_name].concat(Array(assoc_select))
103
118
  @expose_associations[assoc_name].uniq!
104
119
  else
105
120
  missing_attributes << assoc_name
106
- failure.call(assoc_name)
107
121
  end
108
122
  end
109
123
  else
110
124
  name = name.to_s
111
- if self.class._attributes.key?(name)
125
+ if name == "*"
126
+ newly_exposed_attributes.concat(select_all_columns.map(&:to_s))
127
+ elsif self.class._attributes.key?(name)
112
128
  unless @expose_attributes.include?(name)
113
129
  @expose_attributes.push(name)
114
130
  newly_exposed_attributes.push(name)
115
131
  end
116
132
  else
117
133
  missing_attributes << name
118
- failure.call(name)
119
134
  end
120
135
  end
121
136
  end
@@ -161,17 +176,28 @@ module Datasource
161
176
 
162
177
  def get_select_values
163
178
  scope_table = adapter.primary_scope_table(self)
164
-
165
- # SQL select values
166
179
  select_values = Set.new
167
- select_values.add("#{scope_table}.#{self.class.primary_key}")
168
180
 
169
- self.class._attributes.values.each do |att|
170
- if attribute_exposed?(att[:name])
171
- if att[:klass] == nil
172
- select_values.add("#{scope_table}.#{att[:name]}")
173
- elsif att[:klass].ancestors.include?(Attributes::QueryAttribute)
174
- select_values.add("(#{att[:klass].select_value}) as #{att[:name]}")
181
+ if @select_all_columns
182
+ select_values.add("#{scope_table}.*")
183
+
184
+ self.class._attributes.values.each do |att|
185
+ if att[:klass] && attribute_exposed?(att[:name])
186
+ if att[:klass].ancestors.include?(Attributes::QueryAttribute)
187
+ select_values.add("(#{att[:klass].select_value}) as #{att[:name]}")
188
+ end
189
+ end
190
+ end
191
+ else
192
+ select_values.add("#{scope_table}.#{self.class.primary_key}")
193
+
194
+ self.class._attributes.values.each do |att|
195
+ if attribute_exposed?(att[:name])
196
+ if att[:klass] == nil
197
+ select_values.add("#{scope_table}.#{att[:name]}")
198
+ elsif att[:klass].ancestors.include?(Attributes::QueryAttribute)
199
+ select_values.add("(#{att[:klass].select_value}) as #{att[:name]}")
200
+ end
175
201
  end
176
202
  end
177
203
  end
@@ -207,29 +233,33 @@ module Datasource
207
233
  def results(rows = nil)
208
234
  rows ||= adapter.get_rows(self)
209
235
 
210
- @expose_attributes.each do |name|
211
- att = self.class._attributes[name]
212
- fail Datasource::Error, "attribute #{name} doesn't exist for #{self.class.orm_klass.name}, did you forget to call \"computed :#{name}, :db_column_dependency\" in your datasource_module? See https://github.com/mrbrdo/datasource#model-methods--virtual-attributes" unless att
213
- klass = att[:klass]
214
- next unless klass
215
-
216
- next if rows.empty?
217
-
218
- if att[:klass].ancestors.include?(Attributes::ComputedAttribute)
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
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
223
251
  rows.each do |row|
224
- row.loaded_values = {}
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
225
258
  end
226
259
  end
227
- rows.each do |row|
228
- row.loaded_values[name] = loaded_values[row.send(self.class.primary_key)] || loader.default_value
229
- end
260
+ else
261
+ raise Datasource::Error, "loader with name :#{name} could not be found"
230
262
  end
231
- else
232
- raise Datasource::Error, "loader with name :#{name} could not be found"
233
263
  end
234
264
  end
235
265
  end
@@ -0,0 +1,23 @@
1
+ module Datasource
2
+ module Configuration
3
+ include ActiveSupport::Configurable
4
+ extend ActiveSupport::Concern
5
+
6
+ included do |base|
7
+ base.config.adapters = Configuration.default_adapters
8
+ base.config.simple_mode = false
9
+ end
10
+
11
+ def self.default_adapters
12
+ default_adapters = []
13
+ if defined? ActiveRecord
14
+ default_adapters.push(:activerecord)
15
+ elsif defined? Sequel
16
+ default_adapters.push(:sequel)
17
+ end
18
+ if defined? ActiveModel::Serializer
19
+ default_adapters.push(:ams)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -36,6 +36,7 @@ module Datasource
36
36
  def to_datasource_select(result, klass, serializer = nil, serializer_assoc = nil, adapter = nil)
37
37
  adapter ||= Datasource::Base.default_adapter
38
38
  serializer ||= get_serializer_for(klass, serializer_assoc)
39
+ result.unshift("*") if Datasource.config.simple_mode
39
40
  result.concat(serializer._attributes)
40
41
  result_assocs = {}
41
42
  result.push(result_assocs)
data/lib/datasource.rb CHANGED
@@ -1,6 +1,8 @@
1
+ require 'datasource/configuration'
1
2
  module Datasource
2
3
  Error = Class.new(StandardError)
3
4
  RecursionError = Class.new(StandardError)
5
+ include Configuration
4
6
 
5
7
  AdapterPaths = {
6
8
  activerecord: 'datasource/adapters/active_record',
@@ -12,25 +14,23 @@ module Datasource
12
14
 
13
15
  module_function
14
16
  def load(*adapters)
15
- if adapters.empty?
16
- adapters = []
17
- if defined? ActiveRecord
18
- adapters.push(:activerecord)
19
- elsif defined? Sequel
20
- adapters.push(:sequel)
21
- end
22
- if defined? ActiveModel::Serializer
23
- adapters.push(:ams)
24
- end
17
+ unless adapters.empty?
18
+ warn "[DEPRECATION] passing adapters to Datasource.load is deprecated. Use Datasource.setup instead."
19
+ config.adapters = adapters
25
20
  end
26
21
 
27
- adapters.each do |adapter|
22
+ config.adapters.each do |adapter|
28
23
  adapter = AdapterPaths[adapter]
29
24
  adapter = AdapterPaths[adapter] if adapter.is_a?(Symbol)
30
25
  require adapter
31
26
  end
32
27
  end
33
28
 
29
+ def setup
30
+ yield(config)
31
+ load
32
+ end
33
+
34
34
  def orm_adapters
35
35
  @orm_adapters ||= begin
36
36
  Datasource::Adapters.constants.map { |name| Datasource::Adapters.const_get(name) }
@@ -1 +1,10 @@
1
- Datasource.load(:activerecord, :active_model_serializers)
1
+ Datasource.setup do |config|
2
+ # Adapters to load
3
+ # Available ORM adapters: activerecord, sequel
4
+ # Available Serializer adapters: active_model_serializers
5
+ config.adapters = [:activerecord, :active_model_serializers]
6
+
7
+ # Enable simple mode, which will always select all model database columns,
8
+ # making Datasource easier to use. See documentation for details.
9
+ config.simple_mode = true
10
+ 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.6
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Berdajs
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0.9'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '4.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '4.0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rspec
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -80,20 +94,6 @@ dependencies:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0.9'
83
- - !ruby/object:Gem::Dependency
84
- name: activesupport
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '4'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '4'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: sequel
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -139,6 +139,7 @@ files:
139
139
  - lib/datasource/attributes/loader.rb
140
140
  - lib/datasource/attributes/query_attribute.rb
141
141
  - lib/datasource/base.rb
142
+ - lib/datasource/configuration.rb
142
143
  - lib/datasource/consumer_adapters/active_model_serializers.rb
143
144
  - lib/datasource/serializer.rb
144
145
  - lib/generators/datasource/install_generator.rb