rom-sql 1.0.0.beta3 → 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 +4 -4
- data/.travis.yml +5 -5
- data/.yardopts +2 -0
- data/CHANGELOG.md +4 -1
- data/lib/rom/plugins/relation/sql/auto_combine.rb +4 -0
- data/lib/rom/plugins/relation/sql/auto_wrap.rb +4 -0
- data/lib/rom/sql/{type.rb → attribute.rb} +8 -5
- data/lib/rom/sql/commands/update.rb +10 -0
- data/lib/rom/sql/dsl.rb +1 -0
- data/lib/rom/sql/extensions/postgres/inferrer.rb +2 -1
- data/lib/rom/sql/extensions/sqlite.rb +1 -0
- data/lib/rom/sql/extensions/sqlite/inferrer.rb +9 -0
- data/lib/rom/sql/extensions/sqlite/types.rb +11 -0
- data/lib/rom/sql/function.rb +4 -1
- data/lib/rom/sql/gateway.rb +64 -28
- data/lib/rom/sql/migration.rb +21 -29
- data/lib/rom/sql/migration/migrator.rb +8 -0
- data/lib/rom/sql/order_dsl.rb +1 -0
- data/lib/rom/sql/plugin/associates.rb +21 -0
- data/lib/rom/sql/plugin/timestamps.rb +131 -0
- data/lib/rom/sql/plugins.rb +3 -0
- data/lib/rom/sql/projection_dsl.rb +1 -0
- data/lib/rom/sql/relation/reading.rb +256 -75
- data/lib/rom/sql/restriction_dsl.rb +1 -0
- data/lib/rom/sql/schema/associations_dsl.rb +119 -1
- data/lib/rom/sql/schema/dsl.rb +44 -2
- data/lib/rom/sql/version.rb +1 -1
- data/rom-sql.gemspec +2 -2
- data/spec/extensions/sqlite/types_spec.rb +11 -0
- data/spec/integration/plugins/associates_spec.rb +79 -0
- data/spec/integration/schema/inferrer/postgres_spec.rb +3 -1
- data/spec/integration/schema/inferrer/sqlite_spec.rb +2 -0
- data/spec/shared/database_setup.rb +10 -1
- data/spec/spec_helper.rb +1 -1
- data/spec/support/helpers.rb +2 -2
- data/spec/unit/plugin/timestamp_spec.rb +77 -0
- data/spec/unit/types_spec.rb +1 -1
- metadata +37 -24
@@ -4,15 +4,50 @@ require 'rom/sql/association'
|
|
4
4
|
module ROM
|
5
5
|
module SQL
|
6
6
|
class Schema < ROM::Schema
|
7
|
+
# Additional schema DSL for definition SQL associations
|
8
|
+
#
|
9
|
+
# This DSL is exposed in `associations do .. end` blocks in schema defintions.
|
10
|
+
#
|
11
|
+
# @api public
|
7
12
|
class AssociationsDSL < BasicObject
|
8
|
-
|
13
|
+
# @!attribute [r] source
|
14
|
+
# @return [Relation::Name] The source relation
|
15
|
+
attr_reader :source
|
9
16
|
|
17
|
+
# @!attribute [r] registry
|
18
|
+
# @return [RelationRegistry] Relations registry from a rom container
|
19
|
+
attr_reader :registry
|
20
|
+
|
21
|
+
# @api private
|
10
22
|
def initialize(source, &block)
|
11
23
|
@source = source
|
12
24
|
@registry = {}
|
13
25
|
instance_exec(&block)
|
14
26
|
end
|
15
27
|
|
28
|
+
# Establish a one-to-many association
|
29
|
+
#
|
30
|
+
# @example using relation identifier
|
31
|
+
# has_many :tasks
|
32
|
+
#
|
33
|
+
# @example with a :through option
|
34
|
+
# # this establishes many-to-many association
|
35
|
+
# has_many :tasks, through: :users_tasks
|
36
|
+
#
|
37
|
+
# @example using aliased association with a custom view
|
38
|
+
# has_many :posts, as: :published_posts, view: :published
|
39
|
+
#
|
40
|
+
# @example using custom target relation
|
41
|
+
# has_many :user_posts, relation: :posts
|
42
|
+
#
|
43
|
+
# @param [Symbol] target The target relation identifier
|
44
|
+
# @param [Hash] options A hash with additional options
|
45
|
+
#
|
46
|
+
# @return [Associations::OneToMany]
|
47
|
+
#
|
48
|
+
# @see #many_to_many
|
49
|
+
#
|
50
|
+
# @api public
|
16
51
|
def one_to_many(target, options = {})
|
17
52
|
if options[:through]
|
18
53
|
many_to_many(target, options)
|
@@ -22,6 +57,22 @@ module ROM
|
|
22
57
|
end
|
23
58
|
alias_method :has_many, :one_to_many
|
24
59
|
|
60
|
+
# Establish a one-to-one association
|
61
|
+
#
|
62
|
+
# @example using relation identifier
|
63
|
+
# one_to_one :addresses, as: :address
|
64
|
+
#
|
65
|
+
# @example with an intermediate join relation
|
66
|
+
# one_to_one :tasks, as: :priority_task, through: :assignments
|
67
|
+
#
|
68
|
+
# @param [Symbol] target The target relation identifier
|
69
|
+
# @param [Hash] options A hash with additional options
|
70
|
+
#
|
71
|
+
# @return [Associations::OneToOne]
|
72
|
+
#
|
73
|
+
# @see #belongs_to
|
74
|
+
#
|
75
|
+
# @api public
|
25
76
|
def one_to_one(target, options = {})
|
26
77
|
if options[:through]
|
27
78
|
one_to_one_through(target, options)
|
@@ -30,36 +81,103 @@ module ROM
|
|
30
81
|
end
|
31
82
|
end
|
32
83
|
|
84
|
+
# Establish a one-to-one association with a :through option
|
85
|
+
#
|
86
|
+
# @example
|
87
|
+
# one_to_one_through :users, as: :author, through: :users_posts
|
88
|
+
#
|
89
|
+
# @return [Associations::OneToOneThrough]
|
90
|
+
#
|
91
|
+
# @api public
|
33
92
|
def one_to_one_through(target, options = {})
|
34
93
|
add(Association::OneToOneThrough.new(source, target, options))
|
35
94
|
end
|
36
95
|
|
96
|
+
# Establish a many-to-many association
|
97
|
+
#
|
98
|
+
# @example using relation identifier
|
99
|
+
# many_to_many :tasks, through: :users_tasks
|
100
|
+
#
|
101
|
+
# @param [Symbol] target The target relation identifier
|
102
|
+
# @param [Hash] options A hash with additional options
|
103
|
+
#
|
104
|
+
# @return [Associations::OneToOne]
|
105
|
+
#
|
106
|
+
# @see #one_to_many
|
107
|
+
#
|
108
|
+
# @api public
|
37
109
|
def many_to_many(target, options = {})
|
38
110
|
add(Association::ManyToMany.new(source, target, options))
|
39
111
|
end
|
40
112
|
|
113
|
+
# Establish a many-to-one association
|
114
|
+
#
|
115
|
+
# @example using relation identifier
|
116
|
+
# many_to_one :users, as: :author
|
117
|
+
#
|
118
|
+
# @param [Symbol] target The target relation identifier
|
119
|
+
# @param [Hash] options A hash with additional options
|
120
|
+
#
|
121
|
+
# @return [Associations::OneToOne]
|
122
|
+
#
|
123
|
+
# @see #one_to_many
|
124
|
+
#
|
125
|
+
# @api public
|
41
126
|
def many_to_one(target, options = {})
|
42
127
|
add(Association::ManyToOne.new(source, target, options))
|
43
128
|
end
|
44
129
|
|
130
|
+
# Shortcut for many_to_one which sets alias automatically
|
131
|
+
#
|
132
|
+
# @example with an alias (relation identifier is inferred via pluralization)
|
133
|
+
# belongs_to :user
|
134
|
+
#
|
135
|
+
# @example with an explicit alias
|
136
|
+
# belongs_to :users, as: :author
|
137
|
+
#
|
138
|
+
# @see #many_to_one
|
139
|
+
#
|
140
|
+
# @return [Associations::ManyToOne]
|
141
|
+
#
|
142
|
+
# @api public
|
45
143
|
def belongs_to(name, options = {})
|
46
144
|
many_to_one(dataset_name(name), {as: name}.merge(options))
|
47
145
|
end
|
48
146
|
|
147
|
+
# Shortcut for one_to_one which sets alias automatically
|
148
|
+
#
|
149
|
+
# @example with an alias (relation identifier is inferred via pluralization)
|
150
|
+
# one_to_one :address
|
151
|
+
#
|
152
|
+
# @example with an explicit alias and a custom view
|
153
|
+
# one_to_one :posts, as: :priority_post, view: :prioritized
|
154
|
+
#
|
155
|
+
# @see #one_to_one
|
156
|
+
#
|
157
|
+
# @return [Associations::ManyToOne]
|
158
|
+
#
|
159
|
+
# @api public
|
49
160
|
def has_one(name, options = {})
|
50
161
|
one_to_one(dataset_name(name), {as: name}.merge(options))
|
51
162
|
end
|
52
163
|
|
164
|
+
# Return an association set for a schema
|
165
|
+
#
|
166
|
+
# @return [AssociationSet]
|
167
|
+
#
|
168
|
+
# @api private
|
53
169
|
def call
|
54
170
|
AssociationSet.new(registry)
|
55
171
|
end
|
56
172
|
|
57
173
|
private
|
58
174
|
|
175
|
+
# @api private
|
59
176
|
def add(association)
|
60
177
|
registry[association.name] = association
|
61
178
|
end
|
62
179
|
|
180
|
+
# @api private
|
63
181
|
def dataset_name(name)
|
64
182
|
::Dry::Core::Inflector.pluralize(name).to_sym
|
65
183
|
end
|
data/lib/rom/sql/schema/dsl.rb
CHANGED
@@ -1,23 +1,65 @@
|
|
1
|
-
require 'rom/sql/
|
1
|
+
require 'rom/sql/attribute'
|
2
2
|
require 'rom/sql/schema/inferrer'
|
3
3
|
require 'rom/sql/schema/associations_dsl'
|
4
4
|
|
5
5
|
module ROM
|
6
6
|
module SQL
|
7
7
|
class Schema < ROM::Schema
|
8
|
+
# Extended schema DSL
|
9
|
+
#
|
10
|
+
# @api private
|
8
11
|
class DSL < ROM::Schema::DSL
|
9
12
|
attr_reader :associations_dsl
|
10
13
|
|
14
|
+
# Define associations for a relation
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# class Users < ROM::Relation[:sql]
|
18
|
+
# schema(infer: true) do
|
19
|
+
# associations do
|
20
|
+
# has_many :tasks
|
21
|
+
# has_many :posts
|
22
|
+
# has_many :posts, as: :priority_posts, view: :prioritized
|
23
|
+
# belongs_to :account
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# class Posts < ROM::Relation[:sql]
|
29
|
+
# schema(infer: true) do
|
30
|
+
# associations do
|
31
|
+
# belongs_to :users, as: :author
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# view(:prioritized) do
|
36
|
+
# where { priority <= 3 }
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# @return [AssociationDSL]
|
41
|
+
#
|
42
|
+
# @api public
|
11
43
|
def associations(&block)
|
12
44
|
@associations_dsl = AssociationsDSL.new(relation, &block)
|
13
45
|
end
|
14
46
|
|
47
|
+
# Return a schema
|
48
|
+
#
|
49
|
+
# @api private
|
15
50
|
def call
|
16
51
|
SQL::Schema.define(
|
17
|
-
relation, opts.merge(attributes: attributes.values,
|
52
|
+
relation, opts.merge(attributes: attributes.values, attr_class: SQL::Attribute)
|
18
53
|
)
|
19
54
|
end
|
20
55
|
|
56
|
+
private
|
57
|
+
|
58
|
+
# Return schema opts
|
59
|
+
#
|
60
|
+
# @return [Hash]
|
61
|
+
#
|
62
|
+
# @api private
|
21
63
|
def opts
|
22
64
|
opts = { inferrer: inferrer }
|
23
65
|
|
data/lib/rom/sql/version.rb
CHANGED
data/rom-sql.gemspec
CHANGED
@@ -20,9 +20,9 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_runtime_dependency 'sequel', '~> 4.42'
|
22
22
|
spec.add_runtime_dependency 'dry-equalizer', '~> 0.2'
|
23
|
-
spec.add_runtime_dependency 'dry-types', '~> 0.9'
|
23
|
+
spec.add_runtime_dependency 'dry-types', '~> 0.9', '>= 0.9.4'
|
24
24
|
spec.add_runtime_dependency 'dry-core', '~> 0.2', '>= 0.2.3'
|
25
|
-
spec.add_runtime_dependency 'rom', '~> 3.0.0.
|
25
|
+
spec.add_runtime_dependency 'rom', '~> 3.0.0.rc'
|
26
26
|
|
27
27
|
spec.add_development_dependency 'bundler'
|
28
28
|
spec.add_development_dependency 'rake', '~> 10.0'
|
@@ -0,0 +1,11 @@
|
|
1
|
+
RSpec.describe 'ROM::SQL::Types' do
|
2
|
+
describe 'ROM::SQL::Types::SQLite::Object' do
|
3
|
+
let(:type) { ROM::SQL::Types::SQLite::Object }
|
4
|
+
|
5
|
+
it 'passes an object of any type' do
|
6
|
+
[Object.new, 1, true, BasicObject.new].each do |obj|
|
7
|
+
expect(type[obj]).to be obj
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -192,4 +192,83 @@ RSpec.describe 'Plugins / :associates' do
|
|
192
192
|
end
|
193
193
|
end
|
194
194
|
end
|
195
|
+
|
196
|
+
with_adapters :sqlite do
|
197
|
+
context 'with Update command' do
|
198
|
+
subject(:command) do
|
199
|
+
container.commands[:tasks][:update].with_association(:user).by_pk(jane_task[:id])
|
200
|
+
end
|
201
|
+
|
202
|
+
let(:john) do
|
203
|
+
container.commands[:users][:create].call(name: 'John')
|
204
|
+
end
|
205
|
+
|
206
|
+
let(:jane) do
|
207
|
+
container.commands[:users][:create].call(name: 'Jane')
|
208
|
+
end
|
209
|
+
|
210
|
+
let(:jane_task) do
|
211
|
+
container.commands[:tasks][:create].call(user_id: jane[:id], title: 'Jane Task')
|
212
|
+
end
|
213
|
+
|
214
|
+
let(:john_task) do
|
215
|
+
container.commands[:tasks][:create].call(user_id: john[:id], title: 'John Task')
|
216
|
+
end
|
217
|
+
|
218
|
+
before do
|
219
|
+
conf.relation_classes[1].class_eval do
|
220
|
+
schema(infer: true) do
|
221
|
+
associations do
|
222
|
+
belongs_to :user
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
conf.commands(:users) do
|
228
|
+
define(:create) do
|
229
|
+
result :one
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
conf.commands(:tasks) do
|
234
|
+
define(:create) do
|
235
|
+
result :one
|
236
|
+
end
|
237
|
+
|
238
|
+
define(:update) do
|
239
|
+
result :one
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
it 'automatically sets FK prior execution' do
|
245
|
+
expect(command.curry(title: 'Another John task').call(john)).
|
246
|
+
to eql(id: jane_task[:id], user_id: john[:id], title: 'Another John task')
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
context 'misconfigured assocs', :sqlite do
|
252
|
+
subject(:command) do
|
253
|
+
container.commands[:users][:create]
|
254
|
+
end
|
255
|
+
|
256
|
+
context 'when keys are missing in class-level config' do
|
257
|
+
before do
|
258
|
+
conf.commands(:users) do
|
259
|
+
define(:create) do
|
260
|
+
associates :tasks
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
it 'raises error' do
|
266
|
+
expect { command }.
|
267
|
+
to raise_error(
|
268
|
+
ROM::SQL::Plugin::Associates::MissingJoinKeysError,
|
269
|
+
':create command for :users relation is missing join keys configuration for :tasks association'
|
270
|
+
)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
195
274
|
end
|
@@ -24,6 +24,7 @@ RSpec.describe 'ROM::SQL::Schema::PostgresInferrer', :postgres do
|
|
24
24
|
column :hw_address, "macaddr"
|
25
25
|
rainbow :color
|
26
26
|
point :center
|
27
|
+
xml :page
|
27
28
|
end
|
28
29
|
end
|
29
30
|
|
@@ -69,7 +70,8 @@ RSpec.describe 'ROM::SQL::Schema::PostgresInferrer', :postgres do
|
|
69
70
|
name: :center,
|
70
71
|
source: source,
|
71
72
|
read: ROM::SQL::Types::PG::PointTR.optional
|
72
|
-
)
|
73
|
+
),
|
74
|
+
page: ROM::SQL::Types::String.optional.meta(name: :page, source: source)
|
73
75
|
)
|
74
76
|
end
|
75
77
|
end
|
@@ -7,6 +7,7 @@ RSpec.describe 'ROM::SQL::Schema::SqliteInferrer', :sqlite do
|
|
7
7
|
conn.create_table :test_inferrence do
|
8
8
|
tinyint :tiny
|
9
9
|
int8 :big
|
10
|
+
column :dummy, nil
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
@@ -31,6 +32,7 @@ RSpec.describe 'ROM::SQL::Schema::SqliteInferrer', :sqlite do
|
|
31
32
|
expect(schema.to_h).to eql(
|
32
33
|
tiny: ROM::SQL::Types::Int.optional.meta(name: :tiny, source: source),
|
33
34
|
big: ROM::SQL::Types::Int.optional.meta(name: :big, source: source),
|
35
|
+
dummy: ROM::SQL::Types::SQLite::Object.optional.meta(name: :dummy, source: source)
|
34
36
|
)
|
35
37
|
end
|
36
38
|
end
|
@@ -21,7 +21,7 @@ shared_context 'database setup' do
|
|
21
21
|
%i(task_tags users_tasks tasks tags
|
22
22
|
subscriptions cards accounts
|
23
23
|
posts users
|
24
|
-
rabbits carrots
|
24
|
+
rabbits carrots notes
|
25
25
|
puppies schema_migrations
|
26
26
|
).each do |name|
|
27
27
|
conn.drop_table?(name)
|
@@ -91,6 +91,15 @@ shared_context 'database setup' do
|
|
91
91
|
String :name, null: false
|
92
92
|
TrueClass :cute, null: false, default: true
|
93
93
|
end
|
94
|
+
|
95
|
+
conn.create_table :notes do
|
96
|
+
primary_key :id
|
97
|
+
String :text, null: false
|
98
|
+
DateTime :created_at, null: false
|
99
|
+
DateTime :updated_at, null: false
|
100
|
+
DateTime :completed_at
|
101
|
+
Date :written
|
102
|
+
end
|
94
103
|
end
|
95
104
|
|
96
105
|
after do
|
data/spec/spec_helper.rb
CHANGED
@@ -47,7 +47,7 @@ Dir[root.join('support/**/*')].each { |f| require f }
|
|
47
47
|
require 'dry/core/deprecations'
|
48
48
|
Dry::Core::Deprecations.set_logger!(root.join('../log/deprecations.log'))
|
49
49
|
|
50
|
-
ROM::SQL.load_extensions(:postgres)
|
50
|
+
ROM::SQL.load_extensions(:postgres, :sqlite)
|
51
51
|
|
52
52
|
require 'dry-types'
|
53
53
|
module Types
|
data/spec/support/helpers.rb
CHANGED
@@ -11,11 +11,11 @@ module Helpers
|
|
11
11
|
ROM::SQL::Schema.define(
|
12
12
|
name,
|
13
13
|
attributes: attrs.map { |key, value| value.meta(name: key, source: ROM::Relation::Name.new(name)) },
|
14
|
-
|
14
|
+
attr_class: ROM::SQL::Attribute
|
15
15
|
)
|
16
16
|
end
|
17
17
|
|
18
18
|
def define_type(name, id, **opts)
|
19
|
-
ROM::SQL::
|
19
|
+
ROM::SQL::Attribute.new(ROM::Types.const_get(id).meta(name: name, **opts))
|
20
20
|
end
|
21
21
|
end
|