rom-sql 1.3.5 → 2.0.0.beta1
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 +4 -4
- data/CHANGELOG.md +13 -7
- data/Gemfile +7 -5
- data/lib/rom/plugins/relation/sql/auto_restrictions.rb +11 -17
- data/lib/rom/sql.rb +3 -2
- data/lib/rom/sql/associations.rb +5 -0
- data/lib/rom/sql/associations/core.rb +20 -0
- data/lib/rom/sql/associations/many_to_many.rb +83 -0
- data/lib/rom/sql/associations/many_to_one.rb +55 -0
- data/lib/rom/sql/associations/one_to_many.rb +31 -0
- data/lib/rom/sql/{association → associations}/one_to_one.rb +3 -2
- data/lib/rom/sql/{association → associations}/one_to_one_through.rb +3 -2
- data/lib/rom/sql/associations/self_ref.rb +39 -0
- data/lib/rom/sql/attribute.rb +44 -54
- data/lib/rom/sql/errors.rb +2 -0
- data/lib/rom/sql/extensions/mysql.rb +1 -1
- data/lib/rom/sql/extensions/mysql/attributes_inferrer.rb +10 -0
- data/lib/rom/sql/extensions/postgres.rb +1 -1
- data/lib/rom/sql/extensions/postgres/{inferrer.rb → attributes_inferrer.rb} +4 -4
- data/lib/rom/sql/extensions/postgres/types.rb +9 -19
- data/lib/rom/sql/extensions/sqlite.rb +1 -1
- data/lib/rom/sql/extensions/sqlite/{inferrer.rb → attributes_inferrer.rb} +2 -2
- data/lib/rom/sql/gateway.rb +29 -30
- data/lib/rom/sql/index.rb +13 -0
- data/lib/rom/sql/migration.rb +10 -0
- data/lib/rom/sql/migration/inline_runner.rb +86 -0
- data/lib/rom/sql/migration/migrator.rb +17 -0
- data/lib/rom/sql/migration/schema_diff.rb +177 -0
- data/lib/rom/sql/plugin/associates.rb +11 -45
- data/lib/rom/sql/plugin/pagination.rb +4 -4
- data/lib/rom/sql/relation.rb +22 -42
- data/lib/rom/sql/relation/reading.rb +3 -3
- data/lib/rom/sql/schema.rb +14 -21
- data/lib/rom/sql/schema/associations_dsl.rb +7 -6
- data/lib/rom/sql/schema/attributes_inferrer.rb +164 -0
- data/lib/rom/sql/schema/inferrer.rb +40 -141
- data/lib/rom/sql/type_extensions.rb +44 -0
- data/lib/rom/sql/version.rb +1 -1
- data/lib/rom/sql/wrap.rb +25 -0
- data/rom-sql.gemspec +2 -2
- data/spec/integration/{association → associations}/many_to_many/custom_fks_spec.rb +4 -2
- data/spec/integration/{association → associations}/many_to_many/from_view_spec.rb +2 -2
- data/spec/integration/{association → associations}/many_to_many_spec.rb +25 -30
- data/spec/integration/{association → associations}/many_to_one/custom_fks_spec.rb +5 -3
- data/spec/integration/{association → associations}/many_to_one/from_view_spec.rb +3 -3
- data/spec/integration/{association → associations}/many_to_one/self_ref_spec.rb +2 -2
- data/spec/integration/{association → associations}/many_to_one_spec.rb +20 -38
- data/spec/integration/{association → associations}/one_to_many/custom_fks_spec.rb +4 -2
- data/spec/integration/{association → associations}/one_to_many/from_view_spec.rb +2 -2
- data/spec/integration/{association → associations}/one_to_many/self_ref_spec.rb +2 -2
- data/spec/integration/{association → associations}/one_to_many_spec.rb +24 -11
- data/spec/integration/{association → associations}/one_to_one_spec.rb +13 -9
- data/spec/integration/{association → associations}/one_to_one_through_spec.rb +15 -11
- data/spec/integration/auto_migrations/errors_spec.rb +31 -0
- data/spec/integration/auto_migrations/indexes_spec.rb +109 -0
- data/spec/integration/auto_migrations/managing_columns_spec.rb +156 -0
- data/spec/integration/auto_migrations/postgres/column_types_spec.rb +63 -0
- data/spec/integration/commands/create_spec.rb +2 -4
- data/spec/integration/commands/delete_spec.rb +2 -2
- data/spec/integration/commands/update_spec.rb +2 -0
- data/spec/integration/graph_spec.rb +9 -3
- data/spec/integration/plugins/associates_spec.rb +16 -55
- data/spec/integration/plugins/auto_restrictions_spec.rb +0 -11
- data/spec/integration/relation_schema_spec.rb +49 -25
- data/spec/integration/schema/inferrer/postgres_spec.rb +1 -1
- data/spec/integration/schema/inferrer_spec.rb +7 -18
- data/spec/integration/setup_spec.rb +4 -0
- data/spec/integration/{plugins/auto_wrap_spec.rb → wrap_spec.rb} +13 -36
- data/spec/shared/accounts.rb +4 -0
- data/spec/shared/database_setup.rb +2 -1
- data/spec/shared/notes.rb +2 -0
- data/spec/shared/posts.rb +2 -0
- data/spec/shared/puppies.rb +2 -0
- data/spec/shared/relations.rb +2 -2
- data/spec/shared/users.rb +2 -0
- data/spec/shared/users_and_tasks.rb +4 -0
- data/spec/spec_helper.rb +3 -6
- data/spec/support/helpers.rb +11 -8
- data/spec/support/test_configuration.rb +16 -0
- data/spec/unit/plugin/associates_spec.rb +5 -10
- data/spec/unit/plugin/pagination_spec.rb +9 -9
- data/spec/unit/plugin/timestamp_spec.rb +9 -9
- data/spec/unit/relation/dataset_spec.rb +7 -5
- data/spec/unit/relation/inner_join_spec.rb +2 -15
- data/spec/unit/relation/primary_key_spec.rb +1 -1
- data/spec/unit/schema_spec.rb +6 -4
- metadata +65 -70
- data/lib/rom/plugins/relation/sql/auto_combine.rb +0 -71
- data/lib/rom/plugins/relation/sql/auto_wrap.rb +0 -62
- data/lib/rom/sql/association.rb +0 -103
- data/lib/rom/sql/association/many_to_many.rb +0 -119
- data/lib/rom/sql/association/many_to_one.rb +0 -73
- data/lib/rom/sql/association/name.rb +0 -78
- data/lib/rom/sql/association/one_to_many.rb +0 -60
- data/lib/rom/sql/extensions/mysql/inferrer.rb +0 -10
- data/lib/rom/sql/qualified_attribute.rb +0 -53
- data/lib/rom/sql/schema/dsl.rb +0 -75
- data/spec/unit/association/many_to_many_spec.rb +0 -89
- data/spec/unit/association/many_to_one_spec.rb +0 -81
- data/spec/unit/association/name_spec.rb +0 -68
- data/spec/unit/association/one_to_many_spec.rb +0 -82
- data/spec/unit/association/one_to_one_spec.rb +0 -83
- data/spec/unit/association/one_to_one_through_spec.rb +0 -69
|
@@ -1,170 +1,69 @@
|
|
|
1
1
|
require 'set'
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
require 'rom/sql/schema/attributes_inferrer'
|
|
4
|
+
require 'rom/sql/attribute'
|
|
3
5
|
|
|
4
6
|
module ROM
|
|
5
7
|
module SQL
|
|
6
8
|
class Schema < ROM::Schema
|
|
7
9
|
# @api private
|
|
8
|
-
class Inferrer
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
defines :ruby_type_mapping, :numeric_pk_type, :db_type, :db_registry
|
|
12
|
-
|
|
13
|
-
ruby_type_mapping(
|
|
14
|
-
integer: Types::Int,
|
|
15
|
-
string: Types::String,
|
|
16
|
-
time: Types::Time,
|
|
17
|
-
date: Types::Date,
|
|
18
|
-
datetime: Types::Time,
|
|
19
|
-
boolean: Types::Bool,
|
|
20
|
-
decimal: Types::Decimal,
|
|
21
|
-
float: Types::Float,
|
|
22
|
-
blob: Types::Blob
|
|
23
|
-
).freeze
|
|
24
|
-
|
|
25
|
-
numeric_pk_type Types::Serial
|
|
26
|
-
|
|
27
|
-
db_registry Hash.new(self)
|
|
28
|
-
|
|
29
|
-
CONSTRAINT_DB_TYPE = 'add_constraint'.freeze
|
|
30
|
-
DECIMAL_REGEX = /(?:decimal|numeric)\((\d+)(?:,\s*(\d+))?\)/.freeze
|
|
31
|
-
|
|
32
|
-
def self.inherited(klass)
|
|
33
|
-
super
|
|
34
|
-
|
|
35
|
-
Inferrer.db_registry[klass.db_type] = klass unless klass.name.nil?
|
|
10
|
+
class Inferrer < ROM::Schema::Inferrer
|
|
11
|
+
attributes_inferrer -> (schema, gateway, options) do
|
|
12
|
+
AttributesInferrer.get(gateway.database_type).with(options).(schema, gateway)
|
|
36
13
|
end
|
|
37
14
|
|
|
38
|
-
|
|
39
|
-
Class.new(self) { db_type(type) }
|
|
40
|
-
end
|
|
15
|
+
attr_class SQL::Attribute
|
|
41
16
|
|
|
42
|
-
|
|
43
|
-
db_registry[type]
|
|
44
|
-
end
|
|
17
|
+
option :silent, default: -> { false }
|
|
45
18
|
|
|
46
|
-
|
|
47
|
-
warn "[#{relation}] failed to infer schema. " \
|
|
48
|
-
"Make sure tables exist before ROM container is set up. " \
|
|
49
|
-
"This may also happen when your migration tasks load ROM container, " \
|
|
50
|
-
"which is not needed for migrations as only the connection is required " \
|
|
51
|
-
"(#{e.message})"
|
|
52
|
-
end
|
|
19
|
+
option :raise_on_error, default: -> { true }
|
|
53
20
|
|
|
54
|
-
|
|
55
|
-
def call(source, gateway)
|
|
56
|
-
dataset = source.dataset
|
|
57
|
-
|
|
58
|
-
columns = filter_columns(gateway.connection.schema(dataset))
|
|
59
|
-
all_indexes = indexes_for(gateway, dataset)
|
|
60
|
-
fks = fks_for(gateway, dataset)
|
|
61
|
-
|
|
62
|
-
inferred = columns.map do |(name, definition)|
|
|
63
|
-
indexes = column_indexes(all_indexes, name)
|
|
64
|
-
type = build_type(**definition, foreign_key: fks[name], indexes: indexes)
|
|
65
|
-
|
|
66
|
-
if type
|
|
67
|
-
type.meta(name: name, source: source)
|
|
68
|
-
end
|
|
69
|
-
end.compact
|
|
70
|
-
|
|
71
|
-
[inferred, columns.map(&:first) - inferred.map { |attr| attr.meta[:name] }]
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
private
|
|
75
|
-
|
|
76
|
-
def filter_columns(schema)
|
|
77
|
-
schema.reject { |(_, definition)| definition[:db_type] == CONSTRAINT_DB_TYPE }
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def build_type(primary_key:, db_type:, type:, allow_null:, foreign_key:, indexes:, **rest)
|
|
81
|
-
if primary_key
|
|
82
|
-
map_pk_type(type, db_type)
|
|
83
|
-
else
|
|
84
|
-
mapped_type = map_type(type, db_type, rest)
|
|
85
|
-
|
|
86
|
-
if mapped_type
|
|
87
|
-
read_type = mapped_type.meta[:read]
|
|
88
|
-
mapped_type = mapped_type.optional if allow_null
|
|
89
|
-
mapped_type = mapped_type.meta(foreign_key: true, target: foreign_key) if foreign_key
|
|
90
|
-
mapped_type = mapped_type.meta(index: indexes) unless indexes.empty?
|
|
91
|
-
|
|
92
|
-
if read_type && allow_null
|
|
93
|
-
mapped_type.meta(read: read_type.optional)
|
|
94
|
-
elsif read_type
|
|
95
|
-
mapped_type.meta(read: read_type)
|
|
96
|
-
else
|
|
97
|
-
mapped_type
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def map_pk_type(_ruby_type, _db_type)
|
|
104
|
-
self.class.numeric_pk_type.meta(primary_key: true)
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def map_type(ruby_type, db_type, **kw)
|
|
108
|
-
type = self.class.ruby_type_mapping[ruby_type]
|
|
109
|
-
|
|
110
|
-
if db_type.is_a?(String) && db_type.include?('numeric') || db_type.include?('decimal')
|
|
111
|
-
map_decimal_type(db_type)
|
|
112
|
-
elsif db_type.is_a?(String) && db_type.include?('char') && kw[:max_length]
|
|
113
|
-
type.meta(limit: kw[:max_length])
|
|
114
|
-
else
|
|
115
|
-
type
|
|
116
|
-
end
|
|
117
|
-
end
|
|
21
|
+
FALLBACK_SCHEMA = { attributes: EMPTY_ARRAY, indexes: EMPTY_SET }.freeze
|
|
118
22
|
|
|
119
23
|
# @api private
|
|
120
|
-
def
|
|
121
|
-
|
|
122
|
-
column, fk = build_fk(definition)
|
|
24
|
+
def call(schema, gateway)
|
|
25
|
+
inferred = super
|
|
123
26
|
|
|
124
|
-
|
|
125
|
-
|
|
27
|
+
indexes = get_indexes(gateway, schema.name.dataset, inferred[:attributes])
|
|
28
|
+
|
|
29
|
+
{ **inferred, indexes: indexes }
|
|
30
|
+
rescue Sequel::Error => error
|
|
31
|
+
on_error(schema.name, error)
|
|
32
|
+
FALLBACK_SCHEMA
|
|
126
33
|
end
|
|
127
34
|
|
|
128
35
|
# @api private
|
|
129
|
-
def
|
|
130
|
-
if gateway.connection.respond_to?(:indexes)
|
|
131
|
-
gateway.connection.indexes(dataset)
|
|
36
|
+
def get_indexes(gateway, dataset, attributes)
|
|
37
|
+
if enabled? && gateway.connection.respond_to?(:indexes)
|
|
38
|
+
gateway.connection.indexes(dataset).map { |name, body|
|
|
39
|
+
columns = body[:columns].map { |name|
|
|
40
|
+
attributes.find { |attr| attr.name == name }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
SQL::Index.new(columns, name: name)
|
|
44
|
+
}.to_set
|
|
132
45
|
else
|
|
133
|
-
|
|
134
|
-
EMPTY_HASH
|
|
46
|
+
attributes.select(&:indexed?).map { |attr| SQL::Index.new([attr]) }.to_set
|
|
135
47
|
end
|
|
136
48
|
end
|
|
137
49
|
|
|
138
50
|
# @api private
|
|
139
|
-
def
|
|
140
|
-
|
|
141
|
-
indexes << name if idx[:columns][0] == column
|
|
142
|
-
end
|
|
51
|
+
def suppress_errors
|
|
52
|
+
with(raise_on_error: false, silent: true)
|
|
143
53
|
end
|
|
144
54
|
|
|
145
|
-
|
|
146
|
-
def build_fk(columns: , table: , **rest)
|
|
147
|
-
if columns.size == 1
|
|
148
|
-
[columns[0], table]
|
|
149
|
-
else
|
|
150
|
-
# We don't have support for multicolumn foreign keys
|
|
151
|
-
columns[0]
|
|
152
|
-
end
|
|
153
|
-
end
|
|
55
|
+
private
|
|
154
56
|
|
|
155
57
|
# @api private
|
|
156
|
-
def
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
)
|
|
166
|
-
else
|
|
167
|
-
self.class.ruby_type_mapping[:decimal]
|
|
58
|
+
def on_error(dataset, e)
|
|
59
|
+
if raise_on_error
|
|
60
|
+
raise e
|
|
61
|
+
elsif !silent
|
|
62
|
+
warn "[#{dataset}] failed to infer schema. " \
|
|
63
|
+
"Make sure tables exist before ROM container is set up. " \
|
|
64
|
+
"This may also happen when your migration tasks load ROM container, " \
|
|
65
|
+
"which is not needed for migrations as only the connection is required " \
|
|
66
|
+
"(#{e.message})"
|
|
168
67
|
end
|
|
169
68
|
end
|
|
170
69
|
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module ROM
|
|
2
|
+
module SQL
|
|
3
|
+
# Type-specific methods
|
|
4
|
+
#
|
|
5
|
+
# @api public
|
|
6
|
+
module TypeExtensions
|
|
7
|
+
class << self
|
|
8
|
+
# Gets extensions for a type
|
|
9
|
+
#
|
|
10
|
+
# @param [Dry::Types::Type] type
|
|
11
|
+
#
|
|
12
|
+
# @return [Hash]
|
|
13
|
+
#
|
|
14
|
+
# @api public
|
|
15
|
+
def [](type)
|
|
16
|
+
unwrapped = type.optional? ? type.right : type
|
|
17
|
+
@types[unwrapped.pristine] || EMPTY_HASH
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Registers a set of operations supported for a specific type
|
|
21
|
+
#
|
|
22
|
+
# @example
|
|
23
|
+
# ROM::SQL::Attribute::TypeExtensions.register(ROM::SQL::Types::PG::JSONB) do
|
|
24
|
+
# def contain(type, expr, keys)
|
|
25
|
+
# Attribute[Types::Bool].meta(sql_expr: expr.pg_jsonb.contains(value))
|
|
26
|
+
# end
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# @param [Dry::Types::Type] type Type
|
|
30
|
+
#
|
|
31
|
+
# @api public
|
|
32
|
+
def register(type, &block)
|
|
33
|
+
raise ArgumentError, "Type #{ type } already registered" if @types.key?(type)
|
|
34
|
+
mod = Module.new(&block)
|
|
35
|
+
ctx = Object.new.extend(mod)
|
|
36
|
+
functions = mod.public_instance_methods.each_with_object({}) { |m, ms| ms[m] = ctx.method(m) }
|
|
37
|
+
@types[type] = functions
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
@types = {}
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/rom/sql/version.rb
CHANGED
data/lib/rom/sql/wrap.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'rom/relation/wrap'
|
|
2
|
+
|
|
3
|
+
module ROM
|
|
4
|
+
module SQL
|
|
5
|
+
class Wrap < Relation::Wrap
|
|
6
|
+
# @api public
|
|
7
|
+
def schema
|
|
8
|
+
root.schema.merge(nodes.map(&:schema).reduce(:merge)).qualified
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# @api private
|
|
12
|
+
def relation
|
|
13
|
+
relation = nodes.reduce(root) do |a, e|
|
|
14
|
+
if associations.key?(e.name.key)
|
|
15
|
+
a.associations[e.name.key].join(:join, a, e)
|
|
16
|
+
else
|
|
17
|
+
# TODO: deprecate this before 2.0
|
|
18
|
+
a.qualified.join(e.name.dataset, e.meta[:keys])
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
schema.(relation)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/rom-sql.gemspec
CHANGED
|
@@ -19,9 +19,9 @@ Gem::Specification.new do |spec|
|
|
|
19
19
|
|
|
20
20
|
spec.add_runtime_dependency 'sequel', '~> 4.43'
|
|
21
21
|
spec.add_runtime_dependency 'dry-equalizer', '~> 0.2'
|
|
22
|
-
spec.add_runtime_dependency 'dry-types', '~> 0.11
|
|
22
|
+
spec.add_runtime_dependency 'dry-types', '~> 0.11'
|
|
23
23
|
spec.add_runtime_dependency 'dry-core', '~> 0.3'
|
|
24
|
-
spec.add_runtime_dependency 'rom', '~>
|
|
24
|
+
spec.add_runtime_dependency 'rom-core', '~> 4.0.0.beta'
|
|
25
25
|
|
|
26
26
|
spec.add_development_dependency 'bundler'
|
|
27
27
|
spec.add_development_dependency 'rake', '~> 10.0'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
RSpec.describe ROM::SQL::
|
|
1
|
+
RSpec.describe ROM::SQL::Associations::ManyToMany, '#call' do
|
|
2
2
|
include_context 'users'
|
|
3
3
|
|
|
4
4
|
before do
|
|
@@ -25,6 +25,8 @@ RSpec.describe ROM::SQL::Association::ManyToMany, '#call' do
|
|
|
25
25
|
primary_key [:solver_id, :puzzle_id]
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
conf.relation(:puzzles) { schema(infer: true) }
|
|
29
|
+
|
|
28
30
|
conf.relation(:puzzle_solvers) do
|
|
29
31
|
schema(infer: true) do
|
|
30
32
|
associations do
|
|
@@ -55,7 +57,7 @@ RSpec.describe ROM::SQL::Association::ManyToMany, '#call' do
|
|
|
55
57
|
end
|
|
56
58
|
|
|
57
59
|
it 'prepares joined relations using custom FK' do
|
|
58
|
-
relation = assoc.
|
|
60
|
+
relation = assoc.().order(puzzles[:text].qualified, puzzle_solvers[:solver_id].qualified)
|
|
59
61
|
|
|
60
62
|
expect(relation.schema.map(&:to_sql_name)).
|
|
61
63
|
to eql([Sequel.qualify(:puzzles, :id),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
RSpec.describe ROM::SQL::
|
|
1
|
+
RSpec.describe ROM::SQL::Associations::ManyToMany, '#call' do
|
|
2
2
|
include_context 'users'
|
|
3
3
|
|
|
4
4
|
before do
|
|
@@ -70,7 +70,7 @@ RSpec.describe ROM::SQL::Association::ManyToMany, '#call' do
|
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
it 'prepares joined relations using custom FK' do
|
|
73
|
-
relation = assoc.
|
|
73
|
+
relation = assoc.().order(puzzles[:text].qualified, puzzle_solvers[:user_id].qualified)
|
|
74
74
|
|
|
75
75
|
expect(relation.schema.map(&:to_sql_name)).
|
|
76
76
|
to eql([Sequel.qualify(:puzzles, :id),
|
|
@@ -1,50 +1,45 @@
|
|
|
1
|
-
RSpec.describe ROM::SQL::
|
|
1
|
+
RSpec.describe ROM::SQL::Associations::ManyToMany, helpers: true do
|
|
2
2
|
include_context 'users and tasks'
|
|
3
3
|
|
|
4
4
|
with_adapters do
|
|
5
5
|
context 'through a relation with a composite PK' do
|
|
6
|
-
subject(:assoc)
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
subject(:assoc) do
|
|
7
|
+
build_assoc(:many_to_many, :tasks, :tags, through: :task_tags)
|
|
8
|
+
end
|
|
9
9
|
|
|
10
|
-
let(:tags) {
|
|
10
|
+
let(:tags) { relations[:tags] }
|
|
11
11
|
|
|
12
12
|
before do
|
|
13
13
|
conf.relation(:task_tags) do
|
|
14
|
-
schema do
|
|
15
|
-
attribute :task_id, ROM::SQL::Types::ForeignKey(:tasks)
|
|
16
|
-
attribute :tag_id, ROM::SQL::Types::ForeignKey(:tags)
|
|
17
|
-
|
|
18
|
-
primary_key :task_id, :tag_id
|
|
19
|
-
|
|
14
|
+
schema(infer: true) do
|
|
20
15
|
associations do
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
belongs_to :task
|
|
17
|
+
belongs_to :tag
|
|
23
18
|
end
|
|
24
19
|
end
|
|
25
20
|
end
|
|
26
21
|
|
|
27
22
|
conf.relation(:tasks) do
|
|
28
|
-
schema do
|
|
29
|
-
attribute :id, ROM::SQL::Types::Serial
|
|
30
|
-
attribute :user_id, ROM::SQL::Types::ForeignKey(:users)
|
|
31
|
-
attribute :title, ROM::SQL::Types::String
|
|
32
|
-
|
|
23
|
+
schema(infer: true) do
|
|
33
24
|
associations do
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
has_many :task_tags
|
|
26
|
+
has_many :tags, through: :task_tags
|
|
36
27
|
end
|
|
37
28
|
end
|
|
38
29
|
end
|
|
39
30
|
end
|
|
40
31
|
|
|
41
32
|
describe '#result' do
|
|
42
|
-
specify { expect(
|
|
33
|
+
specify { expect(assoc.result).to be(:many) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
describe '#combine_keys' do
|
|
37
|
+
specify { expect(assoc.combine_keys).to eql(id: :task_id) }
|
|
43
38
|
end
|
|
44
39
|
|
|
45
40
|
describe '#call' do
|
|
46
41
|
it 'prepares joined relations' do
|
|
47
|
-
relation = assoc.
|
|
42
|
+
relation = assoc.()
|
|
48
43
|
|
|
49
44
|
expect(relation.schema.map(&:to_sql_name)).
|
|
50
45
|
to eql([Sequel.qualify(:tags, :id),
|
|
@@ -56,11 +51,11 @@ RSpec.describe ROM::SQL::Association::ManyToMany do
|
|
|
56
51
|
|
|
57
52
|
describe ':through another assoc' do
|
|
58
53
|
subject(:assoc) do
|
|
59
|
-
|
|
54
|
+
build_assoc(:many_to_many, :users, :tags, through: :tasks)
|
|
60
55
|
end
|
|
61
56
|
|
|
62
57
|
it 'prepares joined relations through other association' do
|
|
63
|
-
relation = assoc.
|
|
58
|
+
relation = assoc.()
|
|
64
59
|
|
|
65
60
|
expect(relation.schema.map(&:to_sql_name)).
|
|
66
61
|
to eql([Sequel.qualify(:tags, :id),
|
|
@@ -70,9 +65,9 @@ RSpec.describe ROM::SQL::Association::ManyToMany do
|
|
|
70
65
|
end
|
|
71
66
|
end
|
|
72
67
|
|
|
73
|
-
describe
|
|
68
|
+
describe '#eager_load' do
|
|
74
69
|
it 'preloads relation based on association' do
|
|
75
|
-
relation = tags.
|
|
70
|
+
relation = tags.eager_load(assoc).call(tasks.call)
|
|
76
71
|
|
|
77
72
|
expect(relation.to_a).to eql([id: 1, name: 'important', task_id: 1])
|
|
78
73
|
end
|
|
@@ -80,7 +75,7 @@ RSpec.describe ROM::SQL::Association::ManyToMany do
|
|
|
80
75
|
it 'maintains original relation' do
|
|
81
76
|
relation = tags.
|
|
82
77
|
select_append(tags[:name].as(:tag)).
|
|
83
|
-
|
|
78
|
+
eager_load(assoc).call(tasks.call)
|
|
84
79
|
|
|
85
80
|
expect(relation.to_a).to eql([id: 1, tag: 'important', name: 'important', task_id: 1])
|
|
86
81
|
end
|
|
@@ -91,7 +86,7 @@ RSpec.describe ROM::SQL::Association::ManyToMany do
|
|
|
91
86
|
|
|
92
87
|
relation = tags.
|
|
93
88
|
order(tags[:name].qualified).
|
|
94
|
-
|
|
89
|
+
eager_load(assoc).call(tasks.call)
|
|
95
90
|
|
|
96
91
|
expect(relation.to_a).
|
|
97
92
|
to eql([
|
|
@@ -148,7 +143,7 @@ RSpec.describe ROM::SQL::Association::ManyToMany do
|
|
|
148
143
|
tasks = container.relations[:tasks]
|
|
149
144
|
assoc = users.associations[:tasks]
|
|
150
145
|
|
|
151
|
-
relation = tasks.
|
|
146
|
+
relation = tasks.eager_load(assoc).call(users.call)
|
|
152
147
|
|
|
153
148
|
expect(relation.to_a).to be_empty
|
|
154
149
|
end
|
|
@@ -158,7 +153,7 @@ RSpec.describe ROM::SQL::Association::ManyToMany do
|
|
|
158
153
|
tasks = container.relations[:tasks]
|
|
159
154
|
assoc = users.associations[:priv_tasks]
|
|
160
155
|
|
|
161
|
-
relation = tasks.
|
|
156
|
+
relation = tasks.eager_load(assoc).call(users.where(id: 2).call)
|
|
162
157
|
|
|
163
158
|
expect(relation.to_a).to eql([id: 1, user_id: 2, title: "Joe's task"])
|
|
164
159
|
end
|