datasource 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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