rom-sql 1.0.0.beta3 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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