pakyow-data 1.0.0.rc1
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 +7 -0
- data/CHANGELOG.md +0 -0
- data/LICENSE +4 -0
- data/README.md +29 -0
- data/lib/pakyow/data/adapters/abstract.rb +58 -0
- data/lib/pakyow/data/adapters/sql/commands.rb +58 -0
- data/lib/pakyow/data/adapters/sql/dataset_methods.rb +29 -0
- data/lib/pakyow/data/adapters/sql/differ.rb +76 -0
- data/lib/pakyow/data/adapters/sql/migrator/adapter_methods.rb +95 -0
- data/lib/pakyow/data/adapters/sql/migrator.rb +181 -0
- data/lib/pakyow/data/adapters/sql/migrators/automator.rb +49 -0
- data/lib/pakyow/data/adapters/sql/migrators/finalizer.rb +96 -0
- data/lib/pakyow/data/adapters/sql/runner.rb +49 -0
- data/lib/pakyow/data/adapters/sql/source_extension.rb +31 -0
- data/lib/pakyow/data/adapters/sql/types.rb +50 -0
- data/lib/pakyow/data/adapters/sql.rb +247 -0
- data/lib/pakyow/data/behavior/config.rb +28 -0
- data/lib/pakyow/data/behavior/lookup.rb +75 -0
- data/lib/pakyow/data/behavior/serialization.rb +40 -0
- data/lib/pakyow/data/connection.rb +103 -0
- data/lib/pakyow/data/container.rb +273 -0
- data/lib/pakyow/data/errors.rb +169 -0
- data/lib/pakyow/data/framework.rb +42 -0
- data/lib/pakyow/data/helpers.rb +11 -0
- data/lib/pakyow/data/lookup.rb +85 -0
- data/lib/pakyow/data/migrator.rb +182 -0
- data/lib/pakyow/data/object.rb +98 -0
- data/lib/pakyow/data/proxy.rb +262 -0
- data/lib/pakyow/data/result.rb +53 -0
- data/lib/pakyow/data/sources/abstract.rb +82 -0
- data/lib/pakyow/data/sources/ephemeral.rb +72 -0
- data/lib/pakyow/data/sources/relational/association.rb +43 -0
- data/lib/pakyow/data/sources/relational/associations/belongs_to.rb +47 -0
- data/lib/pakyow/data/sources/relational/associations/has_many.rb +54 -0
- data/lib/pakyow/data/sources/relational/associations/has_one.rb +54 -0
- data/lib/pakyow/data/sources/relational/associations/through.rb +67 -0
- data/lib/pakyow/data/sources/relational/command.rb +531 -0
- data/lib/pakyow/data/sources/relational/migrator.rb +101 -0
- data/lib/pakyow/data/sources/relational.rb +587 -0
- data/lib/pakyow/data/subscribers/adapters/memory.rb +153 -0
- data/lib/pakyow/data/subscribers/adapters/redis/pipeliner.rb +45 -0
- data/lib/pakyow/data/subscribers/adapters/redis/scripts/_shared.lua +73 -0
- data/lib/pakyow/data/subscribers/adapters/redis/scripts/expire.lua +16 -0
- data/lib/pakyow/data/subscribers/adapters/redis/scripts/persist.lua +15 -0
- data/lib/pakyow/data/subscribers/adapters/redis/scripts/register.lua +37 -0
- data/lib/pakyow/data/subscribers/adapters/redis.rb +209 -0
- data/lib/pakyow/data/subscribers.rb +148 -0
- data/lib/pakyow/data/tasks/bootstrap.rake +18 -0
- data/lib/pakyow/data/tasks/create.rake +22 -0
- data/lib/pakyow/data/tasks/drop.rake +32 -0
- data/lib/pakyow/data/tasks/finalize.rake +56 -0
- data/lib/pakyow/data/tasks/migrate.rake +24 -0
- data/lib/pakyow/data/tasks/reset.rake +18 -0
- data/lib/pakyow/data/types.rb +37 -0
- data/lib/pakyow/data.rb +27 -0
- data/lib/pakyow/environment/data/auto_migrate.rb +31 -0
- data/lib/pakyow/environment/data/config.rb +54 -0
- data/lib/pakyow/environment/data/connections.rb +76 -0
- data/lib/pakyow/environment/data/memory_db.rb +23 -0
- data/lib/pakyow/validations/unique.rb +26 -0
- metadata +186 -0
@@ -0,0 +1,182 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "uri"
|
4
|
+
require "sequel"
|
5
|
+
|
6
|
+
require "pakyow/support/inflector"
|
7
|
+
|
8
|
+
module Pakyow
|
9
|
+
module Data
|
10
|
+
class Migrator
|
11
|
+
def initialize(connection)
|
12
|
+
@connection = connection
|
13
|
+
end
|
14
|
+
|
15
|
+
IVARS_TO_DISCONNECT = %i(@runner @migrator).freeze
|
16
|
+
|
17
|
+
def disconnect!
|
18
|
+
IVARS_TO_DISCONNECT.each do |ivar|
|
19
|
+
if instance_variable_defined?(ivar) && value = instance_variable_get(ivar)
|
20
|
+
value.disconnect!
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
@connection.disconnect
|
25
|
+
end
|
26
|
+
|
27
|
+
def create!
|
28
|
+
migrator.create!
|
29
|
+
disconnect!
|
30
|
+
|
31
|
+
# Recreate the connection, since we just created the database it's supposed to connect to.
|
32
|
+
#
|
33
|
+
@connection = Connection.new(
|
34
|
+
opts: @connection.opts,
|
35
|
+
type: @connection.type,
|
36
|
+
name: @connection.name
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
def drop!
|
41
|
+
migrator.drop!
|
42
|
+
end
|
43
|
+
|
44
|
+
def migrate!
|
45
|
+
if migrations_to_run?
|
46
|
+
runner.run!
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def auto_migrate!
|
51
|
+
migrator.auto_migrate!
|
52
|
+
end
|
53
|
+
|
54
|
+
def finalize!
|
55
|
+
migrator.finalize!.each do |filename, content|
|
56
|
+
FileUtils.mkdir_p(migration_path)
|
57
|
+
|
58
|
+
File.open(File.join(migration_path, filename), "w+") do |file|
|
59
|
+
file.write <<~CONTENT
|
60
|
+
Pakyow.migration do
|
61
|
+
#{content.to_s.split("\n").map { |line| " #{line}" }.join("\n")}
|
62
|
+
end
|
63
|
+
CONTENT
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def migrator
|
71
|
+
@migrator = self.class.migrator_for_adapter(Support.inflector.classify(@connection.type), :Migrator).new(
|
72
|
+
@connection, sources: sources
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
def runner
|
77
|
+
@runner = self.class.migrator_for_adapter(Support.inflector.classify(@connection.type), :Runner).new(
|
78
|
+
@connection, migration_path
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
82
|
+
def sources
|
83
|
+
Pakyow.apps.reject(&:rescued?).flat_map { |app|
|
84
|
+
app.data.containers.flat_map(&:sources).concat(
|
85
|
+
app.plugs.flat_map { |plug|
|
86
|
+
plug.data.containers.flat_map(&:sources)
|
87
|
+
}
|
88
|
+
)
|
89
|
+
}.select { |source|
|
90
|
+
source.connection == @connection.name && source.adapter == @connection.type
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
def migrations
|
95
|
+
Dir.glob(File.join(migration_path, "*.rb"))
|
96
|
+
end
|
97
|
+
|
98
|
+
def migrations_to_run?
|
99
|
+
migrations.count > 0
|
100
|
+
end
|
101
|
+
|
102
|
+
def migration_path
|
103
|
+
File.join(Pakyow.config.data.migration_path, "#{@connection.type}/#{@connection.name}")
|
104
|
+
end
|
105
|
+
|
106
|
+
def track_exported_migrations
|
107
|
+
initial_migrations = migrations
|
108
|
+
yield
|
109
|
+
migrations - initial_migrations
|
110
|
+
end
|
111
|
+
|
112
|
+
class << self
|
113
|
+
def migrator_for_adapter(adapter, type = :Migrator)
|
114
|
+
Adapters.const_get(Support.inflector.camelize(adapter)).const_get(type)
|
115
|
+
end
|
116
|
+
|
117
|
+
def connect(adapter:, connection:, connection_overrides: {})
|
118
|
+
adapter = if adapter
|
119
|
+
adapter.to_sym
|
120
|
+
else
|
121
|
+
Pakyow.config.data.default_adapter
|
122
|
+
end
|
123
|
+
|
124
|
+
connection = if connection
|
125
|
+
connection.to_sym
|
126
|
+
else
|
127
|
+
Pakyow.config.data.default_connection
|
128
|
+
end
|
129
|
+
|
130
|
+
connection_opts = Connection.parse_connection_string(
|
131
|
+
Pakyow.config.data.connections.send(adapter)[connection]
|
132
|
+
)
|
133
|
+
|
134
|
+
merge_connection_overrides!(connection_opts, connection_overrides)
|
135
|
+
new(Connection.new(opts: connection_opts, type: adapter, name: connection))
|
136
|
+
end
|
137
|
+
|
138
|
+
def connect_global(adapter:, connection:, connection_overrides: {})
|
139
|
+
adapter = if adapter
|
140
|
+
adapter.to_sym
|
141
|
+
else
|
142
|
+
Pakyow.config.data.default_adapter
|
143
|
+
end
|
144
|
+
|
145
|
+
connection = if connection
|
146
|
+
connection.to_sym
|
147
|
+
else
|
148
|
+
Pakyow.config.data.default_connection
|
149
|
+
end
|
150
|
+
|
151
|
+
connection_opts = Connection.parse_connection_string(
|
152
|
+
Pakyow.config.data.connections.send(adapter)[connection]
|
153
|
+
)
|
154
|
+
|
155
|
+
merge_connection_overrides!(connection_opts, connection_overrides)
|
156
|
+
globalize_connection_opts!(adapter, connection_opts)
|
157
|
+
|
158
|
+
connect(
|
159
|
+
adapter: adapter,
|
160
|
+
connection: connection,
|
161
|
+
connection_overrides: connection_opts
|
162
|
+
)
|
163
|
+
end
|
164
|
+
|
165
|
+
def globalize_connection_opts!(adapter, connection_opts)
|
166
|
+
migrator_for_adapter(adapter).globalize_connection_opts!(connection_opts)
|
167
|
+
end
|
168
|
+
|
169
|
+
def merge_connection_overrides!(connection_opts, connection_overrides)
|
170
|
+
connection_overrides.each do |key, value|
|
171
|
+
key = key.to_sym
|
172
|
+
connection_opts[key] = if value.is_a?(Proc)
|
173
|
+
value.call(connection_opts[key])
|
174
|
+
else
|
175
|
+
value
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pakyow/support/bindable"
|
4
|
+
require "pakyow/support/class_state"
|
5
|
+
require "pakyow/support/makeable"
|
6
|
+
require "pakyow/support/indifferentize"
|
7
|
+
|
8
|
+
module Pakyow
|
9
|
+
module Data
|
10
|
+
# Wraps values for a data object returned by a source.
|
11
|
+
#
|
12
|
+
class Object
|
13
|
+
include Support::Bindable
|
14
|
+
|
15
|
+
attr_reader :values
|
16
|
+
|
17
|
+
# @api private
|
18
|
+
attr_accessor :originating_source
|
19
|
+
|
20
|
+
extend Support::ClassState
|
21
|
+
class_state :__serialized_methods, default: [], inheritable: true
|
22
|
+
|
23
|
+
extend Support::Makeable
|
24
|
+
|
25
|
+
def initialize(values)
|
26
|
+
@values = Support::IndifferentHash.new(values).freeze
|
27
|
+
end
|
28
|
+
|
29
|
+
def source
|
30
|
+
@originating_source.__object_name.name
|
31
|
+
end
|
32
|
+
|
33
|
+
def include?(key)
|
34
|
+
respond_to?(key)
|
35
|
+
end
|
36
|
+
alias key? include?
|
37
|
+
|
38
|
+
def [](key)
|
39
|
+
key = key.to_s.to_sym
|
40
|
+
if respond_to?(key)
|
41
|
+
public_send(key)
|
42
|
+
else
|
43
|
+
@values[key]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def method_missing(name, *_args)
|
48
|
+
if include?(name)
|
49
|
+
@values[name]
|
50
|
+
else
|
51
|
+
super
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def respond_to_missing?(name, *)
|
56
|
+
@values.include?(name) || super
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_h
|
60
|
+
hash = @values.dup
|
61
|
+
self.class.__serialized_methods.each do |method|
|
62
|
+
hash[method] = public_send(method)
|
63
|
+
end
|
64
|
+
|
65
|
+
hash
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_json(*)
|
69
|
+
@values.to_json
|
70
|
+
end
|
71
|
+
|
72
|
+
def ==(other)
|
73
|
+
comparator = case other
|
74
|
+
when self.class
|
75
|
+
other
|
76
|
+
when Result
|
77
|
+
other.__getobj__
|
78
|
+
else
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
|
82
|
+
comparator && comparator.class == self.class && comparator.values == @values
|
83
|
+
end
|
84
|
+
|
85
|
+
class << self
|
86
|
+
attr_reader :name
|
87
|
+
|
88
|
+
def make(name, state: nil, parent: nil, **kwargs, &block)
|
89
|
+
super(name, state: state, parent: parent, **kwargs, &block)
|
90
|
+
end
|
91
|
+
|
92
|
+
def serialize(*methods)
|
93
|
+
@__serialized_methods.concat(methods.map(&:to_sym)).uniq!
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,262 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pakyow/support/core_refinements/array/ensurable"
|
4
|
+
require "pakyow/support/deep_dup"
|
5
|
+
require "pakyow/support/inspectable"
|
6
|
+
|
7
|
+
require "pakyow/data/result"
|
8
|
+
|
9
|
+
module Pakyow
|
10
|
+
module Data
|
11
|
+
# @api private
|
12
|
+
class Proxy
|
13
|
+
include Support::Inspectable
|
14
|
+
inspectable :@source
|
15
|
+
|
16
|
+
using Support::Refinements::Array::Ensurable
|
17
|
+
|
18
|
+
using Support::DeepDup
|
19
|
+
|
20
|
+
attr_reader :source, :proxied_calls, :nested_proxies, :app
|
21
|
+
|
22
|
+
def initialize(source, subscribers, app)
|
23
|
+
@source, @subscribers, @app = source, subscribers, app
|
24
|
+
|
25
|
+
@proxied_calls = []
|
26
|
+
@subscribable = true
|
27
|
+
@nested_proxies = []
|
28
|
+
end
|
29
|
+
|
30
|
+
IVARS_TO_DUP = %i(@proxied_calls @nested_proxies).freeze
|
31
|
+
|
32
|
+
def deep_dup
|
33
|
+
super.tap do |duped|
|
34
|
+
IVARS_TO_DUP.each do |ivar|
|
35
|
+
duped.instance_variable_set(ivar, duped.instance_variable_get(ivar).deep_dup)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def method_missing(method_name, *args, &block)
|
41
|
+
if @source.command?(method_name)
|
42
|
+
dup.tap { |duped_proxy|
|
43
|
+
result = @source.command(method_name).call(*args) { |yielded_result|
|
44
|
+
duped_proxy.instance_variable_set(:@source, yielded_result)
|
45
|
+
yield duped_proxy if block_given?
|
46
|
+
}
|
47
|
+
|
48
|
+
if @source.respond_to?(:transaction) && @source.transaction?
|
49
|
+
@source.on_commit do
|
50
|
+
@subscribers.did_mutate(
|
51
|
+
@source.source_name, args[0], result
|
52
|
+
)
|
53
|
+
end
|
54
|
+
else
|
55
|
+
@subscribers.did_mutate(
|
56
|
+
@source.source_name, args[0], result
|
57
|
+
)
|
58
|
+
end
|
59
|
+
}
|
60
|
+
elsif @source.query?(method_name) || @source.modifier?(method_name)
|
61
|
+
dup.tap { |duped_proxy|
|
62
|
+
nested_calls = []
|
63
|
+
|
64
|
+
new_source = if block_given? && @source.block_for_nested_source?(method_name)
|
65
|
+
# In this case a block has been passed that would, without intervention,
|
66
|
+
# be called in context of a source instance. We don't want that, since
|
67
|
+
# it would provide full access to the underlying dataset. Instead the
|
68
|
+
# exposed object should simply be another proxy.
|
69
|
+
|
70
|
+
local_subscribers, local_app = @subscribers, @app
|
71
|
+
@source.source_from_self.public_send(method_name, *args) {
|
72
|
+
nested_proxy = Proxy.new(self, local_subscribers, local_app)
|
73
|
+
nested_proxy.instance_variable_set(:@proxied_calls, nested_calls)
|
74
|
+
nested_proxy.instance_exec(&block).source.tap do |nested_proxy_source|
|
75
|
+
duped_proxy.nested_proxies << nested_proxy.dup.tap do |finalized_nested_proxy|
|
76
|
+
finalized_nested_proxy.instance_variable_set(:@source, nested_proxy_source)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
}
|
80
|
+
else
|
81
|
+
@source.source_from_self.public_send(method_name, *args).tap do |working_source|
|
82
|
+
working_source.included.each do |_, included_source|
|
83
|
+
nested_proxy = Proxy.new(included_source, @subscribers, @app)
|
84
|
+
duped_proxy.nested_proxies << nested_proxy
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
duped_proxy.instance_variable_set(:@source, new_source)
|
90
|
+
duped_proxy.instance_variable_get(:@proxied_calls) << [
|
91
|
+
method_name, args, nested_calls
|
92
|
+
]
|
93
|
+
}
|
94
|
+
else
|
95
|
+
if Array.instance_methods.include?(method_name) && !@source.class.instance_methods.include?(method_name)
|
96
|
+
build_result(
|
97
|
+
@source.to_a.public_send(method_name, *args, &block),
|
98
|
+
method_name, args
|
99
|
+
)
|
100
|
+
elsif @source.class.instance_methods.include?(method_name)
|
101
|
+
build_result(
|
102
|
+
@source.public_send(method_name, *args, &block),
|
103
|
+
method_name, args
|
104
|
+
)
|
105
|
+
else
|
106
|
+
super
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def respond_to_missing?(method_name, include_private = false)
|
112
|
+
return false if method_name == :marshal_dump || method_name == :marshal_load
|
113
|
+
@source.command?(method_name) || @source.query?(method_name) || @source.modifier?(method_name) || @source.respond_to?(method_name, include_private)
|
114
|
+
end
|
115
|
+
|
116
|
+
def to_ary
|
117
|
+
to_a
|
118
|
+
end
|
119
|
+
|
120
|
+
def to_json(*)
|
121
|
+
@source.to_json
|
122
|
+
end
|
123
|
+
|
124
|
+
def subscribe(subscriber, handler:, payload: nil, &block)
|
125
|
+
subscriptions = []
|
126
|
+
|
127
|
+
if subscribable?
|
128
|
+
subscriptions << {
|
129
|
+
source: @source.source_name,
|
130
|
+
ephemeral: @source.is_a?(Sources::Ephemeral),
|
131
|
+
handler: handler,
|
132
|
+
payload: payload,
|
133
|
+
qualifications: qualifications,
|
134
|
+
proxy: self
|
135
|
+
}
|
136
|
+
|
137
|
+
@nested_proxies.each do |related_proxy|
|
138
|
+
subscriptions.concat(
|
139
|
+
related_proxy.subscribe_related(
|
140
|
+
parent_source: @source,
|
141
|
+
serialized_proxy: self,
|
142
|
+
handler: handler,
|
143
|
+
payload: payload
|
144
|
+
)
|
145
|
+
)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
@subscribers.register_subscriptions(subscriptions, subscriber: subscriber, &block)
|
150
|
+
end
|
151
|
+
|
152
|
+
def subscribe_related(parent_source:, serialized_proxy:, handler:, payload: nil)
|
153
|
+
subscriptions = []
|
154
|
+
|
155
|
+
if association = parent_source.class.find_association_to_source(@source)
|
156
|
+
parent_source.each do |parent_result|
|
157
|
+
subscriptions << {
|
158
|
+
source: @source.source_name,
|
159
|
+
handler: handler,
|
160
|
+
payload: payload,
|
161
|
+
qualifications: qualifications.merge(
|
162
|
+
association.associated_query_field => parent_result[association.query_field]
|
163
|
+
),
|
164
|
+
proxy: serialized_proxy
|
165
|
+
}
|
166
|
+
end
|
167
|
+
else
|
168
|
+
Pakyow.logger.error "tried to subscribe a related source, but we don't know how it's related"
|
169
|
+
end
|
170
|
+
|
171
|
+
@nested_proxies.each do |related_proxy|
|
172
|
+
subscriptions.concat(
|
173
|
+
related_proxy.subscribe_related(
|
174
|
+
parent_source: @source,
|
175
|
+
serialized_proxy: serialized_proxy,
|
176
|
+
handler: handler,
|
177
|
+
payload: payload
|
178
|
+
)
|
179
|
+
)
|
180
|
+
end
|
181
|
+
|
182
|
+
subscriptions
|
183
|
+
end
|
184
|
+
|
185
|
+
def unsubscribe
|
186
|
+
subscribable(false)
|
187
|
+
end
|
188
|
+
|
189
|
+
def subscribable(boolean)
|
190
|
+
tap do
|
191
|
+
@subscribable = boolean
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def subscribable?
|
196
|
+
@subscribable == true
|
197
|
+
end
|
198
|
+
|
199
|
+
def _dump(_)
|
200
|
+
Marshal.dump(
|
201
|
+
{
|
202
|
+
app: @app,
|
203
|
+
source: @source.is_a?(Sources::Ephemeral) ? @source : @source.source_name,
|
204
|
+
proxied_calls: @proxied_calls
|
205
|
+
}
|
206
|
+
)
|
207
|
+
end
|
208
|
+
|
209
|
+
def self._load(state)
|
210
|
+
state = Marshal.load(state)
|
211
|
+
|
212
|
+
case state[:source]
|
213
|
+
when Sources::Ephemeral
|
214
|
+
ephemeral = state[:app].data.ephemeral(state[:source].source_name)
|
215
|
+
ephemeral.instance_variable_set(:@source, state[:source])
|
216
|
+
ephemeral
|
217
|
+
else
|
218
|
+
state[:app].data.public_send(state[:source]).apply(state[:proxied_calls])
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def qualifications
|
223
|
+
@proxied_calls.inject(@source.qualifications) { |qualifications, proxied_call|
|
224
|
+
qualifications_for_proxied_call = @source.class.qualifications(proxied_call[0]).dup
|
225
|
+
|
226
|
+
# Populate argument qualifications with argument values.
|
227
|
+
#
|
228
|
+
qualifications_for_proxied_call.each do |qualification_key, qualification_value|
|
229
|
+
next unless qualification_value.to_s.start_with?("__arg")
|
230
|
+
arg_number = qualification_value.to_s.gsub(/[^0-9]/, "").to_i
|
231
|
+
qualifications_for_proxied_call[qualification_key] = @source.class.attributes[qualification_key][proxied_call[1][arg_number]]
|
232
|
+
end
|
233
|
+
|
234
|
+
qualifications.merge(qualifications_for_proxied_call)
|
235
|
+
}
|
236
|
+
end
|
237
|
+
|
238
|
+
# @api private
|
239
|
+
def apply(proxied_calls)
|
240
|
+
proxied_calls.inject(self) { |proxy, proxied_call|
|
241
|
+
if proxied_call[2].any?
|
242
|
+
proxy.public_send(proxied_call[0], *proxied_call[1]) do
|
243
|
+
apply(proxied_call[2])
|
244
|
+
end
|
245
|
+
else
|
246
|
+
proxy.public_send(proxied_call[0], *proxied_call[1])
|
247
|
+
end
|
248
|
+
}
|
249
|
+
end
|
250
|
+
|
251
|
+
private
|
252
|
+
|
253
|
+
def build_result(value, method_name, args)
|
254
|
+
if method_name.to_s.end_with?("?")
|
255
|
+
value
|
256
|
+
else
|
257
|
+
Result.new(value, self, originating_method: method_name, originating_args: args)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "delegate"
|
4
|
+
|
5
|
+
module Pakyow
|
6
|
+
module Data
|
7
|
+
class Result < SimpleDelegator
|
8
|
+
def initialize(result, proxy, originating_method: nil, originating_args: [])
|
9
|
+
@__proxy = proxy
|
10
|
+
@originating_method = originating_method
|
11
|
+
@originating_args = originating_args
|
12
|
+
__setobj__(result)
|
13
|
+
end
|
14
|
+
|
15
|
+
def nil?
|
16
|
+
__getobj__.nil?
|
17
|
+
end
|
18
|
+
|
19
|
+
def marshal_dump
|
20
|
+
{
|
21
|
+
proxy: {
|
22
|
+
app: @__proxy.app,
|
23
|
+
source: @__proxy.source.source_name,
|
24
|
+
proxied_calls: @__proxy.proxied_calls
|
25
|
+
},
|
26
|
+
|
27
|
+
originating_method: @originating_method,
|
28
|
+
originating_args: @originating_args
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def marshal_load(state)
|
33
|
+
result = state[:proxy][:app].data.public_send(
|
34
|
+
state[:proxy][:source]
|
35
|
+
).apply(
|
36
|
+
state[:proxy][:proxied_calls]
|
37
|
+
)
|
38
|
+
|
39
|
+
if state[:originating_method]
|
40
|
+
result = result.public_send(state[:originating_method], *state[:originating_args])
|
41
|
+
end
|
42
|
+
|
43
|
+
__setobj__(result)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Fixes an issue using pp inside a delegator.
|
47
|
+
#
|
48
|
+
def pp(*args)
|
49
|
+
Kernel.pp(*args)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "delegate"
|
4
|
+
|
5
|
+
require "pakyow/support/class_state"
|
6
|
+
|
7
|
+
module Pakyow
|
8
|
+
module Data
|
9
|
+
module Sources
|
10
|
+
class Abstract < SimpleDelegator
|
11
|
+
extend Support::ClassState
|
12
|
+
class_state :__finalized, default: false, inheritable: true
|
13
|
+
|
14
|
+
def initialize(dataset)
|
15
|
+
__setobj__(dataset)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Fixes an issue using pp inside a delegator.
|
19
|
+
#
|
20
|
+
def pp(*args)
|
21
|
+
Kernel.pp(*args)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @api private
|
25
|
+
attr_reader :original_results
|
26
|
+
|
27
|
+
# @api private
|
28
|
+
def qualifications
|
29
|
+
{}
|
30
|
+
end
|
31
|
+
|
32
|
+
# @api private
|
33
|
+
def command?(_maybe_command_name)
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
# @api private
|
38
|
+
def query?(_maybe_query_name)
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
# @api private
|
43
|
+
def modifier?(_maybe_modifier_name)
|
44
|
+
false
|
45
|
+
end
|
46
|
+
|
47
|
+
# @api private
|
48
|
+
def source_from_self(dataset = __getobj__)
|
49
|
+
self.class.source_from_source(self, dataset)
|
50
|
+
end
|
51
|
+
|
52
|
+
class << self
|
53
|
+
attr_reader :container
|
54
|
+
|
55
|
+
def instance
|
56
|
+
container.source(plural_name)
|
57
|
+
end
|
58
|
+
|
59
|
+
# @api private
|
60
|
+
attr_writer :container
|
61
|
+
|
62
|
+
# @api private
|
63
|
+
def plural_name
|
64
|
+
Support.inflector.pluralize(__object_name.name).to_sym
|
65
|
+
end
|
66
|
+
|
67
|
+
# @api private
|
68
|
+
def singular_name
|
69
|
+
Support.inflector.singularize(__object_name.name).to_sym
|
70
|
+
end
|
71
|
+
|
72
|
+
# @api private
|
73
|
+
def source_from_source(source, dataset)
|
74
|
+
source.dup.tap do |duped_source|
|
75
|
+
duped_source.__setobj__(dataset)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|