Class: Pakyow::Data::Sources::Relational
- Inherits:
-
Base
- Object
- SimpleDelegator
- Base
- Pakyow::Data::Sources::Relational
show all
- Extended by:
- Support::ClassState, Support::Makeable
- Defined in:
- lib/pakyow/data/sources/relational.rb,
lib/pakyow/data/sources/relational/command.rb,
lib/pakyow/data/sources/relational/migrator.rb,
lib/pakyow/data/sources/relational/association.rb,
lib/pakyow/data/sources/relational/associations/has_one.rb,
lib/pakyow/data/sources/relational/associations/through.rb,
lib/pakyow/data/sources/relational/associations/has_many.rb,
lib/pakyow/data/sources/relational/associations/belongs_to.rb
Overview
A relational data source through which you interact with a persistence layer such as a sql database, redis, or http. Defines the schema, queries, and other adapter-specific metadata (e.g. sql table).
Each adapter provides its own interface for interacting with the underlying persistence layer. For example, the sql adapter exposes Sequel::Dataset
provided by the fantastic Sequel gem.
In normal use, the underlying dataset is inaccessible from outside of the source. Instead, access to the dataset occurs through queries defined on the source that interact with the dataset and return a result.
Results are always returned as a new source instance (or when used from the app, a Proxy object). Access to the underlying value is provided through methods such as one
, to_a
, and each
. (@see Pakyow::Data::Container#wrap_defined_queries!)
Mutations occur through commands. Commands do not implement validation other than checking for required attributes and checking that the given attributes are defined on the source. Use the input verifier pattern to verify and validate input before passing it to a command (@see Pakyow::Verifier).
Defined Under Namespace
Modules: Associations
Classes: Association, Command, Migrator
Constant Summary
collapse
- IVARS_TO_RELOAD =
This constant is part of a private API.
You should avoid using this constant if possible, as it may be removed or be changed in the future.
%i(
@results @result
)
- MODIFIER_METHODS =
This constant is part of a private API.
You should avoid using this constant if possible, as it may be removed or be changed in the future.
%i(as including limit order).freeze
- NESTED_METHODS =
This constant is part of a private API.
You should avoid using this constant if possible, as it may be removed or be changed in the future.
%i(including).freeze
Class Attribute Summary collapse
Instance Attribute Summary collapse
Attributes inherited from Base
#original_results
Class Method Summary
collapse
-
.association_with_name?(name) ⇒ Boolean
private
-
.attribute(name, type = :string, **options) ⇒ Object
-
.belongs_to(association_name, query: nil, source: association_name) ⇒ Object
-
.command(command_name, provides_dataset: true, creates: false, updates: false, deletes: false, &block) ⇒ Object
-
.default_primary_key_type ⇒ Object
-
.find_association_to_source(source) ⇒ Object
private
-
.has_many(association_name, query: nil, source: association_name, as: singular_name, through: nil, dependent: :raise) ⇒ Object
rubocop:disable Naming/PredicateName.
-
.has_one(association_name, query: nil, source: association_name, as: singular_name, through: nil, dependent: :raise) ⇒ Object
rubocop:disable Naming/PredicateName.
-
.make(name, adapter: Pakyow.config.data.default_adapter, connection: Pakyow.config.data.default_connection, state: nil, parent: nil, primary_id: true, timestamps: true, **kwargs, &block) ⇒ Object
-
.primary_id ⇒ Object
-
.primary_key(field) ⇒ Object
-
.primary_key_attribute ⇒ Object
-
.primary_key_type ⇒ Object
-
.qualifications(query_name) ⇒ Object
-
.queries ⇒ Object
-
.query(query_name = nil, &block) ⇒ Object
-
.setup_as_through(association, through:) ⇒ Object
rubocop:enable Naming/PredicateName.
-
.source_from_source ⇒ Object
private
-
.subscribe(query_name, qualifications) ⇒ Object
-
.timestamps(create: :created_at, update: :updated_at) ⇒ Object
Instance Method Summary
collapse
Methods inherited from Base
instance, plural_name, #pp, #qualifications, singular_name, #source_from_self
Constructor Details
Returns a new instance of Relational.
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
# File 'lib/pakyow/data/sources/relational.rb', line 69
def initialize(*)
super
@wrap_as = self.class.singular_name
@included = []
if default_query = self.class.__default_query
result = if default_query.is_a?(Proc)
instance_exec(&default_query)
else
public_send(self.class.__default_query)
end
result = case result
when self.class
result.__getobj__
else
result
end
__setobj__(result)
end
end
|
Class Attribute Details
Returns the value of attribute adapter.
430
431
432
|
# File 'lib/pakyow/data/sources/relational.rb', line 430
def adapter
@adapter
end
|
.connection ⇒ Object
Returns the value of attribute connection.
430
431
432
|
# File 'lib/pakyow/data/sources/relational.rb', line 430
def connection
@connection
end
|
Returns the value of attribute name.
430
431
432
|
# File 'lib/pakyow/data/sources/relational.rb', line 430
def name
@name
end
|
Instance Attribute Details
This method is part of a private API.
You should avoid using this method if possible, as it may be removed or be changed in the future.
67
68
69
|
# File 'lib/pakyow/data/sources/relational.rb', line 67
def included
@included
end
|
Class Method Details
.association_with_name?(name) ⇒ Boolean
This method is part of a private API.
You should avoid using this method if possible, as it may be removed or be changed in the future.
582
583
584
585
586
|
# File 'lib/pakyow/data/sources/relational.rb', line 582
def association_with_name?(name)
associations.values.flatten.find { |association|
association.name == name
}
end
|
.attribute(name, type = :string, **options) ⇒ Object
486
487
488
489
490
491
|
# File 'lib/pakyow/data/sources/relational.rb', line 486
def attribute(name, type = :string, **options)
attributes[name.to_sym] = {
type: type,
options: options
}
end
|
.belongs_to(association_name, query: nil, source: association_name) ⇒ Object
501
502
503
504
505
506
507
|
# File 'lib/pakyow/data/sources/relational.rb', line 501
def belongs_to(association_name, query: nil, source: association_name)
Associations::BelongsTo.new(
name: association_name, query: query, source: self, associated_source_name: source
).tap do |association|
@associations[:belongs_to] << association
end
end
|
.command(command_name, provides_dataset: true, creates: false, updates: false, deletes: false, &block) ⇒ Object
432
433
434
435
436
437
438
439
440
|
# File 'lib/pakyow/data/sources/relational.rb', line 432
def command(command_name, provides_dataset: true, creates: false, updates: false, deletes: false, &block)
@commands[command_name] = {
block: block,
provides_dataset: provides_dataset,
creates: creates,
updates: updates,
deletes: deletes
}
end
|
.default_primary_key_type ⇒ Object
482
483
484
|
# File 'lib/pakyow/data/sources/relational.rb', line 482
def default_primary_key_type
:integer
end
|
.find_association_to_source(source) ⇒ Object
This method is part of a private API.
You should avoid using this method if possible, as it may be removed or be changed in the future.
575
576
577
578
579
|
# File 'lib/pakyow/data/sources/relational.rb', line 575
def find_association_to_source(source)
associations.values.flatten.find { |association|
association.associated_source == source.class
}
end
|
.has_many(association_name, query: nil, source: association_name, as: singular_name, through: nil, dependent: :raise) ⇒ Object
rubocop:disable Naming/PredicateName
510
511
512
513
514
515
516
517
518
519
520
|
# File 'lib/pakyow/data/sources/relational.rb', line 510
def has_many(association_name, query: nil, source: association_name, as: singular_name, through: nil, dependent: :raise)
Associations::HasMany.new(
name: association_name, query: query, source: self, associated_source_name: source, as: as, dependent: dependent
).tap do |association|
@associations[:has_many] << association
if through
setup_as_through(association, through: through)
end
end
end
|
.has_one(association_name, query: nil, source: association_name, as: singular_name, through: nil, dependent: :raise) ⇒ Object
rubocop:disable Naming/PredicateName
524
525
526
527
528
529
530
531
532
533
534
|
# File 'lib/pakyow/data/sources/relational.rb', line 524
def has_one(association_name, query: nil, source: association_name, as: singular_name, through: nil, dependent: :raise)
Associations::HasOne.new(
name: association_name, query: query, source: self, associated_source_name: source, as: as, dependent: dependent
).tap do |association|
@associations[:has_one] << association
if through
setup_as_through(association, through: through)
end
end
end
|
.make(name, adapter: Pakyow.config.data.default_adapter, connection: Pakyow.config.data.default_connection, state: nil, parent: nil, primary_id: true, timestamps: true, **kwargs, &block) ⇒ Object
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
|
# File 'lib/pakyow/data/sources/relational.rb', line 545
def make(name, adapter: Pakyow.config.data.default_adapter, connection: Pakyow.config.data.default_connection, state: nil, parent: nil, primary_id: true, timestamps: true, **kwargs, &block)
super(name, state: state, parent: parent, adapter: adapter, connection: connection, attributes: {}, **kwargs) do
adapter_class = Connection.adapter(adapter)
if adapter_class.const_defined?("SourceExtension")
extension_module = adapter_class.const_get("SourceExtension")
unless ancestors.include?(extension_module)
include(extension_module)
end
self.primary_id if primary_id
self.timestamps if timestamps
end
class_eval(&block) if block_given?
end
end
|
.primary_id ⇒ Object
460
461
462
463
|
# File 'lib/pakyow/data/sources/relational.rb', line 460
def primary_id
primary_key :id
attribute :id, default_primary_key_type
end
|
.primary_key(field) ⇒ Object
465
466
467
|
# File 'lib/pakyow/data/sources/relational.rb', line 465
def primary_key(field)
@primary_key_field = field
end
|
.primary_key_attribute ⇒ Object
478
479
480
|
# File 'lib/pakyow/data/sources/relational.rb', line 478
def primary_key_attribute
attributes[@primary_key_field]
end
|
.primary_key_type ⇒ Object
469
470
471
472
473
474
475
476
|
# File 'lib/pakyow/data/sources/relational.rb', line 469
def primary_key_type
case primary_key_attribute
when Hash
primary_key_attribute[:type]
else
primary_key_attribute.meta[:mapping]
end
end
|
.qualifications(query_name) ⇒ Object
497
498
499
|
# File 'lib/pakyow/data/sources/relational.rb', line 497
def qualifications(query_name)
@qualifications.dig(query_name) || {}
end
|
442
443
444
|
# File 'lib/pakyow/data/sources/relational.rb', line 442
def queries
instance_methods - superclass.instance_methods
end
|
.query(query_name = nil, &block) ⇒ Object
446
447
448
|
# File 'lib/pakyow/data/sources/relational.rb', line 446
def query(query_name = nil, &block)
@__default_query = query_name || block
end
|
.setup_as_through(association, through:) ⇒ Object
rubocop:enable Naming/PredicateName
537
538
539
540
541
542
543
|
# File 'lib/pakyow/data/sources/relational.rb', line 537
def setup_as_through(association, through:)
Associations::Through.new(association, joining_source_name: through).tap do |through_association|
associations[association.specific_type][
associations[association.specific_type].index(association)
] = through_association
end
end
|
.source_from_source ⇒ Object
This method is part of a private API.
You should avoid using this method if possible, as it may be removed or be changed in the future.
570
571
572
|
# File 'lib/pakyow/data/sources/relational.rb', line 570
def source_from_source(*)
super.tap(&:reload)
end
|
.subscribe(query_name, qualifications) ⇒ Object
493
494
495
|
# File 'lib/pakyow/data/sources/relational.rb', line 493
def subscribe(query_name, qualifications)
@qualifications[query_name] = qualifications
end
|
.timestamps(create: :created_at, update: :updated_at) ⇒ Object
450
451
452
453
454
455
456
457
458
|
# File 'lib/pakyow/data/sources/relational.rb', line 450
def timestamps(create: :created_at, update: :updated_at)
@timestamp_fields = {
create: create,
update: update
}
attribute create, :datetime
attribute update, :datetime
end
|
Instance Method Details
#as(object) ⇒ Object
117
118
119
120
121
|
# File 'lib/pakyow/data/sources/relational.rb', line 117
def as(object)
tap do
@wrap_as = object
end
end
|
#block_for_nested_source?(maybe_nested_name) ⇒ Boolean
This method is part of a private API.
You should avoid using this method if possible, as it may be removed or be changed in the future.
256
257
258
|
# File 'lib/pakyow/data/sources/relational.rb', line 256
def block_for_nested_source?(maybe_nested_name)
NESTED_METHODS.include?(maybe_nested_name)
end
|
#command(command_name) ⇒ Object
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
|
# File 'lib/pakyow/data/sources/relational.rb', line 184
def command(command_name)
if command = self.class.commands[command_name]
Command.new(
command_name,
block: command[:block],
source: self,
provides_dataset: command[:provides_dataset],
creates: command[:creates],
updates: command[:updates],
deletes: command[:deletes]
)
else
raise(
UnknownCommand.new_with_message(command: command_name).tap do |error|
error.context = self.class
end
)
end
end
|
#command?(maybe_command_name) ⇒ Boolean
This method is part of a private API.
You should avoid using this method if possible, as it may be removed or be changed in the future.
237
238
239
|
# File 'lib/pakyow/data/sources/relational.rb', line 237
def command?(maybe_command_name)
self.class.commands.include?(maybe_command_name)
end
|
204
205
206
207
208
209
210
|
# File 'lib/pakyow/data/sources/relational.rb', line 204
def count
if self.class.respond_to?(:count)
self.class.count(__getobj__)
else
super
end
end
|
#including(association_name, &block) ⇒ Object
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
# File 'lib/pakyow/data/sources/relational.rb', line 93
def including(association_name, &block)
tap do
association_name = association_name.to_sym
association_to_include = self.class.associations.values.flatten.find { |association|
association.name == association_name
} || raise(UnknownAssociation.new("unknown association `#{association_name}'").tap { |error| error.context = self.class })
included_source = association_to_include.associated_source.instance
if association_to_include.query
included_source = included_source.send(association_to_include.query)
end
final_source = if block_given?
included_source.instance_exec(&block) || included_source
else
included_source
end
@included << [association_to_include, final_source]
end
end
|
#limit(count) ⇒ Object
123
124
125
|
# File 'lib/pakyow/data/sources/relational.rb', line 123
def limit(count)
__setobj__(__getobj__.limit(count)); self
end
|
#modifier?(maybe_modifier_name) ⇒ Boolean
This method is part of a private API.
You should avoid using this method if possible, as it may be removed or be changed in the future.
249
250
251
|
# File 'lib/pakyow/data/sources/relational.rb', line 249
def modifier?(maybe_modifier_name)
MODIFIER_METHODS.include?(maybe_modifier_name)
end
|
#on_commit(&block) ⇒ Object
176
177
178
|
# File 'lib/pakyow/data/sources/relational.rb', line 176
def on_commit(&block)
self.class.container.connection.adapter.connection.after_commit(&block)
end
|
#on_rollback(&block) ⇒ Object
180
181
182
|
# File 'lib/pakyow/data/sources/relational.rb', line 180
def on_rollback(&block)
self.class.container.connection.adapter.connection.after_rollback(&block)
end
|
156
157
158
159
160
161
162
163
164
165
166
|
# File 'lib/pakyow/data/sources/relational.rb', line 156
def one
return @results.first if instance_variable_defined?(:@results)
return @result if instance_variable_defined?(:@result)
if result = self.class.one(__getobj__)
include_results!([result])
@result = finalize(result)
else
nil
end
end
|
#order(*ordering) ⇒ Object
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
# File 'lib/pakyow/data/sources/relational.rb', line 127
def order(*ordering)
__setobj__(
__getobj__.order(
*ordering.flat_map { |order|
case order
when Array
Sequel.public_send(order[1].to_sym, order[0].to_sym)
when Hash
order.each_pair.map { |key, value|
Sequel.public_send(value.to_sym, key.to_sym)
}
else
Sequel.asc(order.to_s.to_sym)
end
}
)
); self
end
|
#query?(maybe_query_name) ⇒ Boolean
This method is part of a private API.
You should avoid using this method if possible, as it may be removed or be changed in the future.
242
243
244
|
# File 'lib/pakyow/data/sources/relational.rb', line 242
def query?(maybe_query_name)
self.class.queries.include?(maybe_query_name)
end
|
217
218
219
220
221
222
223
224
225
|
# File 'lib/pakyow/data/sources/relational.rb', line 217
def reload
IVARS_TO_RELOAD.select { |ivar|
instance_variable_defined?(ivar)
}.each do |ivar|
remove_instance_variable(ivar)
end
self
end
|
#source_name ⇒ Object
This method is part of a private API.
You should avoid using this method if possible, as it may be removed or be changed in the future.
232
233
234
|
# File 'lib/pakyow/data/sources/relational.rb', line 232
def source_name
self.class.__object_name.name
end
|
#to_a ⇒ Object
Also known as:
all
146
147
148
149
150
151
152
153
|
# File 'lib/pakyow/data/sources/relational.rb', line 146
def to_a
return @results if instance_variable_defined?(:@results)
@results = self.class.to_a(__getobj__)
include_results!(@results)
@results.map! { |result|
finalize(result)
}
end
|
227
228
229
|
# File 'lib/pakyow/data/sources/relational.rb', line 227
def to_json(*)
to_a.to_json
end
|
#transaction(&block) ⇒ Object
168
169
170
|
# File 'lib/pakyow/data/sources/relational.rb', line 168
def transaction(&block)
self.class.container.connection.transaction(&block)
end
|
#transaction? ⇒ Boolean
172
173
174
|
# File 'lib/pakyow/data/sources/relational.rb', line 172
def transaction?
self.class.container.connection.adapter.connection.in_transaction?
end
|