rom-sql 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bf05af538889499fb9fc1d4cb73ca842fae74482
4
- data.tar.gz: '07441291fd839e047161b70cb9f20c88ebb240f9'
3
+ metadata.gz: ef99832857b3fd2134904b5c5cd99d33ad1665d8
4
+ data.tar.gz: 5a08cac386afc1d218d51a19d5d2a2167cb617d7
5
5
  SHA512:
6
- metadata.gz: 447330e1514ca0aea9127434f02f2a81792e7702155f29c9a6c534938f1f88209ebc31328ef23cd7b368972854cb9e245916162ac05c981e59533c111c59ea54
7
- data.tar.gz: 35a04cc320ec2d246c58fddb5b95aeb3787af7f055f27395596d84cd56013572999648e514d9beeafc15b9f87d879cf29753f3cd3715a8df5e05fe44d2dc061a
6
+ metadata.gz: 712552863d0d9a5cb8c4ce14557ed8705bc274bddff9c9c3b613379a93f470aac04fa5eddce7d133ad1ffe9bd024d05bb08cd052babf7a158cf0da320cbf55d3
7
+ data.tar.gz: a0baf07fda4b9eb66fa0e296a39e2f1d7ed641db3fe3f9feaacf4d2ad8a2b587902c4f335115fa3edd662c5e0aca818bc3140528591a2eeb9b12b2e602bfa3a1
data/.codeclimate.yml ADDED
@@ -0,0 +1,15 @@
1
+ engines:
2
+ duplication:
3
+ enabled: true
4
+ config:
5
+ languages:
6
+ ruby:
7
+ mass_threshold: 25
8
+ rubocop:
9
+ enabled: true
10
+
11
+ ratings:
12
+ paths:
13
+ - "**.rb"
14
+
15
+ exclude_paths:
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## v1.2.0 2017-03-07
2
+
3
+ ### Added
4
+
5
+ * Support for configuring multiple associations for a command (solnic)
6
+ * Support for passing parent tuple(s) as `parent` option in `Command#with_association` (solnic)
7
+ * Support for join using assocation name (flash-gordon)
8
+
9
+ [Compare v1.1.2...v1.2.0](https://github.com/rom-rb/rom-sql/compare/v1.1.2...v1.2.0)
10
+
1
11
  ## v1.1.2 2017-03-02
2
12
 
3
13
  ### Fixed
@@ -20,6 +20,34 @@ module ROM
20
20
  end
21
21
  end
22
22
 
23
+ class AssociateOptions
24
+ attr_reader :name, :assoc, :opts
25
+
26
+ def initialize(name, relation, opts)
27
+ @name = name
28
+ @opts = { assoc: name, keys: opts[:key] }
29
+
30
+ relation.associations.try(name) do |assoc|
31
+ @assoc = assoc
32
+ @opts.update(assoc: assoc, keys: assoc.join_keys(relation.__registry__))
33
+ end
34
+
35
+ @opts.update(parent: opts[:parent]) if opts[:parent]
36
+ end
37
+
38
+ def after?
39
+ assoc.is_a?(Association::ManyToMany)
40
+ end
41
+
42
+ def ensure_valid(command)
43
+ raise MissingJoinKeysError.new(command, name) unless opts[:keys]
44
+ end
45
+
46
+ def to_hash
47
+ { associate: opts }
48
+ end
49
+ end
50
+
23
51
  # @api private
24
52
  def self.included(klass)
25
53
  klass.class_eval do
@@ -35,6 +63,62 @@ module ROM
35
63
  super
36
64
  end
37
65
 
66
+ module ClassMethods
67
+ # @see ROM::Command::ClassInterface.build
68
+ #
69
+ # @api public
70
+ def build(relation, options = EMPTY_HASH)
71
+ command = super
72
+
73
+ configured_assocs = command.configured_associations
74
+
75
+ associate_options = command.associations.map { |(name, opts)|
76
+ next if configured_assocs.include?(name)
77
+ AssociateOptions.new(name, relation, opts)
78
+ }.compact
79
+
80
+ associate_options.each { |opts| opts.ensure_valid(self) }
81
+
82
+ before_hooks = associate_options.reject(&:after?).map(&:to_hash)
83
+ after_hooks = associate_options.select(&:after?).map(&:to_hash)
84
+
85
+ command.
86
+ with_opts(configured_associations: configured_assocs + associate_options.map(&:name)).
87
+ before(*before_hooks).
88
+ after(*after_hooks)
89
+ end
90
+
91
+ # Set command to associate tuples with a parent tuple using provided keys
92
+ #
93
+ # @example
94
+ # class CreateTask < ROM::Commands::Create[:sql]
95
+ # relation :tasks
96
+ # associates :user, key: [:user_id, :id]
97
+ # end
98
+ #
99
+ # create_user = rom.command(:user).create.with(name: 'Jane')
100
+ #
101
+ # create_tasks = rom.command(:tasks).create
102
+ # .with [{ title: 'One' }, { title: 'Two' } ]
103
+ #
104
+ # command = create_user >> create_tasks
105
+ # command.call
106
+ #
107
+ # @param [Symbol] name The name of associated table
108
+ # @param [Hash] options The options
109
+ # @option options [Array] :key The association keys
110
+ #
111
+ # @api public
112
+ def associates(name, options = EMPTY_HASH)
113
+ if associations.key?(name)
114
+ raise ArgumentError,
115
+ "#{name} association is already defined for #{self.class}"
116
+ end
117
+
118
+ associations(associations.merge(name => options))
119
+ end
120
+ end
121
+
38
122
  module InstanceMethods
39
123
  # Set fk on tuples from parent tuple
40
124
  #
@@ -44,7 +128,7 @@ module ROM
44
128
  # @return [Array<Hash>]
45
129
  #
46
130
  # @api public
47
- def associate(tuples, parent, assoc:, keys:)
131
+ def associate(tuples, curried_parent = nil, assoc:, keys:, parent: curried_parent)
48
132
  result_type = result
49
133
 
50
134
  output_tuples =
@@ -90,97 +174,11 @@ module ROM
90
174
  )
91
175
  end
92
176
 
93
- def associations_configured?
94
- if configured_associations.empty?
95
- false
96
- else
97
- configured_associations.all? { |name| associations.key?(name) }
98
- end
99
- end
100
-
101
177
  # @api private
102
178
  def __registry__
103
179
  relation.__registry__
104
180
  end
105
181
  end
106
-
107
- module ClassMethods
108
- # @see ROM::Command::ClassInterface.build
109
- #
110
- # @api public
111
- def build(relation, options = EMPTY_HASH)
112
- command = super
113
-
114
- if command.associations_configured?
115
- return command
116
- end
117
-
118
- associations = command.associations
119
- assoc_names = []
120
-
121
- before_hooks = associations.each_with_object([]) do |(name, opts), acc|
122
- relation.associations.try(name) do |assoc|
123
- unless assoc.is_a?(Association::ManyToMany)
124
- acc << { associate: { assoc: assoc, keys: assoc.join_keys(relation.__registry__) } }
125
- else
126
- true
127
- end
128
- end or acc << { associate: { assoc: name, keys: opts[:key] } }
129
-
130
- assoc_names << name
131
- end
132
-
133
- after_hooks = associations.each_with_object([]) do |(name, opts), acc|
134
- next unless relation.associations.key?(name)
135
-
136
- assoc = relation.associations[name]
137
-
138
- if assoc.is_a?(Association::ManyToMany)
139
- acc << { associate: { assoc: assoc, keys: assoc.join_keys(relation.__registry__) } }
140
- assoc_names << name
141
- end
142
- end
143
-
144
- [*before_hooks, *after_hooks].
145
- map { |hook| hook[:associate] }.
146
- each { |conf| raise MissingJoinKeysError.new(self, conf[:assoc]) unless conf[:keys] }
147
-
148
- command.
149
- with_opts(configured_associations: assoc_names).
150
- before(*before_hooks).
151
- after(*after_hooks)
152
- end
153
-
154
- # Set command to associate tuples with a parent tuple using provided keys
155
- #
156
- # @example
157
- # class CreateTask < ROM::Commands::Create[:sql]
158
- # relation :tasks
159
- # associates :user, key: [:user_id, :id]
160
- # end
161
- #
162
- # create_user = rom.command(:user).create.with(name: 'Jane')
163
- #
164
- # create_tasks = rom.command(:tasks).create
165
- # .with [{ title: 'One' }, { title: 'Two' } ]
166
- #
167
- # command = create_user >> create_tasks
168
- # command.call
169
- #
170
- # @param [Symbol] name The name of associated table
171
- # @param [Hash] options The options
172
- # @option options [Array] :key The association keys
173
- #
174
- # @api public
175
- def associates(name, options = EMPTY_HASH)
176
- if associations.key?(name)
177
- raise ArgumentError,
178
- "#{name} association is already defined for #{self.class}"
179
- end
180
-
181
- associations(associations.merge(name => options))
182
- end
183
- end
184
182
  end
185
183
  end
186
184
  end
@@ -811,7 +811,12 @@ module ROM
811
811
  def __join__(type, other, join_cond = EMPTY_HASH, opts = EMPTY_HASH, &block)
812
812
  case other
813
813
  when Symbol, Association::Name
814
- new(dataset.__send__(type, other.to_sym, join_cond, opts, &block))
814
+ if join_cond.empty?
815
+ assoc = associations[other]
816
+ assoc.join(__registry__, type, self, __registry__[assoc.target.relation])
817
+ else
818
+ new(dataset.__send__(type, other.to_sym, join_cond, opts, &block))
819
+ end
815
820
  when Relation
816
821
  associations[other.name.dataset].join(__registry__, type, self, other)
817
822
  else
@@ -1,5 +1,5 @@
1
1
  module ROM
2
2
  module SQL
3
- VERSION = '1.1.2'.freeze
3
+ VERSION = '1.2.0'.freeze
4
4
  end
5
5
  end
@@ -38,6 +38,21 @@ RSpec.describe 'Plugins / :associates', seeds: false do
38
38
  expect(command.call(task, user)).
39
39
  to eql(id: 1, title: 'Task one', user_id: user[:id])
40
40
  end
41
+
42
+ it 'allows passing a parent explicitly' do
43
+ command = tasks[:create].with_association(:user, key: %i[user_id id], parent: user)
44
+
45
+ expect(command.call(task)).
46
+ to eql(id: 1, title: 'Task one', user_id: user[:id])
47
+ end
48
+
49
+ it 'allows setting up multiple associations' do
50
+ command = tasks[:create].
51
+ with_association(:user, key: %i[user_id id], parent: user).
52
+ with_association(:other, key: %i[other_id id])
53
+
54
+ expect(command.configured_associations).to eql(%i[user other])
55
+ end
41
56
  end
42
57
 
43
58
  shared_context 'automatic FK setting' do
@@ -3,6 +3,7 @@ RSpec.describe ROM::Relation, '#inner_join' do
3
3
 
4
4
  let(:tasks) { relations[:tasks] }
5
5
  let(:tags) { relations[:tags] }
6
+ let(:puzzles) { relations[:puzzles] }
6
7
 
7
8
  include_context 'users and tasks'
8
9
 
@@ -39,9 +40,22 @@ RSpec.describe ROM::Relation, '#inner_join' do
39
40
 
40
41
  context 'with associations' do
41
42
  before do
43
+ inferrable_relations.concat %i(puzzles)
44
+ end
45
+
46
+ before do
47
+ conn.create_table(:puzzles) do
48
+ primary_key :id
49
+ foreign_key :author_id, :users, null: false
50
+ column :text, String, null: false
51
+ end
52
+
42
53
  conf.relation(:users) do
43
54
  schema(infer: true) do
44
- associations { has_many :tasks }
55
+ associations do
56
+ has_many :tasks
57
+ has_many :tasks, as: :todos, relation: :tasks
58
+ end
45
59
  end
46
60
  end
47
61
 
@@ -64,7 +78,16 @@ RSpec.describe ROM::Relation, '#inner_join' do
64
78
  end
65
79
  end
66
80
 
81
+ conf.relation(:puzzles) do
82
+ schema(infer: true) do
83
+ associations do
84
+ belongs_to :users, as: :author
85
+ end
86
+ end
87
+ end
88
+
67
89
  relation.insert id: 3, name: 'Jade'
90
+ puzzles.insert id: 1, author_id: 1, text: 'solved by Jane'
68
91
  end
69
92
 
70
93
  it 'joins relation with join keys inferred' do
@@ -85,6 +108,39 @@ RSpec.describe ROM::Relation, '#inner_join' do
85
108
 
86
109
  expect(result.to_a).to eql([{ id: 1, user_id: 2, title: "Joe's task" }])
87
110
  end
111
+
112
+ it 'joins by association name if no condition provided' do
113
+ result = relation.
114
+ inner_join(:tasks).
115
+ select(:name, tasks[:title])
116
+
117
+ expect(result.schema.map(&:name)).to eql(%i[name title])
118
+
119
+ expect(result.to_a).to eql([
120
+ { name: 'Jane', title: "Jane's task" },
121
+ { name: 'Joe', title: "Joe's task" }
122
+ ])
123
+ end
124
+
125
+ it 'joins if association name differs from relation name' do
126
+ result = relation.
127
+ inner_join(:todos).
128
+ select(:name, tasks[:title])
129
+
130
+ expect(result.schema.map(&:name)).to eql(%i[name title])
131
+
132
+ expect(result.to_a).to eql([
133
+ { name: 'Jane', title: "Jane's task" },
134
+ { name: 'Joe', title: "Joe's task" }
135
+ ])
136
+ end
137
+
138
+ it 'joins by relation if association name differs from relation name' do
139
+ pending 'waits for support for joins by aliased relation'
140
+ result = puzzles.inner_join(users).select(:name, puzzles[:text])
141
+
142
+ expect(result.to_a).to eql([ name: 'Jane', title: "Jane's task" ])
143
+ end
88
144
  end
89
145
 
90
146
  it 'raises error when column names are ambiguous' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rom-sql
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Solnica
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-03-02 00:00:00.000000000 Z
11
+ date: 2017-03-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
@@ -141,6 +141,7 @@ executables: []
141
141
  extensions: []
142
142
  extra_rdoc_files: []
143
143
  files:
144
+ - ".codeclimate.yml"
144
145
  - ".gitignore"
145
146
  - ".rspec"
146
147
  - ".travis.yml"
@@ -345,7 +346,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
345
346
  version: '0'
346
347
  requirements: []
347
348
  rubyforge_project:
348
- rubygems_version: 2.5.2
349
+ rubygems_version: 2.6.9
349
350
  signing_key:
350
351
  specification_version: 4
351
352
  summary: SQL databases support for ROM