rom-repository 0.3.1 → 1.0.0.beta1
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 +11 -13
- data/CHANGELOG.md +25 -0
- data/Gemfile +13 -3
- data/lib/rom/repository.rb +57 -19
- data/lib/rom/repository/changeset.rb +89 -26
- data/lib/rom/repository/changeset/create.rb +34 -0
- data/lib/rom/repository/changeset/delete.rb +15 -0
- data/lib/rom/repository/changeset/pipe.rb +11 -4
- data/lib/rom/repository/changeset/update.rb +11 -1
- data/lib/rom/repository/command_compiler.rb +51 -30
- data/lib/rom/repository/command_proxy.rb +3 -1
- data/lib/rom/repository/header_builder.rb +3 -3
- data/lib/rom/repository/mapper_builder.rb +2 -2
- data/lib/rom/repository/relation_proxy.rb +26 -35
- data/lib/rom/repository/relation_proxy/combine.rb +59 -27
- data/lib/rom/repository/root.rb +4 -6
- data/lib/rom/repository/session.rb +55 -0
- data/lib/rom/repository/struct_builder.rb +29 -17
- data/lib/rom/repository/version.rb +1 -1
- data/lib/rom/struct.rb +11 -20
- data/rom-repository.gemspec +4 -3
- data/spec/integration/command_macros_spec.rb +5 -2
- data/spec/integration/command_spec.rb +0 -6
- data/spec/integration/multi_adapter_spec.rb +8 -5
- data/spec/integration/repository_spec.rb +58 -2
- data/spec/integration/root_repository_spec.rb +9 -2
- data/spec/integration/typed_structs_spec.rb +31 -0
- data/spec/shared/database.rb +5 -1
- data/spec/shared/relations.rb +3 -1
- data/spec/shared/repo.rb +13 -1
- data/spec/shared/structs.rb +39 -0
- data/spec/spec_helper.rb +7 -5
- data/spec/support/mutant.rb +10 -0
- data/spec/unit/changeset/map_spec.rb +42 -0
- data/spec/unit/changeset_spec.rb +32 -6
- data/spec/unit/relation_proxy_spec.rb +27 -9
- data/spec/unit/repository/changeset_spec.rb +125 -0
- data/spec/unit/repository/inspect_spec.rb +18 -0
- data/spec/unit/repository/session_spec.rb +251 -0
- data/spec/unit/session_spec.rb +54 -0
- data/spec/unit/struct_builder_spec.rb +45 -1
- metadata +41 -17
- data/lib/rom/repository/struct_attributes.rb +0 -46
- data/spec/unit/header_builder_spec.rb +0 -73
- data/spec/unit/plugins/view_spec.rb +0 -29
- data/spec/unit/sql/relation_spec.rb +0 -54
- data/spec/unit/struct_spec.rb +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a5e3e3120dbe44e4f2f116cfe7aaaba6b2ef0a7e
|
4
|
+
data.tar.gz: 1e29da4f4c4c2c8c6cf7d9a70c794365419bc18d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b40d4d1a1c984998a0f58287cf586dd151251f9e9cabddcf22fff0a23e944f7113a1ffef5be76e6c44560d99d9c4d7bcecbb78f074f5079057ac71f7da0708a8
|
7
|
+
data.tar.gz: 8ec19211a0103e67f33578166bb781c1d42271d04e5561451e023aaccc4722d90b9e2fa3061cc1a021efca5c5f13fad00b105f9d72cfb66c1a4f86a679422c13
|
data/.travis.yml
CHANGED
@@ -1,28 +1,26 @@
|
|
1
1
|
language: ruby
|
2
|
-
|
2
|
+
dist: trusty
|
3
|
+
sudo: required
|
3
4
|
cache: bundler
|
4
5
|
bundler_args: --without yard guard benchmarks tools
|
5
6
|
before_script:
|
6
7
|
- psql -c 'create database rom_repository' -U postgres
|
7
8
|
script: "bundle exec rake ci"
|
9
|
+
after_success:
|
10
|
+
- '[ "${TRAVIS_JOB_NUMBER#*.}" = "1" ] && [ "$TRAVIS_BRANCH" = "master" ] && bundle exec codeclimate-test-reporter'
|
8
11
|
rvm:
|
9
|
-
- 2.
|
10
|
-
- 2.
|
11
|
-
- 2.
|
12
|
-
- rbx-
|
13
|
-
- jruby-9.
|
14
|
-
- ruby-head
|
12
|
+
- 2.4.0
|
13
|
+
- 2.3.3
|
14
|
+
- 2.2.6
|
15
|
+
- rbx-3
|
16
|
+
- jruby-9.1.6.0
|
15
17
|
env:
|
16
18
|
global:
|
17
|
-
- CODECLIMATE_REPO_TOKEN=173b95dae5c6ac281bc36a4b212291a89fed9b520b39a86bafdf603692250e60
|
18
19
|
- JRUBY_OPTS='--dev -J-Xmx1024M'
|
20
|
+
- COVERAGE='true'
|
19
21
|
matrix:
|
20
22
|
allow_failures:
|
21
|
-
- rvm:
|
22
|
-
- rvm: jruby-head
|
23
|
-
include:
|
24
|
-
- rvm: jruby-head
|
25
|
-
before_install: gem install bundler --no-ri --no-rdoc
|
23
|
+
- rvm: rbx-3
|
26
24
|
notifications:
|
27
25
|
webhooks:
|
28
26
|
urls:
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,28 @@
|
|
1
|
+
# v1.0.0 to-be-released
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* New `Repository#session` API for building transactions using changesets (solnic)
|
6
|
+
* Support for inferring typed structs based on relation schemas (solnic)
|
7
|
+
* `aggregate` and `combine` support nested association specs, ie `combine(users: [tasks: :tags])` (beauby)
|
8
|
+
* Changesets support data as arrays too (solnic)
|
9
|
+
* Changesets support custom command types via `Changeset#with(command_type: :my_command)` (solnic)
|
10
|
+
* `Changeset::Delete` was added and is accessible via `repo.changeset(delete: some_relation.by_pk(1))` (solnic)
|
11
|
+
* Ability to define custom changeset classes that can be instantiated via `repo.changeset(MyChangesetClass[:rel_name]).data(some_data)` where `MyChangesetClass` inherits from a core changeset class (solnic)
|
12
|
+
* `Changeset.map` which accepts a block and exposes a DSL for data transformations (all [transproc hash methods](https://github.com/solnic/transproc)) are available (solnic)
|
13
|
+
* `Changeset#associate` method that accepts another changeset or parent data and an association identifier, ie `post_changeset.associate(user, :author)` (solnic)
|
14
|
+
|
15
|
+
### Changed
|
16
|
+
|
17
|
+
* `ROM::Struct` is now based on `Dry::Struct` (solnic)
|
18
|
+
* rom-support dependency was removed (flash-gordon)
|
19
|
+
|
20
|
+
### Fixed
|
21
|
+
|
22
|
+
* Auto-mapping to structs ignores FK attributes consistently (solnic)
|
23
|
+
|
24
|
+
[Compare v0.3.1...v1.0.0](https://github.com/rom-rb/rom-repository/compare/v0.3.1...v1.0.0)
|
25
|
+
|
1
26
|
# v0.3.1 2016-07-27
|
2
27
|
|
3
28
|
Fixed gemspec so that we don't exclude all files with 'bin' in their name, geez (solnic)
|
data/Gemfile
CHANGED
@@ -4,8 +4,11 @@ gemspec
|
|
4
4
|
|
5
5
|
gem 'inflecto'
|
6
6
|
|
7
|
+
gem 'rom', git: 'https://github.com/rom-rb/rom.git', branch: 'master'
|
8
|
+
gem 'rom-mapper', git: 'https://github.com/rom-rb/rom-mapper.git', branch: 'master'
|
9
|
+
|
7
10
|
group :development, :test do
|
8
|
-
gem 'rom-sql', '
|
11
|
+
gem 'rom-sql', git: 'https://github.com/rom-rb/rom-sql.git', branch: 'master'
|
9
12
|
end
|
10
13
|
|
11
14
|
group :development do
|
@@ -16,18 +19,25 @@ end
|
|
16
19
|
|
17
20
|
group :test do
|
18
21
|
gem 'rspec'
|
22
|
+
gem 'dry-struct'
|
19
23
|
gem 'byebug', platforms: :mri
|
20
24
|
gem 'pg', platforms: [:mri, :rbx]
|
21
25
|
gem 'jdbc-postgres', platforms: :jruby
|
22
|
-
|
26
|
+
|
27
|
+
platform :mri do
|
28
|
+
gem 'codeclimate-test-reporter', require: false
|
29
|
+
gem 'simplecov'
|
30
|
+
end
|
23
31
|
end
|
24
32
|
|
25
33
|
group :benchmarks do
|
26
34
|
gem 'hotch', platforms: :mri
|
27
35
|
gem 'benchmark-ips'
|
28
|
-
gem 'activerecord', '~>
|
36
|
+
gem 'activerecord', '~> 5.0'
|
29
37
|
end
|
30
38
|
|
31
39
|
group :tools do
|
32
40
|
gem 'pry'
|
41
|
+
gem 'mutant'
|
42
|
+
gem 'mutant-rspec'
|
33
43
|
end
|
data/lib/rom/repository.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
require '
|
2
|
-
require 'rom/support/options'
|
1
|
+
require 'dry/core/deprecations'
|
3
2
|
|
4
3
|
require 'rom/repository/class_interface'
|
5
4
|
require 'rom/repository/mapper_builder'
|
@@ -8,6 +7,7 @@ require 'rom/repository/command_compiler'
|
|
8
7
|
|
9
8
|
require 'rom/repository/root'
|
10
9
|
require 'rom/repository/changeset'
|
10
|
+
require 'rom/repository/session'
|
11
11
|
|
12
12
|
module ROM
|
13
13
|
# Abstract repository class to inherit from
|
@@ -49,14 +49,6 @@ module ROM
|
|
49
49
|
#
|
50
50
|
# @api public
|
51
51
|
class Repository
|
52
|
-
# @deprecated
|
53
|
-
class Base < Repository
|
54
|
-
def self.inherited(klass)
|
55
|
-
super
|
56
|
-
Deprecations.announce(self, 'inherit from Repository instead')
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
52
|
extend ClassInterface
|
61
53
|
|
62
54
|
# @!attribute [r] container
|
@@ -71,6 +63,9 @@ module ROM
|
|
71
63
|
# @return [MapperBuilder] The auto-generated mappers for repo relations
|
72
64
|
attr_reader :mappers
|
73
65
|
|
66
|
+
# @api private
|
67
|
+
attr_reader :command_compiler
|
68
|
+
|
74
69
|
# Initializes a new repo by establishing configured relation proxies from
|
75
70
|
# the passed container
|
76
71
|
#
|
@@ -93,6 +88,7 @@ module ROM
|
|
93
88
|
relations[name] = proxy
|
94
89
|
end
|
95
90
|
end
|
91
|
+
@command_compiler = method(:command)
|
96
92
|
end
|
97
93
|
|
98
94
|
# @overload command(type, relation)
|
@@ -166,23 +162,60 @@ module ROM
|
|
166
162
|
#
|
167
163
|
# @api public
|
168
164
|
def changeset(*args)
|
165
|
+
opts = { command_compiler: command_compiler }
|
166
|
+
|
169
167
|
if args.size == 2
|
170
168
|
name, data = args
|
171
169
|
elsif args.size == 3
|
172
170
|
name, pk, data = args
|
171
|
+
elsif args.size == 1
|
172
|
+
type = args[0]
|
173
|
+
|
174
|
+
if type.is_a?(Class) && type < Changeset
|
175
|
+
return type.new(relations[type.relation], opts)
|
176
|
+
else
|
177
|
+
type, relation = args[0].to_a[0]
|
178
|
+
end
|
173
179
|
else
|
174
|
-
raise ArgumentError, 'Repository#changeset accepts
|
180
|
+
raise ArgumentError, 'Repository#changeset accepts 1-3 arguments'
|
175
181
|
end
|
176
182
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
183
|
+
if type
|
184
|
+
if type.equal?(:delete)
|
185
|
+
Changeset::Delete.new(relation, opts)
|
186
|
+
end
|
181
187
|
else
|
182
|
-
|
188
|
+
relation = relations[name]
|
189
|
+
|
190
|
+
if pk
|
191
|
+
Changeset::Update.new(relation, opts.merge(__data__: data, primary_key: pk))
|
192
|
+
else
|
193
|
+
Changeset::Create.new(relation, opts.merge(__data__: data))
|
194
|
+
end
|
183
195
|
end
|
184
196
|
end
|
185
197
|
|
198
|
+
# TODO: document me, please
|
199
|
+
#
|
200
|
+
# @api public
|
201
|
+
def session(&block)
|
202
|
+
session = Session.new(self)
|
203
|
+
yield(session)
|
204
|
+
transaction { session.commit! }
|
205
|
+
end
|
206
|
+
|
207
|
+
# TODO: document me, please
|
208
|
+
#
|
209
|
+
# @api public
|
210
|
+
def transaction(&block)
|
211
|
+
container.gateways[:default].transaction(&block)
|
212
|
+
end
|
213
|
+
|
214
|
+
# @api public
|
215
|
+
def inspect
|
216
|
+
%(#<#{self.class} relations=[#{self.class.relations.map(&:inspect).join(' ')}]>)
|
217
|
+
end
|
218
|
+
|
186
219
|
private
|
187
220
|
|
188
221
|
# Local command cache
|
@@ -205,12 +238,17 @@ module ROM
|
|
205
238
|
|
206
239
|
if mapper
|
207
240
|
mapper_instance = container.mappers[relation.name.relation][mapper]
|
208
|
-
|
241
|
+
elsif mapper.nil?
|
209
242
|
mapper_instance = mappers[ast]
|
210
243
|
end
|
211
244
|
|
212
|
-
command = CommandCompiler[container, type, adapter, ast, use]
|
213
|
-
|
245
|
+
command = CommandCompiler[container, type, adapter, ast, use, opts]
|
246
|
+
|
247
|
+
if mapper_instance
|
248
|
+
command >> mapper_instance
|
249
|
+
else
|
250
|
+
command.new(relation)
|
251
|
+
end
|
214
252
|
end
|
215
253
|
|
216
254
|
# @api private
|
@@ -1,11 +1,24 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
1
|
+
require 'dry/core/class_attributes'
|
2
|
+
require 'dry/core/cache'
|
3
3
|
|
4
|
+
require 'rom/initializer'
|
4
5
|
require 'rom/repository/changeset/pipe'
|
5
6
|
|
6
7
|
module ROM
|
7
8
|
class Changeset
|
8
|
-
|
9
|
+
extend Initializer
|
10
|
+
extend Dry::Core::Cache
|
11
|
+
extend Dry::Core::ClassAttributes
|
12
|
+
|
13
|
+
defines :relation
|
14
|
+
|
15
|
+
# @!attribute [r] relation
|
16
|
+
# @return [Relation] The changeset relation
|
17
|
+
param :relation
|
18
|
+
|
19
|
+
# @!attribute [r] __data__
|
20
|
+
# @return [Hash] The relation data
|
21
|
+
option :__data__, reader: true, optional: true, default: proc { nil }
|
9
22
|
|
10
23
|
# @!attribute [r] pipe
|
11
24
|
# @return [Changeset::Pipe] data transformation pipe
|
@@ -13,13 +26,33 @@ module ROM
|
|
13
26
|
changeset.class.default_pipe
|
14
27
|
}
|
15
28
|
|
16
|
-
# @!attribute [r]
|
17
|
-
# @return [
|
18
|
-
|
29
|
+
# @!attribute [r] command_compiler
|
30
|
+
# @return [Proc] a proc that can compile a command (typically provided by a repo)
|
31
|
+
option :command_compiler, reader: true, optional: true
|
19
32
|
|
20
|
-
# @!attribute [r]
|
21
|
-
# @return [
|
22
|
-
|
33
|
+
# @!attribute [r] command_type
|
34
|
+
# @return [Symbol] a custom command identifier
|
35
|
+
option :command_type, reader: true, optional: true, default: -> changeset { changeset.default_command_type }
|
36
|
+
|
37
|
+
# Create a changeset class preconfigured for a specific relation
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# class NewUserChangeset < ROM::Changeset::Create[:users]
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# user_repo.changeset(NewUserChangeset).data(name: 'Jane')
|
44
|
+
#
|
45
|
+
# @api public
|
46
|
+
def self.[](relation_name)
|
47
|
+
fetch_or_store(relation_name) {
|
48
|
+
Class.new(self) { relation(relation_name) }
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
# @api public
|
53
|
+
def self.map(&block)
|
54
|
+
@pipe = Class.new(Pipe, &block).new
|
55
|
+
end
|
23
56
|
|
24
57
|
# Build default pipe object
|
25
58
|
#
|
@@ -27,14 +60,7 @@ module ROM
|
|
27
60
|
#
|
28
61
|
# @return [Pipe]
|
29
62
|
def self.default_pipe
|
30
|
-
Pipe.new
|
31
|
-
end
|
32
|
-
|
33
|
-
# @api private
|
34
|
-
def initialize(relation, data, options = EMPTY_HASH)
|
35
|
-
@relation = relation
|
36
|
-
@data = data
|
37
|
-
super
|
63
|
+
@pipe || Pipe.new
|
38
64
|
end
|
39
65
|
|
40
66
|
# Pipe changeset's data using custom steps define on the pipe
|
@@ -44,8 +70,12 @@ module ROM
|
|
44
70
|
# @return [Changeset]
|
45
71
|
#
|
46
72
|
# @api public
|
47
|
-
def map(*steps)
|
48
|
-
|
73
|
+
def map(*steps, &block)
|
74
|
+
if block
|
75
|
+
__data__.map { |*args| yield(*args) }
|
76
|
+
else
|
77
|
+
with(pipe: steps.reduce(pipe) { |a, e| a >> pipe[e] })
|
78
|
+
end
|
49
79
|
end
|
50
80
|
|
51
81
|
# Coerce changeset to a hash
|
@@ -56,10 +86,22 @@ module ROM
|
|
56
86
|
#
|
57
87
|
# @api public
|
58
88
|
def to_h
|
59
|
-
pipe.call(
|
89
|
+
pipe.call(__data__)
|
60
90
|
end
|
61
91
|
alias_method :to_hash, :to_h
|
62
92
|
|
93
|
+
# Coerce changeset to an array
|
94
|
+
#
|
95
|
+
# This will send the data through the pipe
|
96
|
+
#
|
97
|
+
# @return [Array]
|
98
|
+
#
|
99
|
+
# @api public
|
100
|
+
def to_a
|
101
|
+
result == :one ? [to_h] : __data__.map { |element| pipe.call(element) }
|
102
|
+
end
|
103
|
+
alias_method :to_ary, :to_a
|
104
|
+
|
63
105
|
# Return a new changeset with updated options
|
64
106
|
#
|
65
107
|
# @param [Hash] new_options The new options
|
@@ -68,23 +110,43 @@ module ROM
|
|
68
110
|
#
|
69
111
|
# @api private
|
70
112
|
def with(new_options)
|
71
|
-
self.class.new(relation,
|
113
|
+
self.class.new(relation, options.merge(new_options))
|
114
|
+
end
|
115
|
+
|
116
|
+
# Return changeset with data
|
117
|
+
#
|
118
|
+
# @param [Hash] data
|
119
|
+
#
|
120
|
+
# @return [Changeset]
|
121
|
+
#
|
122
|
+
# @api public
|
123
|
+
def data(data)
|
124
|
+
with(__data__: data)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Return command result type
|
128
|
+
#
|
129
|
+
# @return [Symbol]
|
130
|
+
#
|
131
|
+
# @api private
|
132
|
+
def result
|
133
|
+
__data__.is_a?(Hash) ? :one : :many
|
72
134
|
end
|
73
135
|
|
74
136
|
private
|
75
137
|
|
76
138
|
# @api private
|
77
139
|
def respond_to_missing?(meth, include_private = false)
|
78
|
-
super ||
|
140
|
+
super || __data__.respond_to?(meth)
|
79
141
|
end
|
80
142
|
|
81
143
|
# @api private
|
82
144
|
def method_missing(meth, *args, &block)
|
83
|
-
if
|
84
|
-
response =
|
145
|
+
if __data__.respond_to?(meth)
|
146
|
+
response = __data__.__send__(meth, *args, &block)
|
85
147
|
|
86
|
-
if response.is_a?(
|
87
|
-
|
148
|
+
if response.is_a?(__data__.class)
|
149
|
+
with(__data__: response)
|
88
150
|
else
|
89
151
|
response
|
90
152
|
end
|
@@ -97,3 +159,4 @@ end
|
|
97
159
|
|
98
160
|
require 'rom/repository/changeset/create'
|
99
161
|
require 'rom/repository/changeset/update'
|
162
|
+
require 'rom/repository/changeset/delete'
|
@@ -4,6 +4,15 @@ module ROM
|
|
4
4
|
#
|
5
5
|
# @api public
|
6
6
|
class Create < Changeset
|
7
|
+
# @!attribute [r] association
|
8
|
+
# @return [Array] Associated changesets with its association name
|
9
|
+
option :association, reader: true, optional: true
|
10
|
+
|
11
|
+
# @api public
|
12
|
+
def associate(other, assoc)
|
13
|
+
with(association: [other, assoc])
|
14
|
+
end
|
15
|
+
|
7
16
|
# Return false
|
8
17
|
#
|
9
18
|
# @return [FalseClass]
|
@@ -21,6 +30,31 @@ module ROM
|
|
21
30
|
def create?
|
22
31
|
true
|
23
32
|
end
|
33
|
+
|
34
|
+
# @api private
|
35
|
+
def command
|
36
|
+
if options[:association]
|
37
|
+
other, assoc = options[:association]
|
38
|
+
|
39
|
+
if other.is_a?(Changeset)
|
40
|
+
create_command.curry(self) >> other.command.with_association(assoc)
|
41
|
+
else
|
42
|
+
create_command.with_association(assoc).curry(self, other)
|
43
|
+
end
|
44
|
+
else
|
45
|
+
create_command.curry(self)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# @api private
|
50
|
+
def create_command
|
51
|
+
command_compiler.(command_type, relation, mapper: false, result: result)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @api private
|
55
|
+
def default_command_type
|
56
|
+
:create
|
57
|
+
end
|
24
58
|
end
|
25
59
|
end
|
26
60
|
end
|