rom-repository 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +5 -5
- data/CHANGELOG.md +24 -0
- data/Gemfile +21 -5
- data/README.md +6 -110
- data/lib/rom/repository/changeset/create.rb +26 -0
- data/lib/rom/repository/changeset/pipe.rb +40 -0
- data/lib/rom/repository/changeset/update.rb +82 -0
- data/lib/rom/repository/changeset.rb +99 -0
- data/lib/rom/repository/class_interface.rb +142 -0
- data/lib/rom/repository/command_compiler.rb +214 -0
- data/lib/rom/repository/command_proxy.rb +22 -0
- data/lib/rom/repository/header_builder.rb +13 -16
- data/lib/rom/repository/mapper_builder.rb +7 -14
- data/lib/rom/repository/{loading_proxy → relation_proxy}/wrap.rb +7 -7
- data/lib/rom/repository/relation_proxy.rb +225 -0
- data/lib/rom/repository/root.rb +110 -0
- data/lib/rom/repository/struct_attributes.rb +46 -0
- data/lib/rom/repository/struct_builder.rb +31 -14
- data/lib/rom/repository/version.rb +1 -1
- data/lib/rom/repository.rb +192 -31
- data/lib/rom/struct.rb +13 -8
- data/rom-repository.gemspec +9 -10
- data/spec/integration/changeset_spec.rb +86 -0
- data/spec/integration/command_macros_spec.rb +175 -0
- data/spec/integration/command_spec.rb +224 -0
- data/spec/integration/multi_adapter_spec.rb +3 -3
- data/spec/integration/repository_spec.rb +97 -2
- data/spec/integration/root_repository_spec.rb +88 -0
- data/spec/shared/database.rb +47 -3
- data/spec/shared/mappers.rb +35 -0
- data/spec/shared/models.rb +41 -0
- data/spec/shared/plugins.rb +66 -0
- data/spec/shared/relations.rb +76 -0
- data/spec/shared/repo.rb +38 -17
- data/spec/shared/seeds.rb +19 -0
- data/spec/spec_helper.rb +4 -1
- data/spec/support/mapper_registry.rb +1 -3
- data/spec/unit/changeset_spec.rb +58 -0
- data/spec/unit/header_builder_spec.rb +34 -35
- data/spec/unit/relation_proxy_spec.rb +170 -0
- data/spec/unit/sql/relation_spec.rb +5 -5
- data/spec/unit/struct_builder_spec.rb +7 -4
- data/spec/unit/struct_spec.rb +22 -0
- metadata +38 -41
- data/lib/rom/plugins/relation/key_inference.rb +0 -31
- data/lib/rom/repository/loading_proxy/combine.rb +0 -158
- data/lib/rom/repository/loading_proxy.rb +0 -182
- data/spec/unit/loading_proxy_spec.rb +0 -147
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bac31b2dc1dafd64ec8a3852d1cb9e595c49ded5
|
4
|
+
data.tar.gz: 9110fc0e2f6d1b742ed89c4f7afb5c9b61ad0cdc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 77b81f8ab9040eeaa5059dee76046f2330ebaddc75c1dad792e1b7e911d42bd4056d364d5b59194208c55d366d3d3e3358376292abfa2580f4e96a86b3ab1e83
|
7
|
+
data.tar.gz: c6b9d0809edd8b5b8742ff9de4a1761c287f502706416bf698453a1153cea2f122972d0dcbfdc6d06ead0acc55100b9a1f4d230fd13d24bb536e0969b301d4ac
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -6,13 +6,11 @@ before_script:
|
|
6
6
|
- psql -c 'create database rom_repository' -U postgres
|
7
7
|
script: "bundle exec rake ci"
|
8
8
|
rvm:
|
9
|
-
- 2.0
|
10
9
|
- 2.1
|
11
10
|
- 2.2
|
12
|
-
- 2.3.
|
11
|
+
- 2.3.1
|
13
12
|
- rbx-2
|
14
|
-
- jruby-
|
15
|
-
- jruby-head
|
13
|
+
- jruby-9.0.5.0
|
16
14
|
- ruby-head
|
17
15
|
env:
|
18
16
|
global:
|
@@ -22,7 +20,9 @@ matrix:
|
|
22
20
|
allow_failures:
|
23
21
|
- rvm: ruby-head
|
24
22
|
- rvm: jruby-head
|
25
|
-
|
23
|
+
include:
|
24
|
+
- rvm: jruby-head
|
25
|
+
before_install: gem install bundler --no-ri --no-rdoc
|
26
26
|
notifications:
|
27
27
|
webhooks:
|
28
28
|
urls:
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,27 @@
|
|
1
|
+
# v0.3.0 2016-07-27
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* `Repository#command` for inferring commands automatically from relations (solnic)
|
6
|
+
* `Repository.commands` macro which generates command methods (solnic)
|
7
|
+
* `Repository[rel_name]` for setting up a repository with a root relation (solnic)
|
8
|
+
* `Repository#aggregate` as a shortcut for composing relation graphs from root (solnic)
|
9
|
+
* `Repository#changeset` API for building specialized objects for handling changes in relations (solnic)
|
10
|
+
|
11
|
+
### Fixed
|
12
|
+
|
13
|
+
* Auto-mapping includes default custom mapper for a given relation (AMHOL)
|
14
|
+
* When custom mapper is set, default struct mapper won't be used (AMHOL)
|
15
|
+
|
16
|
+
### Changed
|
17
|
+
|
18
|
+
* `Relation#combine` supports passing name of configured associations for automatic relation composition (solnic)
|
19
|
+
* `Repository` constructor simply expects a rom container, which makes it work with DI libs like `dry-auto_inject` (solnic)
|
20
|
+
* Depends on `rom 2.0.0` now (solnic)
|
21
|
+
* Replace anima with `ROM::Repository::StructAttributes` (flash-gordon)
|
22
|
+
|
23
|
+
[Compare v0.2.0...v0.3.0](https://github.com/rom-rb/rom-repository/compare/v0.2.0...v0.3.0)
|
24
|
+
|
1
25
|
# v0.2.0 2016-01-06
|
2
26
|
|
3
27
|
### Added
|
data/Gemfile
CHANGED
@@ -4,14 +4,30 @@ gemspec
|
|
4
4
|
|
5
5
|
gem 'inflecto'
|
6
6
|
|
7
|
+
group :development, :test do
|
8
|
+
gem 'rom-sql', '~> 0.8'
|
9
|
+
end
|
10
|
+
|
11
|
+
group :development do
|
12
|
+
gem 'dry-equalizer', '~> 0.2'
|
13
|
+
gem 'sqlite3', platforms: [:mri, :rbx]
|
14
|
+
gem 'jdbc-sqlite3', platforms: :jruby
|
15
|
+
end
|
16
|
+
|
7
17
|
group :test do
|
8
|
-
gem 'anima', '~> 0.2.0'
|
9
|
-
gem 'rom-sql', '~> 0.7.0'
|
10
18
|
gem 'rspec'
|
11
19
|
gem 'byebug', platforms: :mri
|
12
20
|
gem 'pg', platforms: [:mri, :rbx]
|
13
|
-
gem '
|
14
|
-
gem
|
21
|
+
gem 'jdbc-postgres', platforms: :jruby
|
22
|
+
gem 'codeclimate-test-reporter', require: nil
|
15
23
|
end
|
16
24
|
|
17
|
-
|
25
|
+
group :benchmarks do
|
26
|
+
gem 'hotch', platforms: :mri
|
27
|
+
gem 'benchmark-ips'
|
28
|
+
gem 'activerecord', '~> 4.2'
|
29
|
+
end
|
30
|
+
|
31
|
+
group :tools do
|
32
|
+
gem 'pry'
|
33
|
+
end
|
data/README.md
CHANGED
@@ -4,125 +4,21 @@
|
|
4
4
|
[codeclimate]: https://codeclimate.com/github/rom-rb/rom-repository
|
5
5
|
[inchpages]: http://inch-ci.org/github/rom-rb/rom-repository
|
6
6
|
|
7
|
-
#
|
7
|
+
# rom-repository
|
8
8
|
|
9
9
|
[![Gem Version](https://badge.fury.io/rb/rom-repository.svg)][gem]
|
10
10
|
[![Build Status](https://travis-ci.org/rom-rb/rom-repository.svg?branch=master)][travis]
|
11
|
-
[![Dependency Status](https://gemnasium.com/rom-rb/rom-repository.
|
11
|
+
[![Dependency Status](https://gemnasium.com/rom-rb/rom-repository.svg)][gemnasium]
|
12
12
|
[![Code Climate](https://codeclimate.com/github/rom-rb/rom-repository/badges/gpa.svg)][codeclimate]
|
13
13
|
[![Test Coverage](https://codeclimate.com/github/rom-rb/rom-repository/badges/coverage.svg)][codeclimate]
|
14
14
|
[![Inline docs](http://inch-ci.org/github/rom-rb/rom-repository.svg?branch=master)][inchpages]
|
15
15
|
|
16
|
-
Repository for [
|
17
|
-
extensions.
|
16
|
+
Repository for [rom-rb](https://github.com/rom-rb/rom) with auto-mapping and commands.
|
18
17
|
|
19
|
-
|
18
|
+
Resources:
|
20
19
|
|
21
|
-
|
22
|
-
|
23
|
-
relations that you define and use them to build more complex views.
|
24
|
-
|
25
|
-
Repository relations are enhanced with a couple of extra features on top of ROM
|
26
|
-
relations:
|
27
|
-
|
28
|
-
- Every relation has an auto-generated mapper which turns raw data into simple, immutable structs
|
29
|
-
- `Relation#combine` can accept a simple hash defining what other relations should be joined
|
30
|
-
- `Relation#combine_parents` automatically joins parents using eager-loading
|
31
|
-
- `Relation#combine_children` automatically joins children using eager-loading
|
32
|
-
- `Relation#wrap_parent` automatically joins a parent using inner join
|
33
|
-
|
34
|
-
### Relation Views
|
35
|
-
|
36
|
-
A relation view is a result of some query which returns results specific to your
|
37
|
-
application. You can define them using a simple DSL where you specify a name, what
|
38
|
-
attributes resulting tuples will have and of course the query itself:
|
39
|
-
|
40
|
-
``` ruby
|
41
|
-
class Users < ROM::Relation[:sql]
|
42
|
-
view(:by_id, [:id, :name]) do |id|
|
43
|
-
where(id: id).select(:id, :name)
|
44
|
-
end
|
45
|
-
|
46
|
-
view(:listing, [:id, :name, :email, :created_at]) do
|
47
|
-
select(:id, :name, :email, :created_at).order(:name)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
```
|
51
|
-
|
52
|
-
This way we can explicitly define all our relation view that our application will
|
53
|
-
depend on. It encapsulates access to application-specific data structures and allows
|
54
|
-
you to easily test individual views in isolation.
|
55
|
-
|
56
|
-
Thanks to explicit definition of attributes mappers are derived automatically.
|
57
|
-
|
58
|
-
### Auto-combine & Auto-wrap
|
59
|
-
|
60
|
-
Repository relations support automatic `combine` and `wrap` by using a simple
|
61
|
-
convention that every relation defines `for_combine(keys, other)` and `for_wrap(keys, other)`.
|
62
|
-
|
63
|
-
You can override the default behavior for combine by defining `for_other_rel_name`
|
64
|
-
in example, if you combine tasks with users you can define `Tasks#for_users` and
|
65
|
-
this will be used instead of the generic `for_combine`.
|
66
|
-
|
67
|
-
### Mapping & Structs
|
68
|
-
|
69
|
-
Currently repositories map to `ROM::Struct` by default. In the near future this
|
70
|
-
will be configurable.
|
71
|
-
|
72
|
-
ROM structs are simple and don't expose an interface to mutate them; however, they
|
73
|
-
are not being frozen (at least not yet, we could add a feature for freezing them).
|
74
|
-
|
75
|
-
They are coercible to `Hash` so it should be possible to map them further in some
|
76
|
-
special cases using ROM mappers.
|
77
|
-
|
78
|
-
``` ruby
|
79
|
-
class Users < ROM::Relation[:sql]
|
80
|
-
view(:by_id, [:id, :name]) do |id|
|
81
|
-
where(id: id).select(:id, :name)
|
82
|
-
end
|
83
|
-
|
84
|
-
view(:listing, [:id, :name, :email, :created_at]) do
|
85
|
-
select(:id, :name, :email, :created_at).order(:name)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
class UserRepository < ROM::Repository::Base
|
90
|
-
relations :users, :tasks
|
91
|
-
|
92
|
-
def by_id(id)
|
93
|
-
users.by_id(id)
|
94
|
-
end
|
95
|
-
|
96
|
-
def with_tasks(id)
|
97
|
-
users.by_id(id).combine_children(many: tasks)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
rom = ROM.finalize.env
|
102
|
-
|
103
|
-
user_repo = UserRepository.new(rom)
|
104
|
-
|
105
|
-
puts user_repo.by_id(1).to_a.inspect
|
106
|
-
# [#<ROM::Struct[User] id=1 name="Jane">]
|
107
|
-
|
108
|
-
puts user_repo.with_tasks.to_a.inspect
|
109
|
-
# [#<ROM::Struct[User] id=1 name="Jane" tasks=[#<ROM::Struct[Task] id=2 user_id=1 title="Jane Task">]>, #<ROM::Struct[User] id=2 name="Joe" tasks=[#<ROM::Struct[Task] id=1 user_id=2 title="Joe Task">]>]
|
110
|
-
```
|
111
|
-
|
112
|
-
### Using Custom Model Types
|
113
|
-
|
114
|
-
To use a custom model type you simply use the standard `Relation#as` inteface
|
115
|
-
but you can pass a constant:
|
116
|
-
|
117
|
-
``` ruby
|
118
|
-
class UserRepository < ROM::Repository::Base
|
119
|
-
relations :users, :tasks
|
120
|
-
|
121
|
-
def by_id(id)
|
122
|
-
users.by_id(id).as(User)
|
123
|
-
end
|
124
|
-
end
|
125
|
-
```
|
20
|
+
* [User documentation](http://rom-rb.org/learn/repositories)
|
21
|
+
* [API documentation](http://rubydoc.info/gems/rom-repository)
|
126
22
|
|
127
23
|
## License
|
128
24
|
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ROM
|
2
|
+
class Changeset
|
3
|
+
# Changeset specialization for create commands
|
4
|
+
#
|
5
|
+
# @api public
|
6
|
+
class Create < Changeset
|
7
|
+
# Return false
|
8
|
+
#
|
9
|
+
# @return [FalseClass]
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
def update?
|
13
|
+
false
|
14
|
+
end
|
15
|
+
|
16
|
+
# Return true
|
17
|
+
#
|
18
|
+
# @return [TrueClass]
|
19
|
+
#
|
20
|
+
# @api public
|
21
|
+
def create?
|
22
|
+
true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'transproc/registry'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
class Changeset
|
5
|
+
class Pipe
|
6
|
+
extend Transproc::Registry
|
7
|
+
|
8
|
+
attr_reader :processor
|
9
|
+
|
10
|
+
def self.add_timestamps(data)
|
11
|
+
now = Time.now
|
12
|
+
data.merge(created_at: now, updated_at: now)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.touch(data)
|
16
|
+
data.merge(updated_at: Time.now)
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(processor = nil)
|
20
|
+
@processor = processor
|
21
|
+
end
|
22
|
+
|
23
|
+
def >>(other)
|
24
|
+
if processor
|
25
|
+
self.class.new(processor >> other)
|
26
|
+
else
|
27
|
+
self.class.new(other)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def call(data)
|
32
|
+
if processor
|
33
|
+
processor.call(data)
|
34
|
+
else
|
35
|
+
data
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module ROM
|
2
|
+
class Changeset
|
3
|
+
# Changeset specialization for update commands
|
4
|
+
#
|
5
|
+
# @api public
|
6
|
+
class Update < Changeset
|
7
|
+
# @!attribute [r] primary_key
|
8
|
+
# @return [Symbol] The name of the relation's primary key attribute
|
9
|
+
option :primary_key, reader: true
|
10
|
+
|
11
|
+
# Return true
|
12
|
+
#
|
13
|
+
# @return [TrueClass]
|
14
|
+
#
|
15
|
+
# @api public
|
16
|
+
def update?
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
# Return false
|
21
|
+
#
|
22
|
+
# @return [FalseClass]
|
23
|
+
#
|
24
|
+
# @api public
|
25
|
+
def create?
|
26
|
+
false
|
27
|
+
end
|
28
|
+
|
29
|
+
# Return original tuple that this changeset may update
|
30
|
+
#
|
31
|
+
# @return [Hash]
|
32
|
+
#
|
33
|
+
# @api public
|
34
|
+
def original
|
35
|
+
@original ||= relation.fetch(primary_key)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Return diff hash sent through the pipe
|
39
|
+
#
|
40
|
+
# @return [Hash]
|
41
|
+
#
|
42
|
+
# @api public
|
43
|
+
def to_h
|
44
|
+
pipe.call(diff)
|
45
|
+
end
|
46
|
+
alias_method :to_hash, :to_h
|
47
|
+
|
48
|
+
# Return true if there's a diff between original and changeset data
|
49
|
+
#
|
50
|
+
# @return [TrueClass, FalseClass]
|
51
|
+
#
|
52
|
+
# @api public
|
53
|
+
def diff?
|
54
|
+
! diff.empty?
|
55
|
+
end
|
56
|
+
|
57
|
+
# Return if there's no diff between the original and changeset data
|
58
|
+
#
|
59
|
+
# @return [TrueClass, FalseClass]
|
60
|
+
#
|
61
|
+
# @api public
|
62
|
+
def clean?
|
63
|
+
diff.empty?
|
64
|
+
end
|
65
|
+
|
66
|
+
# Calculate the diff between the original and changeset data
|
67
|
+
#
|
68
|
+
# @return [Hash[
|
69
|
+
#
|
70
|
+
# @api public
|
71
|
+
def diff
|
72
|
+
@diff ||=
|
73
|
+
begin
|
74
|
+
new_tuple = data.to_a
|
75
|
+
ori_tuple = original.to_a
|
76
|
+
|
77
|
+
Hash[new_tuple - (new_tuple & ori_tuple)]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'rom/support/constants'
|
2
|
+
require 'rom/support/options'
|
3
|
+
|
4
|
+
require 'rom/repository/changeset/pipe'
|
5
|
+
|
6
|
+
module ROM
|
7
|
+
class Changeset
|
8
|
+
include Options
|
9
|
+
|
10
|
+
# @!attribute [r] pipe
|
11
|
+
# @return [Changeset::Pipe] data transformation pipe
|
12
|
+
option :pipe, reader: true, accept: [Proc, Pipe], default: -> changeset {
|
13
|
+
changeset.class.default_pipe
|
14
|
+
}
|
15
|
+
|
16
|
+
# @!attribute [r] relation
|
17
|
+
# @return [Relation] The changeset relation
|
18
|
+
attr_reader :relation
|
19
|
+
|
20
|
+
# @!attribute [r] data
|
21
|
+
# @return [Hash] The relation data
|
22
|
+
attr_reader :data
|
23
|
+
|
24
|
+
# Build default pipe object
|
25
|
+
#
|
26
|
+
# This can be overridden in a custom changeset subclass
|
27
|
+
#
|
28
|
+
# @return [Pipe]
|
29
|
+
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
|
38
|
+
end
|
39
|
+
|
40
|
+
# Pipe changeset's data using custom steps define on the pipe
|
41
|
+
#
|
42
|
+
# @param *steps [Array<Symbol>] A list of mapping steps
|
43
|
+
#
|
44
|
+
# @return [Changeset]
|
45
|
+
#
|
46
|
+
# @api public
|
47
|
+
def map(*steps)
|
48
|
+
with(pipe: steps.reduce(pipe) { |a, e| a >> pipe.class[e] })
|
49
|
+
end
|
50
|
+
|
51
|
+
# Coerce changeset to a hash
|
52
|
+
#
|
53
|
+
# This will send the data through the pipe
|
54
|
+
#
|
55
|
+
# @return [Hash]
|
56
|
+
#
|
57
|
+
# @api public
|
58
|
+
def to_h
|
59
|
+
pipe.call(data)
|
60
|
+
end
|
61
|
+
alias_method :to_hash, :to_h
|
62
|
+
|
63
|
+
# Return a new changeset with updated options
|
64
|
+
#
|
65
|
+
# @param [Hash] new_options The new options
|
66
|
+
#
|
67
|
+
# @return [Changeset]
|
68
|
+
#
|
69
|
+
# @api private
|
70
|
+
def with(new_options)
|
71
|
+
self.class.new(relation, data, options.merge(new_options))
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# @api private
|
77
|
+
def respond_to_missing?(meth, include_private = false)
|
78
|
+
super || data.respond_to?(meth)
|
79
|
+
end
|
80
|
+
|
81
|
+
# @api private
|
82
|
+
def method_missing(meth, *args, &block)
|
83
|
+
if data.respond_to?(meth)
|
84
|
+
response = data.__send__(meth, *args, &block)
|
85
|
+
|
86
|
+
if response.is_a?(Hash)
|
87
|
+
self.class.new(relation, response, options)
|
88
|
+
else
|
89
|
+
response
|
90
|
+
end
|
91
|
+
else
|
92
|
+
super
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
require 'rom/repository/changeset/create'
|
99
|
+
require 'rom/repository/changeset/update'
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module ROM
|
2
|
+
class Repository
|
3
|
+
# Class-level APIs for repositories
|
4
|
+
#
|
5
|
+
# @api public
|
6
|
+
module ClassInterface
|
7
|
+
# Create a root-repository class and set its root relation
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # where :users is the relation name in your rom container
|
11
|
+
# class UserRepo < ROM::Repository[:users]
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# @param name [Symbol] The relation `register_as` value
|
15
|
+
#
|
16
|
+
# @return [Class] descendant of ROM::Repository::Root
|
17
|
+
#
|
18
|
+
# @api public
|
19
|
+
def [](name)
|
20
|
+
klass = Class.new(self < Repository::Root ? self : Repository::Root)
|
21
|
+
klass.relations(name)
|
22
|
+
klass.root(name)
|
23
|
+
klass
|
24
|
+
end
|
25
|
+
|
26
|
+
# Inherits configured relations and commands
|
27
|
+
#
|
28
|
+
# @api private
|
29
|
+
def inherited(klass)
|
30
|
+
super
|
31
|
+
|
32
|
+
return if self === Repository
|
33
|
+
|
34
|
+
klass.relations(*relations)
|
35
|
+
klass.commands(*commands)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Define which relations your repository is going to use
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# class MyRepo < ROM::Repository::Base
|
42
|
+
# relations :users, :tasks
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# my_repo = MyRepo.new(rom)
|
46
|
+
#
|
47
|
+
# my_repo.users
|
48
|
+
# my_repo.tasks
|
49
|
+
#
|
50
|
+
# @return [Array<Symbol>]
|
51
|
+
#
|
52
|
+
# @api public
|
53
|
+
def relations(*names)
|
54
|
+
if names.any?
|
55
|
+
attr_reader(*names)
|
56
|
+
|
57
|
+
if defined?(@relations)
|
58
|
+
@relations.concat(names).uniq!
|
59
|
+
else
|
60
|
+
@relations = names
|
61
|
+
end
|
62
|
+
|
63
|
+
@relations
|
64
|
+
else
|
65
|
+
@relations
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Defines command methods on a root repository
|
70
|
+
#
|
71
|
+
# @example
|
72
|
+
# class UserRepo < ROM::Repository[:users]
|
73
|
+
# commands :create, update: :by_pk, delete: :by_pk
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# # with custom command plugin
|
77
|
+
# class UserRepo < ROM::Repository[:users]
|
78
|
+
# commands :create, plugin: :my_command_plugin
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# # with custom mapper
|
82
|
+
# class UserRepo < ROM::Repository[:users]
|
83
|
+
# commands :create, mapper: :my_custom_mapper
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# @param *names [Array<Symbol>] A list of command names
|
87
|
+
# @option :mapper [Symbol] An optional mapper identifier
|
88
|
+
# @option :use [Symbol] An optional command plugin identifier
|
89
|
+
#
|
90
|
+
# @return [Array<Symbol>] A list of defined command names
|
91
|
+
#
|
92
|
+
# @api public
|
93
|
+
def commands(*names, mapper: nil, use: nil, **opts)
|
94
|
+
if names.any? || opts.any?
|
95
|
+
@commands = names + opts.to_a
|
96
|
+
|
97
|
+
@commands.each do |spec|
|
98
|
+
type, *view = Array(spec).flatten
|
99
|
+
|
100
|
+
if view.size > 0
|
101
|
+
define_restricted_command_method(type, view, mapper: mapper, use: use)
|
102
|
+
else
|
103
|
+
define_command_method(type, mapper: mapper, use: use)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
else
|
107
|
+
@commands || []
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
# @api private
|
114
|
+
def define_command_method(type, **opts)
|
115
|
+
define_method(type) do |*args|
|
116
|
+
command(type => self.class.root, **opts).call(*args)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# @api private
|
121
|
+
def define_restricted_command_method(type, views, **opts)
|
122
|
+
views.each do |view_name|
|
123
|
+
meth_name = views.size > 1 ? :"#{type}_#{view_name}" : type
|
124
|
+
|
125
|
+
define_method(meth_name) do |*args|
|
126
|
+
view_args, *input = args
|
127
|
+
|
128
|
+
changeset = input.first
|
129
|
+
|
130
|
+
if changeset.is_a?(Changeset) && changeset.clean?
|
131
|
+
map_tuple(changeset.relation, changeset.original)
|
132
|
+
else
|
133
|
+
command(type => self.class.root, **opts)
|
134
|
+
.public_send(view_name, *view_args)
|
135
|
+
.call(*input)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|