rom 0.3.1 → 0.4.0

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