rom-repository 1.2.0 → 1.3.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: 1d1abbcf0ab779840aaf19b899c6e5b725c8621e
4
- data.tar.gz: b4cfd46194e94c7509c920c21f54e4d317ace881
3
+ metadata.gz: 362832c67aba16f8f9a1dd37eac6d361b39e2344
4
+ data.tar.gz: bedb0cd2de862f654a709becc55095240a50dcb2
5
5
  SHA512:
6
- metadata.gz: d96d070cf7eaf2729c8ebd1b78ed8cc9aebfc128d01c81211eca941fd53ad3f84d6be86ab2ba62e6ef9d1cbf97d4c1eaf299906bb7f0aac26842c070ac9f8c69
7
- data.tar.gz: b82756a5892e7d62c5ce822372f51bd2b8af2e76d28b0e8dde4b01a9e798460bafac9b23ce511fbe761350c598b240024a0f6203e4946f784cb595d0df917c0d
6
+ metadata.gz: 2dfc1e26fd3ee71949cae67a87657b35e876588f930e00023c5fbf69a216882f35058b7f3c9038975ef619d972bc25b91f39bf8a7a69c3670923d649daf989fa
7
+ data.tar.gz: '02393346642d169103689807063b580ff765de616ffac3b15babdb757fef60bef91fe0bd27127bdb9720a07d296e0afdbf78a07b54b17fe75f62db9055ba8865'
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ # v1.3.0 2017-03-07
2
+
3
+ ### Added
4
+
5
+ * Support for chaining `Changeset#associate` (solnic)
6
+ * Inferring association name for `Changeset#associate` (solnic)
7
+ * Support for restricting `Update` and `Delete` changesets explicitly via `Changeset#by_pk` (solnic)
8
+
9
+ [Compare v1.2.0...v1.3.0](https://github.com/rom-rb/rom-repository/compare/v1.2.0...v1.3.0)
10
+
1
11
  # v1.2.0 2017-03-01
2
12
 
3
13
  ### Fixed
@@ -7,7 +17,7 @@
7
17
 
8
18
  ### Changed
9
19
 
10
- * Depends on rom >= 3.0.4 now as it provides `Relation::Graph#with_nodes` which is needed here (solnic)
20
+ * Depends on rom >= 3.1.0 now as it provides `Relation::Graph#with_nodes` which is needed here (solnic)
11
21
 
12
22
  [Compare v1.1.0...v1.2.0](https://github.com/rom-rb/rom-repository/compare/v1.1.0...v1.2.0)
13
23
 
@@ -12,13 +12,30 @@ module ROM
12
12
  # @return [Changeset::Create] Child changeset
13
13
  param :left
14
14
 
15
- # @!attribute [r] right
16
- # @return [Changeset::Create, Hash, #to_hash] Parent changeset or data
17
- param :right
18
-
19
15
  # @!attribute [r] association
20
16
  # @return [Symbol] Association identifier from relation schema
21
- option :association, reader: true
17
+ option :associations, reader: true
18
+
19
+ # Infer association name from an object with a schema
20
+ #
21
+ # This expects other to be an object with a schema that includes a primary key
22
+ # attribute with :source meta information. This makes it work with both structs
23
+ # and relations
24
+ #
25
+ # @see Stateful#associate
26
+ #
27
+ # @api private
28
+ def self.infer_assoc_name(other)
29
+ schema = other.class.schema
30
+ attrs = schema.is_a?(Hash) ? schema.values : schema
31
+ pk = attrs.detect { |attr| attr.meta[:primary_key] }
32
+
33
+ if pk
34
+ pk.meta[:source].relation
35
+ else
36
+ raise ArgumentError, "can't infer association name for #{other}"
37
+ end
38
+ end
22
39
 
23
40
  # Commit changeset's composite command
24
41
  #
@@ -36,6 +53,11 @@ module ROM
36
53
  command.call
37
54
  end
38
55
 
56
+ # @api public
57
+ def associate(other, name = Associated.infer_assoc_name(other))
58
+ self.class.new(left, associations: associations.merge(name => other))
59
+ end
60
+
39
61
  # Create a composed command
40
62
  #
41
63
  # @example using existing parent data
@@ -57,13 +79,15 @@ module ROM
57
79
  #
58
80
  # @api public
59
81
  def command
60
- case right
61
- when Changeset
62
- left.command.curry(left) >> right.command.with_association(association).curry(right)
63
- when Associated
64
- left.command.curry(left) >> right.command.with_association(association)
65
- else
66
- left.command.with_association(association).curry(left, right)
82
+ associations.reduce(left.command.curry(left)) do |a, (assoc, other)|
83
+ case other
84
+ when Changeset
85
+ a >> other.command.with_association(assoc).curry(other)
86
+ when Associated
87
+ a >> other.command.with_association(assoc)
88
+ else
89
+ a.with_association(assoc, parent: other)
90
+ end
67
91
  end
68
92
  end
69
93
 
@@ -9,6 +9,20 @@ module ROM
9
9
  def command
10
10
  super.new(relation)
11
11
  end
12
+
13
+ # Restrict changeset's relation by its PK
14
+ #
15
+ # @example
16
+ # repo.changeset(UpdateUser).by_pk(1).data(name: "Jane")
17
+ #
18
+ # @param [Object] pk
19
+ #
20
+ # @return [Changeset]
21
+ #
22
+ # @api public
23
+ def by_pk(pk, data = EMPTY_HASH)
24
+ new(relation.by_pk(pk), __data__: data)
25
+ end
12
26
  end
13
27
  end
14
28
  end
@@ -187,8 +187,8 @@ module ROM
187
187
  # @param [Symbol] assoc The association identifier from schema
188
188
  #
189
189
  # @api public
190
- def associate(other, name)
191
- Associated.new(self, other, association: name)
190
+ def associate(other, name = Associated.infer_assoc_name(other))
191
+ Associated.new(self, associations: { name => other })
192
192
  end
193
193
 
194
194
  # Return command result type
@@ -92,6 +92,20 @@ module ROM
92
92
  self.class.new(relation, options.merge(new_options))
93
93
  end
94
94
 
95
+ # Return a new changeset with provided relation
96
+ #
97
+ # New options can be provided too
98
+ #
99
+ # @param [Relation] relation
100
+ # @param [Hash] options
101
+ #
102
+ # @return [Changeset]
103
+ #
104
+ # @api public
105
+ def new(relation, new_options = EMPTY_HASH)
106
+ self.class.new(relation, new_options.empty? ? options : options.merge(new_options))
107
+ end
108
+
95
109
  # Persist changeset
96
110
  #
97
111
  # @example
@@ -1,5 +1,5 @@
1
1
  module ROM
2
2
  class Repository
3
- VERSION = '1.2.0'.freeze
3
+ VERSION = '1.3.0'.freeze
4
4
  end
5
5
  end
@@ -1,7 +1,10 @@
1
- RSpec.shared_context 'database' do
1
+ RSpec.shared_context 'database setup' do
2
2
  let(:configuration) { ROM::Configuration.new(:sql, uri) }
3
+
3
4
  let(:conn) { configuration.gateways[:default].connection }
5
+
4
6
  let(:rom) { ROM.container(configuration) }
7
+
5
8
  let(:uri) do
6
9
  if defined? JRUBY_VERSION
7
10
  'jdbc:postgresql://localhost/rom_repository'
@@ -12,7 +15,13 @@ RSpec.shared_context 'database' do
12
15
 
13
16
  before do
14
17
  conn.loggers << LOGGER
18
+ end
19
+ end
20
+
21
+ RSpec.shared_context 'database' do
22
+ include_context 'database setup'
15
23
 
24
+ before do
16
25
  [:tags, :tasks, :posts, :users, :posts_labels, :labels, :books,
17
26
  :reactions, :messages].each { |table| conn.drop_table?(table) }
18
27
 
@@ -0,0 +1,120 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ROM::Changeset, '#associate' do
4
+ include_context 'database setup'
5
+
6
+ before do
7
+ [:todos, :projects, :people].each { |table| conn.drop_table?(table) }
8
+
9
+ conn.create_table :people do
10
+ primary_key :id
11
+ column :name, String
12
+ end
13
+
14
+ conn.create_table :projects do
15
+ primary_key :id
16
+ column :name, String
17
+ end
18
+
19
+ conn.create_table :todos do
20
+ primary_key :id
21
+ foreign_key :user_id, :people, null: false, on_delete: :cascade
22
+ foreign_key :project_id, :projects, null: true, on_delete: :cascade
23
+ column :title, String
24
+ end
25
+
26
+ configuration.relation(:people) do
27
+ schema(infer: true) do
28
+ associations do
29
+ has_many :todos
30
+ end
31
+ end
32
+ end
33
+
34
+ configuration.relation(:projects) do
35
+ schema(infer: true) do
36
+ associations do
37
+ has_many :todos
38
+ end
39
+ end
40
+ end
41
+
42
+ configuration.relation(:todos) do
43
+ schema(infer: true) do
44
+ associations do
45
+ belongs_to :people, as: :user
46
+ belongs_to :projects, as: :project
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ let(:user_repo) do
53
+ Class.new(ROM::Repository[:people]) { commands :create }.new(rom)
54
+ end
55
+
56
+ let(:project_repo) do
57
+ Class.new(ROM::Repository[:projects]) { commands :create }.new(rom)
58
+ end
59
+
60
+ let(:task_repo) do
61
+ Class.new(ROM::Repository[:todos]) { commands :create }.new(rom)
62
+ end
63
+
64
+ context 'with Create' do
65
+ let(:jane) do
66
+ user_repo.create(name: 'Jane')
67
+ end
68
+
69
+ let(:project) do
70
+ project_repo.create(name: 'rom-rb')
71
+ end
72
+
73
+ it 'associates child with parent' do
74
+ changeset = task_repo.changeset(title: 'Test 1').associate(jane, :user)
75
+
76
+ expect(changeset.commit).to include(user_id: jane.id, title: 'Test 1')
77
+ end
78
+
79
+ it 'associates child with multiple parents' do
80
+ changeset = task_repo.changeset(title: 'Test 1').
81
+ associate(jane, :user).
82
+ associate(project)
83
+
84
+ expect(changeset.commit).
85
+ to include(user_id: jane.id, project_id: project.id, title: 'Test 1')
86
+ end
87
+
88
+ it 'raises when assoc name cannot be inferred' do
89
+ other = Class.new do
90
+ def self.schema
91
+ []
92
+ end
93
+ end.new
94
+
95
+ expect { task_repo.changeset(title: 'Test 1').associate(other) }.
96
+ to raise_error(ArgumentError, /can't infer association name for/)
97
+ end
98
+ end
99
+
100
+ context 'with Update' do
101
+ let!(:john) do
102
+ user_repo.create(name: 'John')
103
+ end
104
+
105
+ let!(:jane) do
106
+ user_repo.create(name: 'Jane')
107
+ end
108
+
109
+ let!(:task) do
110
+ task_repo.create(title: 'Test 1', user_id: john.id)
111
+ end
112
+
113
+ it 'associates child with parent' do
114
+ changeset = task_repo.changeset(task.id, title: 'Test 2')
115
+
116
+ expect(changeset.associate(jane, :user).commit).
117
+ to include(user_id: jane.id, title: 'Test 2')
118
+ end
119
+ end
120
+ end
@@ -121,28 +121,58 @@ RSpec.describe ROM::Repository, '#changeset' do
121
121
  end
122
122
 
123
123
  describe 'custom changeset class' do
124
- let(:changeset) do
125
- repo.changeset(changeset_class[:users]).data({})
126
- end
124
+ context 'with a Create' do
125
+ let(:changeset) do
126
+ repo.changeset(changeset_class[:users]).data({})
127
+ end
127
128
 
128
- let(:changeset_class) do
129
- Class.new(ROM::Changeset::Create) do
130
- def to_h
131
- __data__.merge(name: 'Jane')
129
+ let(:changeset_class) do
130
+ Class.new(ROM::Changeset::Create) do
131
+ def to_h
132
+ __data__.merge(name: 'Jane')
133
+ end
132
134
  end
133
135
  end
134
- end
135
136
 
136
- it 'has data' do
137
- expect(changeset.to_h).to eql(name: 'Jane')
138
- end
137
+ it 'has data' do
138
+ expect(changeset.to_h).to eql(name: 'Jane')
139
+ end
139
140
 
140
- it 'has relation' do
141
- expect(changeset.relation).to be(repo.users)
141
+ it 'has relation' do
142
+ expect(changeset.relation).to be(repo.users)
143
+ end
144
+
145
+ it 'can be commited' do
146
+ expect(changeset.commit).to eql(id: 1, name: 'Jane')
147
+ end
142
148
  end
143
149
 
144
- it 'can be commited' do
145
- expect(changeset.commit).to eql(id: 1, name: 'Jane')
150
+ context 'with an Update' do
151
+ let(:changeset) do
152
+ repo.changeset(changeset_class).by_pk(user.id, name: 'Jade')
153
+ end
154
+
155
+ let(:changeset_class) do
156
+ Class.new(ROM::Changeset::Update[:users]) do
157
+ map { |t| t.merge(name: "#{t[:name]} Doe") }
158
+ end
159
+ end
160
+
161
+ let(:user) do
162
+ repo.command(:create, repo.users).call(name: 'Jane')
163
+ end
164
+
165
+ it 'has data' do
166
+ expect(changeset.to_h).to eql(name: 'Jade Doe')
167
+ end
168
+
169
+ it 'has relation restricted by pk' do
170
+ expect(changeset.relation.dataset).to eql(repo.users.by_pk(user.id).dataset)
171
+ end
172
+
173
+ it 'can be commited' do
174
+ expect(changeset.commit).to eql(id: 1, name: 'Jade Doe')
175
+ end
146
176
  end
147
177
  end
148
178
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rom-repository
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.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-01 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: rom
@@ -161,6 +161,7 @@ files:
161
161
  - spec/spec_helper.rb
162
162
  - spec/support/mapper_registry.rb
163
163
  - spec/support/mutant.rb
164
+ - spec/unit/changeset/associate_spec.rb
164
165
  - spec/unit/changeset/map_spec.rb
165
166
  - spec/unit/changeset_spec.rb
166
167
  - spec/unit/relation_proxy_spec.rb