rom-repository 1.0.0.beta3 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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