rom 0.3.1 → 0.4.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.
@@ -0,0 +1,25 @@
1
+ module ROM
2
+ class Adapter
3
+ class Memory < Adapter
4
+
5
+ class Storage
6
+ attr_reader :data
7
+
8
+ def initialize(*)
9
+ super
10
+ @data = {}
11
+ end
12
+
13
+ def [](name)
14
+ data[name] ||= Dataset.new([])
15
+ end
16
+
17
+ def key?(name)
18
+ data.key?(name)
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+ end
25
+ end
data/lib/rom/boot.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'rom/boot/dsl'
2
2
  require 'rom/relation_builder'
3
3
  require 'rom/reader_builder'
4
+ require 'rom/command_registry'
4
5
 
5
6
  module ROM
6
7
 
@@ -18,6 +19,7 @@ module ROM
18
19
  @schema = {}
19
20
  @relations = {}
20
21
  @mappers = []
22
+ @commands = {}
21
23
  @adapter_relation_map = {}
22
24
  @env = nil
23
25
  end
@@ -77,6 +79,10 @@ module ROM
77
79
  self
78
80
  end
79
81
 
82
+ def commands(name, &block)
83
+ @commands.update(name => DSL.new(self).commands(&block))
84
+ end
85
+
80
86
  # Finalize the setup
81
87
  #
82
88
  # @return [Env] frozen env with access to repositories, schema, relations and mappers
@@ -88,8 +94,9 @@ module ROM
88
94
  schema = load_schema
89
95
  relations = load_relations(schema)
90
96
  readers = load_readers(relations)
97
+ commands = load_commands(relations)
91
98
 
92
- @env = Env.new(repositories, schema, relations, readers)
99
+ @env = Env.new(repositories, schema, relations, readers, commands)
93
100
  end
94
101
 
95
102
  # @api private
@@ -162,6 +169,26 @@ module ROM
162
169
  ReaderRegistry.new(readers)
163
170
  end
164
171
 
172
+ def load_commands(relations)
173
+ return Registry.new unless relations.elements.any?
174
+
175
+ commands = @commands.each_with_object({}) do |(name, definitions), h|
176
+ adapter = adapter_relation_map[name]
177
+
178
+ rel_commands = {}
179
+
180
+ definitions.each do |command_name, definition|
181
+ rel_commands[command_name] = adapter.command(
182
+ command_name, relations[name], definition
183
+ )
184
+ end
185
+
186
+ h[name] = CommandRegistry.new(rel_commands)
187
+ end
188
+
189
+ Registry.new(commands)
190
+ end
191
+
165
192
  end
166
193
 
167
194
  end
@@ -0,0 +1,48 @@
1
+ require 'rom/mapper_builder'
2
+
3
+ module ROM
4
+
5
+ class CommandDSL
6
+ attr_reader :commands
7
+
8
+ class CommandDefinition
9
+ attr_reader :options
10
+
11
+ def initialize(options, &block)
12
+ @options = options
13
+ instance_exec(&block) if block
14
+ end
15
+
16
+ def to_h
17
+ options
18
+ end
19
+
20
+ def type
21
+ options[:type]
22
+ end
23
+
24
+ def method_missing(name, *args, &block)
25
+ if args.size == 1
26
+ options[name] = args.first
27
+ else
28
+ super
29
+ end
30
+ end
31
+ end
32
+
33
+ def initialize
34
+ @commands = {}
35
+ end
36
+
37
+ def call
38
+ commands
39
+ end
40
+
41
+ def define(name, options = {}, &block)
42
+ commands[name] = CommandDefinition.new(options, &block)
43
+ self
44
+ end
45
+
46
+ end
47
+
48
+ end
data/lib/rom/boot/dsl.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'rom/boot/schema_dsl'
2
2
  require 'rom/boot/mapper_dsl'
3
+ require 'rom/boot/command_dsl'
3
4
 
4
5
  module ROM
5
6
  class Boot
@@ -24,6 +25,12 @@ module ROM
24
25
  dsl.call
25
26
  end
26
27
 
28
+ def commands(&block)
29
+ dsl = CommandDSL.new
30
+ dsl.instance_exec(&block)
31
+ dsl.call
32
+ end
33
+
27
34
  end
28
35
 
29
36
  end
@@ -0,0 +1,68 @@
1
+ module ROM
2
+
3
+ class Result
4
+ attr_reader :value, :error
5
+
6
+ def to_ary
7
+ raise NotImplementedError
8
+ end
9
+ alias_method :to_a, :to_ary
10
+
11
+ class Success < Result
12
+ def initialize(value)
13
+ @value = value
14
+ end
15
+
16
+ def >(f)
17
+ f.call(value)
18
+ end
19
+
20
+ def to_ary
21
+ value
22
+ end
23
+ alias_method :to_a, :to_ary
24
+ end
25
+
26
+ class Failure < Result
27
+ def initialize(error)
28
+ @error = error
29
+ end
30
+
31
+ def >(f)
32
+ self
33
+ end
34
+
35
+ def to_ary
36
+ error
37
+ end
38
+ end
39
+ end
40
+
41
+ class CommandRegistry < Registry
42
+
43
+ class Evaluator
44
+ include Concord.new(:registry)
45
+
46
+ private
47
+
48
+ def method_missing(name, *args, &block)
49
+ command = registry[name]
50
+
51
+ super unless command
52
+
53
+ if args.size > 1
54
+ command.new(*args, &block)
55
+ else
56
+ command.call(*args, &block)
57
+ end
58
+ end
59
+ end
60
+
61
+ def try(&f)
62
+ Result::Success.new(Evaluator.new(self).instance_exec(&f))
63
+ rescue CommandError => e
64
+ Result::Failure.new(e)
65
+ end
66
+ end
67
+
68
+ end
@@ -0,0 +1,64 @@
1
+ module ROM
2
+ module Commands
3
+
4
+ class AbstractCommand
5
+ VALID_RESULTS = [:one, :many].freeze
6
+
7
+ attr_reader :relation, :options, :result
8
+
9
+ # @api private
10
+ def initialize(relation, options)
11
+ @relation = relation
12
+ @options = options
13
+
14
+ @result = options[:result] || :many
15
+
16
+ if !VALID_RESULTS.include?(result)
17
+ raise InvalidOptionError.new(:result, VALID_RESULTS)
18
+ end
19
+ end
20
+
21
+ # Call the command and return one or many tuples
22
+ #
23
+ # @api public
24
+ def call(*args)
25
+ tuples = execute(*args)
26
+
27
+ if result == :one
28
+ tuples.first
29
+ else
30
+ tuples
31
+ end
32
+ end
33
+
34
+ # Target relation on which the command will operate
35
+ #
36
+ # By default this is set to the relation that's passed to the constructor.
37
+ # Specialized commands like Delete may set the target to a different relation.
38
+ #
39
+ # @return [Relation]
40
+ #
41
+ # @api public
42
+ def target
43
+ relation
44
+ end
45
+
46
+ # Assert that tuple count in the target relation corresponds to :result setting
47
+ #
48
+ # @raises TupleCountMismatchError
49
+ #
50
+ # @api private
51
+ def assert_tuple_count
52
+ if result == :one && target.size > 1
53
+ raise TupleCountMismatchError, "#{inspect} expects one tuple"
54
+ end
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+ end
61
+
62
+ require 'rom/commands/create'
63
+ require 'rom/commands/update'
64
+ require 'rom/commands/delete'
@@ -0,0 +1,28 @@
1
+ require 'rom/commands/with_options'
2
+
3
+ module ROM
4
+ module Commands
5
+
6
+ # Create command
7
+ #
8
+ # This command inserts a new tuple into a relation
9
+ #
10
+ # @abstract
11
+ class Create < AbstractCommand
12
+ include WithOptions
13
+
14
+ # Execute the command
15
+ #
16
+ # @abstract
17
+ #
18
+ # @return [Array] an array with inserted tuples
19
+ #
20
+ # @api private
21
+ def execute(tuple)
22
+ raise NotImplementedError, "#{self.class}##{__method__} must be implemented"
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,44 @@
1
+ module ROM
2
+ module Commands
3
+
4
+ # Delete command
5
+ #
6
+ # This command removes tuples from its target relation
7
+ #
8
+ # @abstract
9
+ class Delete < AbstractCommand
10
+ attr_reader :target
11
+
12
+ def initialize(relation, options)
13
+ super
14
+ @target = options[:target] || relation
15
+ end
16
+
17
+ # @see AbstractCommand#call
18
+ def call(*args)
19
+ assert_tuple_count
20
+ super
21
+ end
22
+
23
+ # Execute the command
24
+ #
25
+ # @abstract
26
+ #
27
+ # @return [Array] an array with removed tuples
28
+ #
29
+ # @api private
30
+ def execute
31
+ raise NotImplementedError, "#{self.class}##{__method__} must be implemented"
32
+ end
33
+
34
+ # Return new delete command with new target
35
+ #
36
+ # @api private
37
+ def new(*args, &block)
38
+ self.class.new(relation, options.merge(target: relation.public_send(*args, &block)))
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,42 @@
1
+ require 'rom/commands/with_options'
2
+
3
+ module ROM
4
+ module Commands
5
+
6
+ # Update command
7
+ #
8
+ # This command updates all tuples in its relation with new attributes
9
+ #
10
+ # @abstract
11
+ class Update < AbstractCommand
12
+ include WithOptions
13
+
14
+ alias_method :set, :call
15
+
16
+ # @see AbstractCommand#call
17
+ def call(*args)
18
+ assert_tuple_count
19
+ super
20
+ end
21
+
22
+ # Execute the update command
23
+ #
24
+ # @return [Array] an array with updated tuples
25
+ #
26
+ # @abstract
27
+ #
28
+ # @api private
29
+ def execute(params)
30
+ raise NotImplementedError, "#{self.class}##{__method__} must be implemented"
31
+ end
32
+
33
+ # Return new update command with new relation
34
+ #
35
+ # @api private
36
+ def new(*args, &block)
37
+ self.class.new(relation.public_send(*args, &block), options)
38
+ end
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,21 @@
1
+ module ROM
2
+ module Commands
3
+
4
+ # Common behavior for Create and Update commands
5
+ #
6
+ # TODO: find a better name for this module
7
+ module WithOptions
8
+ attr_reader :validator, :input
9
+
10
+ # @api private
11
+ def initialize(relation, options)
12
+ super
13
+
14
+ @validator = options[:validator] || Proc.new {}
15
+ @input = options[:input] || Hash
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+ end
data/lib/rom/env.rb CHANGED
@@ -5,16 +5,17 @@ module ROM
5
5
  # @api public
6
6
  class Env
7
7
  include Adamantium::Flat
8
- include Equalizer.new(:repositories, :schema, :relations, :mappers)
8
+ include Equalizer.new(:repositories, :schema, :relations, :mappers, :commands)
9
9
 
10
- attr_reader :repositories, :schema, :relations, :mappers
10
+ attr_reader :repositories, :schema, :relations, :mappers, :commands
11
11
 
12
12
  # @api private
13
- def initialize(repositories, schema, relations, mappers)
13
+ def initialize(repositories, schema, relations, mappers, commands)
14
14
  @repositories = repositories
15
15
  @schema = schema
16
16
  @relations = relations
17
17
  @mappers = mappers
18
+ @commands = commands
18
19
  end
19
20
 
20
21
  # Returns a reader with access to defined mappers
@@ -28,6 +29,17 @@ module ROM
28
29
  mappers[name]
29
30
  end
30
31
 
32
+ # Returns commands registry for the given relation
33
+ #
34
+ # @example
35
+ #
36
+ # rom.command(:users).create
37
+ #
38
+ # @api public
39
+ def command(name)
40
+ commands[name]
41
+ end
42
+
31
43
  # @api private
32
44
  def [](name)
33
45
  repositories.fetch(name)
@@ -35,14 +47,18 @@ module ROM
35
47
 
36
48
  # @api private
37
49
  def respond_to_missing?(name, include_private = false)
38
- repositories.key?(name)
50
+ repositories.key?(name) || super
39
51
  end
40
52
 
41
53
  private
42
54
 
43
55
  # @api private
44
- def method_missing(name, *args)
45
- repositories.fetch(name)
56
+ def method_missing(name, *args, &block)
57
+ if repositories.key?(name)
58
+ repositories[name]
59
+ else
60
+ super
61
+ end
46
62
  end
47
63
  end
48
64