datasource 0.0.1 → 0.0.2

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.
@@ -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
- def self.computed_attribute(name, deps, &block)
33
- klass = Class.new(Attributes::ComputedAttribute) do
34
- depends deps
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
- define_method(:value, &block)
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
- def self.query_attribute(name, deps, &block)
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
- define_method(:select_value, &block)
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
@@ -1,96 +1,189 @@
1
1
  module Datasource
2
2
  class Base
3
3
  class << self
4
- attr_accessor :_attributes, :_virtual_attributes, :_associations
5
- attr_accessor :adapter
4
+ attr_accessor :_attributes, :_associations, :_update_scope, :_loaders
5
+ attr_writer :orm_klass
6
6
 
7
7
  def inherited(base)
8
- base._attributes = (_attributes || []).dup
9
- @adapter ||= Datasource::Adapters::ActiveRecord
10
- self.send :include, @adapter
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
- @_attributes.push name: name.to_s, klass: klass
53
+ att = { name: name.to_s, klass: klass }
54
+ @_attributes[att[:name]] = att
19
55
  end
20
56
 
21
- def includes_many(name, klass, foreign_key)
22
- @_attributes.push name: name.to_s, klass: klass, foreign_key: foreign_key.to_s, id_key: self::ID_KEY
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 = 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
- @datasource_data = {}
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
- names = names.flat_map do |name|
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
- # datasource data
36
- name.each_pair do |k, v|
37
- @datasource_data[k.to_s] = v
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 = (@expose_attributes + names.map(&:to_s)).uniq
113
+ @expose_attributes.uniq!
45
114
  self
46
115
  end
47
116
 
48
- def attribute_exposed?(name)
49
- @expose_attributes.include?(name)
50
- end
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
- def to_query
53
- to_query(@scope)
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 results
57
- rows = get_rows(@scope)
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
- computed_expose_attributes = []
65
- datasources = {}
155
+ def results(rows = nil)
156
+ rows ||= get_rows
66
157
 
67
158
  @expose_attributes.each do |name|
68
- att = attribute_map[name]
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 klass.ancestors.include?(Attributes::ComputedAttribute)
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
- # TODO: field names...
81
- rows.each do |row|
82
- computed_expose_attributes.each do |att|
83
- klass = att[:klass]
84
- if klass
85
- row[att[:name]] = klass.new(row).value
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