rom-repository 1.0.0.beta3 → 1.0.0.rc1

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.
@@ -2,37 +2,65 @@ require 'dry/core/class_attributes'
2
2
  require 'dry/core/cache'
3
3
 
4
4
  require 'rom/initializer'
5
- require 'rom/repository/changeset/pipe'
6
5
 
7
6
  module ROM
7
+ # Abstract Changeset class
8
+ #
9
+ # If you inherit from this class you need to configure additional settings
10
+ #
11
+ # @example define a custom changeset using :upsert command
12
+ #
13
+ # class NewTag < ROM::Changeset[:tags]
14
+ # def default_command_type
15
+ # :upsert
16
+ # end
17
+ # end
18
+ #
19
+ # @abstract
8
20
  class Changeset
21
+ DEFAULT_COMMAND_OPTS = { mapper: false }.freeze
22
+
9
23
  extend Initializer
10
24
  extend Dry::Core::Cache
11
25
  extend Dry::Core::ClassAttributes
12
26
 
27
+ # @!method self.command_type
28
+ # Get or set changeset command type
29
+ #
30
+ # @overload command_type
31
+ # Return configured command_type
32
+ # @return [Symbol]
33
+ #
34
+ # @overload command_type(identifier)
35
+ # Set relation identifier for this changeset
36
+ # @param [Symbol] identifier The command type identifier
37
+ # @return [Symbol]
38
+ defines :command_type
39
+
40
+ # @!method self.relation
41
+ # Get or set changeset relation identifier
42
+ #
43
+ # @overload relation
44
+ # Return configured relation identifier for this changeset
45
+ # @return [Symbol]
46
+ #
47
+ # @overload relation(identifier)
48
+ # Set relation identifier for this changeset
49
+ # @param [Symbol] identifier The relation identifier from the ROM container
50
+ # @return [Symbol]
13
51
  defines :relation
14
52
 
15
53
  # @!attribute [r] relation
16
54
  # @return [Relation] The changeset relation
17
55
  param :relation
18
56
 
19
- # @!attribute [r] __data__
20
- # @return [Hash] The relation data
21
- option :__data__, reader: true, optional: true, default: proc { nil }
22
-
23
- # @!attribute [r] pipe
24
- # @return [Changeset::Pipe] data transformation pipe
25
- option :pipe, reader: true, accept: [Proc, Pipe], default: -> changeset {
26
- changeset.class.default_pipe(changeset)
27
- }
28
-
29
57
  # @!attribute [r] command_compiler
30
58
  # @return [Proc] a proc that can compile a command (typically provided by a repo)
31
59
  option :command_compiler, reader: true, optional: true
32
60
 
33
61
  # @!attribute [r] command_type
34
62
  # @return [Symbol] a custom command identifier
35
- option :command_type, reader: true, optional: true, default: -> changeset { changeset.default_command_type }
63
+ option :command_type, reader: true, default: -> changeset { changeset.class.command_type }
36
64
 
37
65
  # Create a changeset class preconfigured for a specific relation
38
66
  #
@@ -49,129 +77,24 @@ module ROM
49
77
  }
50
78
  end
51
79
 
52
- # Define a changeset mapping
53
- #
54
- # Subsequent mapping definitions will be composed together
55
- # and applied in the order they way defined
56
- #
57
- # @example Transformation DSL
58
- # class NewUser < ROM::Changeset::Create
59
- # map do
60
- # unwrap :address, prefix: true
61
- # end
62
- # end
63
- #
64
- # @example Using custom block
65
- # class NewUser < ROM::Changeset::Create
66
- # map do |tuple|
67
- # tuple.merge(created_at: Time.now)
68
- # end
69
- # end
70
- #
71
- # @example Multiple mappings (executed in the order of definition)
72
- # class NewUser < ROM::Changeset::Create
73
- # map do
74
- # unwrap :address, prefix: true
75
- # end
80
+ # Return a new changeset with updated options
76
81
  #
77
- # map do |tuple|
78
- # tuple.merge(created_at: Time.now)
79
- # end
82
+ # @example
83
+ # class NewUser < ROM::Changeset::Create[:users]
84
+ # option :token_generator, reader: true
80
85
  # end
81
86
  #
82
- # @return [Array<Pipe, Transproc::Function>]
83
- #
84
- # @api public
85
- def self.map(&block)
86
- if block.arity.zero?
87
- pipes << Class.new(Pipe, &block).new
88
- else
89
- pipes << Pipe.new(block)
90
- end
91
- end
92
-
93
- # Build default pipe object
94
- #
95
- # This can be overridden in a custom changeset subclass
96
- #
97
- # @return [Pipe]
98
- def self.default_pipe(context)
99
- pipes.size > 0 ? pipes.map { |p| p.bind(context) }.reduce(:>>) : Pipe.new
100
- end
101
-
102
- # @api private
103
- def self.inherited(klass)
104
- return if klass == ROM::Changeset
105
- super
106
- klass.instance_variable_set(:@__pipes__, pipes ? pipes.dup : [])
107
- end
108
-
109
- # @api private
110
- def self.pipes
111
- @__pipes__
112
- end
113
-
114
- # Pipe changeset's data using custom steps define on the pipe
115
- #
116
- # @param *steps [Array<Symbol>] A list of mapping steps
117
- #
118
- # @return [Changeset]
119
- #
120
- # @api public
121
- def map(*steps, &block)
122
- if block
123
- __data__.map { |*args| yield(*args) }
124
- else
125
- with(pipe: steps.reduce(pipe) { |a, e| a >> pipe[e] })
126
- end
127
- end
128
-
129
- # Coerce changeset to a hash
130
- #
131
- # This will send the data through the pipe
132
- #
133
- # @return [Hash]
134
- #
135
- # @api public
136
- def to_h
137
- pipe.call(__data__)
138
- end
139
- alias_method :to_hash, :to_h
140
-
141
- # Coerce changeset to an array
142
- #
143
- # This will send the data through the pipe
144
- #
145
- # @return [Array]
146
- #
147
- # @api public
148
- def to_a
149
- result == :one ? [to_h] : __data__.map { |element| pipe.call(element) }
150
- end
151
- alias_method :to_ary, :to_a
152
-
153
- # Return a new changeset with updated options
87
+ # changeset = user_repo.changeset(NewUser).with(token_generator: my_token_gen)
154
88
  #
155
89
  # @param [Hash] new_options The new options
156
90
  #
157
91
  # @return [Changeset]
158
92
  #
159
- # @api private
93
+ # @api public
160
94
  def with(new_options)
161
95
  self.class.new(relation, options.merge(new_options))
162
96
  end
163
97
 
164
- # Return changeset with data
165
- #
166
- # @param [Hash] data
167
- #
168
- # @return [Changeset]
169
- #
170
- # @api public
171
- def data(data)
172
- with(__data__: data)
173
- end
174
-
175
98
  # Persist changeset
176
99
  #
177
100
  # @example
@@ -186,39 +109,36 @@ module ROM
186
109
  command.call
187
110
  end
188
111
 
189
- # Return command result type
112
+ # Return string representation of the changeset
190
113
  #
191
- # @return [Symbol]
114
+ # @return [String]
192
115
  #
193
- # @api private
194
- def result
195
- __data__.is_a?(Hash) ? :one : :many
116
+ # @api public
117
+ def inspect
118
+ %(#<#{self.class} relation=#{relation.name.inspect}>)
196
119
  end
197
120
 
198
- private
199
-
121
+ # Return a command for this changeset
122
+ #
123
+ # @return [ROM::Command]
124
+ #
200
125
  # @api private
201
- def respond_to_missing?(meth, include_private = false)
202
- super || __data__.respond_to?(meth)
126
+ def command
127
+ command_compiler.(command_type, relation_identifier, DEFAULT_COMMAND_OPTS).new(relation)
203
128
  end
204
129
 
130
+ private
131
+
205
132
  # @api private
206
- def method_missing(meth, *args, &block)
207
- if __data__.respond_to?(meth)
208
- response = __data__.__send__(meth, *args, &block)
209
-
210
- if response.is_a?(__data__.class)
211
- with(__data__: response)
212
- else
213
- response
214
- end
215
- else
216
- super
217
- end
133
+ def relation_identifier
134
+ relation.name.relation
218
135
  end
219
136
  end
220
137
  end
221
138
 
139
+ require 'rom/repository/changeset/stateful'
140
+ require 'rom/repository/changeset/associated'
141
+
222
142
  require 'rom/repository/changeset/create'
223
143
  require 'rom/repository/changeset/update'
224
144
  require 'rom/repository/changeset/delete'
@@ -38,7 +38,7 @@ module ROM
38
38
  # Define which relations your repository is going to use
39
39
  #
40
40
  # @example
41
- # class MyRepo < ROM::Repository::Base
41
+ # class MyRepo < ROM::Repository
42
42
  # relations :users, :tasks
43
43
  # end
44
44
  #
@@ -83,7 +83,7 @@ module ROM
83
83
  # commands :create, mapper: :my_custom_mapper
84
84
  # end
85
85
  #
86
- # @param *names [Array<Symbol>] A list of command names
86
+ # @param [Array<Symbol>] names A list of command names
87
87
  # @option :mapper [Symbol] An optional mapper identifier
88
88
  # @option :use [Symbol] An optional command plugin identifier
89
89
  #
@@ -113,7 +113,7 @@ module ROM
113
113
  # @api private
114
114
  def define_command_method(type, **opts)
115
115
  define_method(type) do |input|
116
- if input.is_a?(Changeset)
116
+ if input.respond_to?(:commit)
117
117
  map_tuple(input.relation, input.commit)
118
118
  else
119
119
  command(type => self.class.root, **opts).call(input)
@@ -131,7 +131,7 @@ module ROM
131
131
 
132
132
  changeset = input.first
133
133
 
134
- if changeset.is_a?(Changeset)
134
+ if changeset.respond_to?(:commit)
135
135
  map_tuple(changeset.relation, changeset.commit)
136
136
  else
137
137
  command(type => self.class.root, **opts)
@@ -26,13 +26,15 @@ module ROM
26
26
  # in repositories. It might be worth looking into removing this requirement
27
27
  # from rom core Command::Graph API.
28
28
  #
29
- # @param container [ROM::Container] container where relations are stored
30
- # @param type [Symbol] The type of command
31
- # @param adapter [Symbol] The adapter identifier
32
- # @param ast [Array] The AST representation of a relation
33
- # @param plugins [Array<Symbol>] A list of optional command plugins that should be used
29
+ # @overload [](container, type, adapter, ast, plugins, options)
34
30
  #
35
- # @return [Command, CommandProxy]
31
+ # @param container [ROM::Container] container where relations are stored
32
+ # @param type [Symbol] The type of command
33
+ # @param adapter [Symbol] The adapter identifier
34
+ # @param ast [Array] The AST representation of a relation
35
+ # @param plugins [Array<Symbol>] A list of optional command plugins that should be used
36
+ #
37
+ # @return [Command, CommandProxy]
36
38
  #
37
39
  # @api private
38
40
  def self.[](*args)
@@ -112,24 +114,24 @@ module ROM
112
114
  name, meta, header = node
113
115
  other = visit(header, name)
114
116
 
115
- mapping =
116
- if meta[:combine_type] == :many
117
- name
118
- else
119
- { Dry::Core::Inflector.singularize(name).to_sym => name }
120
- end
121
-
122
117
  if type
123
118
  register_command(name, type, meta, parent_relation)
124
119
 
120
+ mapping =
121
+ if meta[:combine_type] == :many
122
+ name
123
+ else
124
+ { Dry::Core::Inflector.singularize(name).to_sym => name }
125
+ end
126
+
125
127
  if other.size > 0
126
128
  [mapping, [type, other]]
127
129
  else
128
130
  [mapping, type]
129
131
  end
130
132
  else
131
- registry[name][id] = container.commands[name][id].with(options)
132
- [mapping, id]
133
+ registry[name][id] = container.commands[name][id]
134
+ [name, id]
133
135
  end
134
136
  end
135
137
 
@@ -4,6 +4,8 @@ module ROM
4
4
  class Repository
5
5
  # TODO: look into making command graphs work without the root key in the input
6
6
  # so that we can get rid of this wrapper
7
+ #
8
+ # @api private
7
9
  class CommandProxy
8
10
  attr_reader :command, :root
9
11
 
@@ -48,8 +48,6 @@ module ROM
48
48
  # @option :many [Hash] Sets options for "has-many" type of association
49
49
  # @option :one [Hash] Sets options for "has-one/belongs-to" type of association
50
50
  #
51
- # @param [Hash] options
52
- #
53
51
  # @return [RelationProxy]
54
52
  #
55
53
  # @api public
@@ -190,8 +188,9 @@ module ROM
190
188
  # used to get the keys. Otherwise we fall back to using default keys based
191
189
  # on naming conventions.
192
190
  #
193
- # @param [RelationProxy] relation
194
- # @param [Symbol] type The type can be either :parent or :children
191
+ # @param [Relation::Name] source The source relation name
192
+ # @param [Relation::Name] target The target relation name
193
+ # @param [Symbol] type The association type, can be either :parent or :children
195
194
  #
196
195
  # @return [Hash<Symbol=>Symbol>]
197
196
  #
@@ -52,8 +52,21 @@ module ROM
52
52
 
53
53
  # Maps the wrapped relation with other mappers available in the registry
54
54
  #
55
- # @param *names [Array<Symbol, Class>] Either a list of mapper identifiers
56
- # or a custom model class
55
+ # @overload map_with(model)
56
+ # Map tuples to the provided custom model class
57
+ #
58
+ # @example
59
+ # users.as(MyUserModel)
60
+ #
61
+ # @param [Class>] model Your custom model class
62
+ #
63
+ # @overload map_with(*mappers)
64
+ # Map tuples using registered mappers
65
+ #
66
+ # @example
67
+ # users.as(MyUserModel)
68
+ #
69
+ # @param [Array<Symbol>] mappers A list of mapper identifiers
57
70
  #
58
71
  # @return [RelationProxy] A new relation proxy with pipelined relation
59
72
  #
@@ -67,6 +80,15 @@ module ROM
67
80
  end
68
81
  alias_method :as, :map_with
69
82
 
83
+ # Return a string representation of this relation proxy
84
+ #
85
+ # @return [String]
86
+ #
87
+ # @api public
88
+ def inspect
89
+ %(#<#{relation.class} name=#{name} dataset=#{dataset.inspect}>)
90
+ end
91
+
70
92
  # Infers a mapper for the wrapped relation
71
93
  #
72
94
  # @return [ROM::Mapper]
@@ -136,11 +158,6 @@ module ROM
136
158
  relation.respond_to?(meth) || super
137
159
  end
138
160
 
139
- # @api public
140
- def inspect
141
- %(#<#{relation.class} name=#{name} dataset=#{dataset.inspect}>)
142
- end
143
-
144
161
  private
145
162
 
146
163
  # @api private
@@ -31,6 +31,19 @@ module ROM
31
31
  class Root < Repository
32
32
  extend Dry::Core::ClassAttributes
33
33
 
34
+ # @!method self.root
35
+ # Get or set repository root relation identifier
36
+ #
37
+ # This method is automatically used when you define a class using
38
+ # MyRepo[:rel_identifier] shortcut
39
+ #
40
+ # @overload root
41
+ # Return root relation identifier
42
+ # @return [Symbol]
43
+ #
44
+ # @overload root(identifier)
45
+ # Set root relation identifier
46
+ # @return [Symbol]
34
47
  defines :root
35
48
 
36
49
  # @!attribute [r] root
@@ -131,7 +144,7 @@ module ROM
131
144
  #
132
145
  # @return [Changeset]
133
146
  #
134
- # @override Repository#changeset
147
+ # @see Repository#changeset
135
148
  #
136
149
  # @api public
137
150
  def changeset(*args)
@@ -1,6 +1,9 @@
1
1
  require 'dry/equalizer'
2
2
 
3
3
  module ROM
4
+ # TODO: finish this in 1.1.0
5
+ #
6
+ # @api private
4
7
  class Session
5
8
  include Dry::Equalizer(:queue, :status)
6
9
 
@@ -3,7 +3,7 @@ require 'dry/core/cache'
3
3
  require 'dry/core/class_builder'
4
4
 
5
5
  require 'rom/struct'
6
- require 'rom/schema/type'
6
+ require 'rom/schema/attribute'
7
7
 
8
8
  module ROM
9
9
  class Repository
@@ -1,5 +1,5 @@
1
1
  module ROM
2
2
  class Repository
3
- VERSION = '1.0.0.beta3'.freeze
3
+ VERSION = '1.0.0.rc1'.freeze
4
4
  end
5
5
  end