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.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +0 -0
  3. data/LICENSE +4 -0
  4. data/README.md +29 -0
  5. data/lib/pakyow/data/adapters/abstract.rb +58 -0
  6. data/lib/pakyow/data/adapters/sql/commands.rb +58 -0
  7. data/lib/pakyow/data/adapters/sql/dataset_methods.rb +29 -0
  8. data/lib/pakyow/data/adapters/sql/differ.rb +76 -0
  9. data/lib/pakyow/data/adapters/sql/migrator/adapter_methods.rb +95 -0
  10. data/lib/pakyow/data/adapters/sql/migrator.rb +181 -0
  11. data/lib/pakyow/data/adapters/sql/migrators/automator.rb +49 -0
  12. data/lib/pakyow/data/adapters/sql/migrators/finalizer.rb +96 -0
  13. data/lib/pakyow/data/adapters/sql/runner.rb +49 -0
  14. data/lib/pakyow/data/adapters/sql/source_extension.rb +31 -0
  15. data/lib/pakyow/data/adapters/sql/types.rb +50 -0
  16. data/lib/pakyow/data/adapters/sql.rb +247 -0
  17. data/lib/pakyow/data/behavior/config.rb +28 -0
  18. data/lib/pakyow/data/behavior/lookup.rb +75 -0
  19. data/lib/pakyow/data/behavior/serialization.rb +40 -0
  20. data/lib/pakyow/data/connection.rb +103 -0
  21. data/lib/pakyow/data/container.rb +273 -0
  22. data/lib/pakyow/data/errors.rb +169 -0
  23. data/lib/pakyow/data/framework.rb +42 -0
  24. data/lib/pakyow/data/helpers.rb +11 -0
  25. data/lib/pakyow/data/lookup.rb +85 -0
  26. data/lib/pakyow/data/migrator.rb +182 -0
  27. data/lib/pakyow/data/object.rb +98 -0
  28. data/lib/pakyow/data/proxy.rb +262 -0
  29. data/lib/pakyow/data/result.rb +53 -0
  30. data/lib/pakyow/data/sources/abstract.rb +82 -0
  31. data/lib/pakyow/data/sources/ephemeral.rb +72 -0
  32. data/lib/pakyow/data/sources/relational/association.rb +43 -0
  33. data/lib/pakyow/data/sources/relational/associations/belongs_to.rb +47 -0
  34. data/lib/pakyow/data/sources/relational/associations/has_many.rb +54 -0
  35. data/lib/pakyow/data/sources/relational/associations/has_one.rb +54 -0
  36. data/lib/pakyow/data/sources/relational/associations/through.rb +67 -0
  37. data/lib/pakyow/data/sources/relational/command.rb +531 -0
  38. data/lib/pakyow/data/sources/relational/migrator.rb +101 -0
  39. data/lib/pakyow/data/sources/relational.rb +587 -0
  40. data/lib/pakyow/data/subscribers/adapters/memory.rb +153 -0
  41. data/lib/pakyow/data/subscribers/adapters/redis/pipeliner.rb +45 -0
  42. data/lib/pakyow/data/subscribers/adapters/redis/scripts/_shared.lua +73 -0
  43. data/lib/pakyow/data/subscribers/adapters/redis/scripts/expire.lua +16 -0
  44. data/lib/pakyow/data/subscribers/adapters/redis/scripts/persist.lua +15 -0
  45. data/lib/pakyow/data/subscribers/adapters/redis/scripts/register.lua +37 -0
  46. data/lib/pakyow/data/subscribers/adapters/redis.rb +209 -0
  47. data/lib/pakyow/data/subscribers.rb +148 -0
  48. data/lib/pakyow/data/tasks/bootstrap.rake +18 -0
  49. data/lib/pakyow/data/tasks/create.rake +22 -0
  50. data/lib/pakyow/data/tasks/drop.rake +32 -0
  51. data/lib/pakyow/data/tasks/finalize.rake +56 -0
  52. data/lib/pakyow/data/tasks/migrate.rake +24 -0
  53. data/lib/pakyow/data/tasks/reset.rake +18 -0
  54. data/lib/pakyow/data/types.rb +37 -0
  55. data/lib/pakyow/data.rb +27 -0
  56. data/lib/pakyow/environment/data/auto_migrate.rb +31 -0
  57. data/lib/pakyow/environment/data/config.rb +54 -0
  58. data/lib/pakyow/environment/data/connections.rb +76 -0
  59. data/lib/pakyow/environment/data/memory_db.rb +23 -0
  60. data/lib/pakyow/validations/unique.rb +26 -0
  61. 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