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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +11 -13
  3. data/CHANGELOG.md +25 -0
  4. data/Gemfile +13 -3
  5. data/lib/rom/repository.rb +57 -19
  6. data/lib/rom/repository/changeset.rb +89 -26
  7. data/lib/rom/repository/changeset/create.rb +34 -0
  8. data/lib/rom/repository/changeset/delete.rb +15 -0
  9. data/lib/rom/repository/changeset/pipe.rb +11 -4
  10. data/lib/rom/repository/changeset/update.rb +11 -1
  11. data/lib/rom/repository/command_compiler.rb +51 -30
  12. data/lib/rom/repository/command_proxy.rb +3 -1
  13. data/lib/rom/repository/header_builder.rb +3 -3
  14. data/lib/rom/repository/mapper_builder.rb +2 -2
  15. data/lib/rom/repository/relation_proxy.rb +26 -35
  16. data/lib/rom/repository/relation_proxy/combine.rb +59 -27
  17. data/lib/rom/repository/root.rb +4 -6
  18. data/lib/rom/repository/session.rb +55 -0
  19. data/lib/rom/repository/struct_builder.rb +29 -17
  20. data/lib/rom/repository/version.rb +1 -1
  21. data/lib/rom/struct.rb +11 -20
  22. data/rom-repository.gemspec +4 -3
  23. data/spec/integration/command_macros_spec.rb +5 -2
  24. data/spec/integration/command_spec.rb +0 -6
  25. data/spec/integration/multi_adapter_spec.rb +8 -5
  26. data/spec/integration/repository_spec.rb +58 -2
  27. data/spec/integration/root_repository_spec.rb +9 -2
  28. data/spec/integration/typed_structs_spec.rb +31 -0
  29. data/spec/shared/database.rb +5 -1
  30. data/spec/shared/relations.rb +3 -1
  31. data/spec/shared/repo.rb +13 -1
  32. data/spec/shared/structs.rb +39 -0
  33. data/spec/spec_helper.rb +7 -5
  34. data/spec/support/mutant.rb +10 -0
  35. data/spec/unit/changeset/map_spec.rb +42 -0
  36. data/spec/unit/changeset_spec.rb +32 -6
  37. data/spec/unit/relation_proxy_spec.rb +27 -9
  38. data/spec/unit/repository/changeset_spec.rb +125 -0
  39. data/spec/unit/repository/inspect_spec.rb +18 -0
  40. data/spec/unit/repository/session_spec.rb +251 -0
  41. data/spec/unit/session_spec.rb +54 -0
  42. data/spec/unit/struct_builder_spec.rb +45 -1
  43. metadata +41 -17
  44. data/lib/rom/repository/struct_attributes.rb +0 -46
  45. data/spec/unit/header_builder_spec.rb +0 -73
  46. data/spec/unit/plugins/view_spec.rb +0 -29
  47. data/spec/unit/sql/relation_spec.rb +0 -54
  48. data/spec/unit/struct_spec.rb +0 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: db1338c97747a9adc2b9f00febe402d61ac1a6fd
4
- data.tar.gz: bd6ca3140fe897a0d6b9cccc3082e0fdaf63ed9a
3
+ metadata.gz: a5e3e3120dbe44e4f2f116cfe7aaaba6b2ef0a7e
4
+ data.tar.gz: 1e29da4f4c4c2c8c6cf7d9a70c794365419bc18d
5
5
  SHA512:
6
- metadata.gz: 04f9d30b32805886118b5eb36451aeba065cc510d1a92d091c1e99a41585eb821cf69b68fa56b1f049610b390d1403316a662dacfe4ba32b7f65ba6ac60b8e84
7
- data.tar.gz: 48a3b4136e064e00a562a584555765e8fb47a05388457b3cb31ab3b762dda4dd221299a1b621dfaf3705167412af644c8244c7ebbde22da285a8985f3afb712b
6
+ metadata.gz: b40d4d1a1c984998a0f58287cf586dd151251f9e9cabddcf22fff0a23e944f7113a1ffef5be76e6c44560d99d9c4d7bcecbb78f074f5079057ac71f7da0708a8
7
+ data.tar.gz: 8ec19211a0103e67f33578166bb781c1d42271d04e5561451e023aaccc4722d90b9e2fa3061cc1a021efca5c5f13fad00b105f9d72cfb66c1a4f86a679422c13
data/.travis.yml CHANGED
@@ -1,28 +1,26 @@
1
1
  language: ruby
2
- sudo: false
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.1
10
- - 2.2
11
- - 2.3.1
12
- - rbx-2
13
- - jruby-9.0.5.0
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: ruby-head
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', '~> 0.8'
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
- gem 'codeclimate-test-reporter', require: nil
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', '~> 4.2'
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
@@ -1,5 +1,4 @@
1
- require 'rom/support/deprecations'
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 2 or 3 arguments'
180
+ raise ArgumentError, 'Repository#changeset accepts 1-3 arguments'
175
181
  end
176
182
 
177
- relation = relations[name]
178
-
179
- if pk
180
- Changeset::Update.new(relation, data, primary_key: pk)
183
+ if type
184
+ if type.equal?(:delete)
185
+ Changeset::Delete.new(relation, opts)
186
+ end
181
187
  else
182
- Changeset::Create.new(relation, data)
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
- else
241
+ elsif mapper.nil?
209
242
  mapper_instance = mappers[ast]
210
243
  end
211
244
 
212
- command = CommandCompiler[container, type, adapter, ast, use]
213
- command >> mapper_instance
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 'rom/support/constants'
2
- require 'rom/support/options'
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
- include Options
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] relation
17
- # @return [Relation] The changeset relation
18
- attr_reader :relation
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] data
21
- # @return [Hash] The relation data
22
- attr_reader :data
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
- with(pipe: steps.reduce(pipe) { |a, e| a >> pipe.class[e] })
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(data)
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, data, options.merge(new_options))
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 || data.respond_to?(meth)
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 data.respond_to?(meth)
84
- response = data.__send__(meth, *args, &block)
145
+ if __data__.respond_to?(meth)
146
+ response = __data__.__send__(meth, *args, &block)
85
147
 
86
- if response.is_a?(Hash)
87
- self.class.new(relation, response, options)
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