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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -5
  3. data/.yardopts +2 -0
  4. data/CHANGELOG.md +4 -1
  5. data/lib/rom/plugins/relation/sql/auto_combine.rb +4 -0
  6. data/lib/rom/plugins/relation/sql/auto_wrap.rb +4 -0
  7. data/lib/rom/sql/{type.rb → attribute.rb} +8 -5
  8. data/lib/rom/sql/commands/update.rb +10 -0
  9. data/lib/rom/sql/dsl.rb +1 -0
  10. data/lib/rom/sql/extensions/postgres/inferrer.rb +2 -1
  11. data/lib/rom/sql/extensions/sqlite.rb +1 -0
  12. data/lib/rom/sql/extensions/sqlite/inferrer.rb +9 -0
  13. data/lib/rom/sql/extensions/sqlite/types.rb +11 -0
  14. data/lib/rom/sql/function.rb +4 -1
  15. data/lib/rom/sql/gateway.rb +64 -28
  16. data/lib/rom/sql/migration.rb +21 -29
  17. data/lib/rom/sql/migration/migrator.rb +8 -0
  18. data/lib/rom/sql/order_dsl.rb +1 -0
  19. data/lib/rom/sql/plugin/associates.rb +21 -0
  20. data/lib/rom/sql/plugin/timestamps.rb +131 -0
  21. data/lib/rom/sql/plugins.rb +3 -0
  22. data/lib/rom/sql/projection_dsl.rb +1 -0
  23. data/lib/rom/sql/relation/reading.rb +256 -75
  24. data/lib/rom/sql/restriction_dsl.rb +1 -0
  25. data/lib/rom/sql/schema/associations_dsl.rb +119 -1
  26. data/lib/rom/sql/schema/dsl.rb +44 -2
  27. data/lib/rom/sql/version.rb +1 -1
  28. data/rom-sql.gemspec +2 -2
  29. data/spec/extensions/sqlite/types_spec.rb +11 -0
  30. data/spec/integration/plugins/associates_spec.rb +79 -0
  31. data/spec/integration/schema/inferrer/postgres_spec.rb +3 -1
  32. data/spec/integration/schema/inferrer/sqlite_spec.rb +2 -0
  33. data/spec/shared/database_setup.rb +10 -1
  34. data/spec/spec_helper.rb +1 -1
  35. data/spec/support/helpers.rb +2 -2
  36. data/spec/unit/plugin/timestamp_spec.rb +77 -0
  37. data/spec/unit/types_spec.rb +1 -1
  38. metadata +37 -24
@@ -2,6 +2,7 @@ require 'rom/sql/dsl'
2
2
 
3
3
  module ROM
4
4
  module SQL
5
+ # @api private
5
6
  class RestrictionDSL < DSL
6
7
  # @api private
7
8
  def call(&block)
@@ -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
- attr_reader :source, :registry
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
@@ -1,23 +1,65 @@
1
- require 'rom/sql/type'
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, type_class: SQL::Type)
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
 
@@ -1,5 +1,5 @@
1
1
  module ROM
2
2
  module SQL
3
- VERSION = '1.0.0.beta3'.freeze
3
+ VERSION = '1.0.0.rc1'.freeze
4
4
  end
5
5
  end
@@ -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.beta'
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
@@ -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
@@ -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
- type_class: ROM::SQL::Type
14
+ attr_class: ROM::SQL::Attribute
15
15
  )
16
16
  end
17
17
 
18
18
  def define_type(name, id, **opts)
19
- ROM::SQL::Type.new(ROM::Types.const_get(id).meta(name: name, **opts))
19
+ ROM::SQL::Attribute.new(ROM::Types.const_get(id).meta(name: name, **opts))
20
20
  end
21
21
  end