rom-repository 0.2.0 → 0.3.0
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/.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]
|
10
10
|
[][travis]
|
11
|
-
[][gemnasium]
|
12
12
|
[][codeclimate]
|
13
13
|
[][codeclimate]
|
14
14
|
[][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
|