rom 0.4.2 → 0.5.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/.rubocop.yml +81 -0
- data/.travis.yml +2 -1
- data/CHANGELOG.md +41 -0
- data/Gemfile +12 -8
- data/Guardfile +17 -11
- data/README.md +7 -7
- data/Rakefile +29 -0
- data/lib/rom.rb +9 -66
- data/lib/rom/adapter.rb +45 -12
- data/lib/rom/adapter/memory.rb +0 -4
- data/lib/rom/adapter/memory/commands.rb +0 -10
- data/lib/rom/adapter/memory/dataset.rb +18 -6
- data/lib/rom/adapter/memory/storage.rb +0 -3
- data/lib/rom/command_registry.rb +24 -43
- data/lib/rom/commands.rb +5 -6
- data/lib/rom/commands/create.rb +5 -5
- data/lib/rom/commands/delete.rb +8 -6
- data/lib/rom/commands/result.rb +82 -0
- data/lib/rom/commands/update.rb +5 -4
- data/lib/rom/commands/with_options.rb +1 -4
- data/lib/rom/config.rb +70 -0
- data/lib/rom/env.rb +11 -3
- data/lib/rom/global.rb +107 -0
- data/lib/rom/header.rb +122 -89
- data/lib/rom/header/attribute.rb +148 -0
- data/lib/rom/mapper.rb +46 -67
- data/lib/rom/mapper_builder.rb +20 -73
- data/lib/rom/mapper_builder/mapper_dsl.rb +114 -0
- data/lib/rom/mapper_builder/model_dsl.rb +29 -0
- data/lib/rom/mapper_registry.rb +21 -0
- data/lib/rom/model_builder.rb +11 -17
- data/lib/rom/processor.rb +28 -0
- data/lib/rom/processor/transproc.rb +105 -0
- data/lib/rom/reader.rb +81 -21
- data/lib/rom/reader_builder.rb +14 -4
- data/lib/rom/relation.rb +19 -5
- data/lib/rom/relation_builder.rb +20 -6
- data/lib/rom/repository.rb +0 -2
- data/lib/rom/setup.rb +156 -0
- data/lib/rom/{boot → setup}/base_relation_dsl.rb +4 -8
- data/lib/rom/setup/command_dsl.rb +46 -0
- data/lib/rom/setup/finalize.rb +125 -0
- data/lib/rom/setup/mapper_dsl.rb +19 -0
- data/lib/rom/{boot → setup}/relation_dsl.rb +1 -4
- data/lib/rom/setup/schema_dsl.rb +33 -0
- data/lib/rom/support/registry.rb +10 -6
- data/lib/rom/version.rb +1 -1
- data/rom.gemspec +3 -1
- data/spec/integration/adapters/extending_relations_spec.rb +0 -2
- data/spec/integration/commands/create_spec.rb +2 -9
- data/spec/integration/commands/delete_spec.rb +4 -5
- data/spec/integration/commands/error_handling_spec.rb +4 -3
- data/spec/integration/commands/update_spec.rb +3 -8
- data/spec/integration/mappers/deep_embedded_spec.rb +52 -0
- data/spec/integration/mappers/definition_dsl_spec.rb +0 -118
- data/spec/integration/mappers/embedded_spec.rb +82 -0
- data/spec/integration/mappers/group_spec.rb +170 -0
- data/spec/integration/mappers/prefixing_attributes_spec.rb +2 -2
- data/spec/integration/mappers/renaming_attributes_spec.rb +8 -6
- data/spec/integration/mappers/symbolizing_attributes_spec.rb +80 -0
- data/spec/integration/mappers/wrap_spec.rb +162 -0
- data/spec/integration/multi_repo_spec.rb +64 -0
- data/spec/integration/relations/reading_spec.rb +12 -8
- data/spec/integration/relations/registry_dsl_spec.rb +1 -3
- data/spec/integration/schema_spec.rb +10 -0
- data/spec/integration/setup_spec.rb +57 -6
- data/spec/spec_helper.rb +2 -1
- data/spec/unit/config_spec.rb +60 -0
- data/spec/unit/rom/adapter/memory/dataset_spec.rb +52 -0
- data/spec/unit/rom/adapter_spec.rb +31 -11
- data/spec/unit/rom/header_spec.rb +60 -16
- data/spec/unit/rom/mapper_builder_spec.rb +311 -0
- data/spec/unit/rom/mapper_registry_spec.rb +25 -0
- data/spec/unit/rom/mapper_spec.rb +4 -5
- data/spec/unit/rom/model_builder_spec.rb +15 -13
- data/spec/unit/rom/processor/transproc_spec.rb +331 -0
- data/spec/unit/rom/reader_spec.rb +73 -0
- data/spec/unit/rom/registry_spec.rb +38 -0
- data/spec/unit/rom/relation_spec.rb +0 -1
- data/spec/unit/rom/setup_spec.rb +55 -0
- data/spec/unit/rom_spec.rb +14 -0
- metadata +62 -22
- data/Gemfile.devtools +0 -71
- data/lib/rom/boot.rb +0 -197
- data/lib/rom/boot/command_dsl.rb +0 -48
- data/lib/rom/boot/dsl.rb +0 -37
- data/lib/rom/boot/mapper_dsl.rb +0 -23
- data/lib/rom/boot/schema_dsl.rb +0 -27
- data/lib/rom/ra.rb +0 -172
- data/lib/rom/ra/operation/group.rb +0 -47
- data/lib/rom/ra/operation/join.rb +0 -39
- data/lib/rom/ra/operation/wrap.rb +0 -45
- data/lib/rom/transformer.rb +0 -77
- data/spec/integration/ra/group_spec.rb +0 -46
- data/spec/integration/ra/join_spec.rb +0 -50
- data/spec/integration/ra/wrap_spec.rb +0 -37
- data/spec/unit/rom/ra/operation/group_spec.rb +0 -55
- data/spec/unit/rom/ra/operation/wrap_spec.rb +0 -29
- data/spec/unit/rom/transformer_spec.rb +0 -41
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rom/model_builder'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
class MapperBuilder
|
5
|
+
module ModelDSL
|
6
|
+
attr_reader :attributes, :builder, :klass
|
7
|
+
|
8
|
+
DEFAULT_TYPE = :poro
|
9
|
+
|
10
|
+
def model(options = nil)
|
11
|
+
if options.is_a?(Class)
|
12
|
+
@klass = options
|
13
|
+
elsif options
|
14
|
+
type = options.fetch(:type) { DEFAULT_TYPE }
|
15
|
+
@builder = ModelBuilder[type].new(options)
|
16
|
+
end
|
17
|
+
|
18
|
+
build_class unless options
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def build_class
|
24
|
+
return klass if klass
|
25
|
+
return builder.call(attributes.map(&:first)) if builder
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ROM
|
2
|
+
# @private
|
3
|
+
class MapperRegistry < Registry
|
4
|
+
# @api private
|
5
|
+
def []=(name, mapper)
|
6
|
+
elements[name] = mapper
|
7
|
+
end
|
8
|
+
|
9
|
+
# @api private
|
10
|
+
def by_path(path)
|
11
|
+
elements[paths(path).detect { |name| elements.key?(name) }]
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
# @api private
|
17
|
+
def paths(path)
|
18
|
+
path.split('.').map(&:to_sym).reverse
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/rom/model_builder.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
module ROM
|
2
|
-
|
3
2
|
# @api private
|
4
3
|
class ModelBuilder
|
5
4
|
attr_reader :options, :const_name, :namespace, :klass
|
@@ -19,14 +18,15 @@ module ROM
|
|
19
18
|
def initialize(options = {})
|
20
19
|
@options = options
|
21
20
|
|
22
|
-
|
23
|
-
|
21
|
+
name = options[:name]
|
22
|
+
if name
|
23
|
+
parts = name.split('::')
|
24
24
|
|
25
|
-
@const_name =
|
25
|
+
@const_name = parts.pop
|
26
26
|
|
27
27
|
@namespace =
|
28
|
-
if
|
29
|
-
Inflecto.constantize(
|
28
|
+
if parts.any?
|
29
|
+
Inflecto.constantize(parts.join('::'))
|
30
30
|
else
|
31
31
|
Object
|
32
32
|
end
|
@@ -37,32 +37,26 @@ module ROM
|
|
37
37
|
namespace.const_set(const_name, klass)
|
38
38
|
end
|
39
39
|
|
40
|
-
def call(
|
41
|
-
define_class(
|
40
|
+
def call(attrs)
|
41
|
+
define_class(attrs)
|
42
42
|
define_const if const_name
|
43
43
|
@klass
|
44
44
|
end
|
45
45
|
|
46
46
|
class PORO < ModelBuilder
|
47
|
-
|
48
|
-
def define_class(header)
|
47
|
+
def define_class(attrs)
|
49
48
|
@klass = Class.new
|
50
49
|
|
51
|
-
|
52
|
-
|
53
|
-
@klass.send(:attr_reader, *attributes)
|
50
|
+
@klass.send(:attr_reader, *attrs)
|
54
51
|
|
55
52
|
@klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
56
53
|
def initialize(params)
|
57
|
-
#{
|
54
|
+
#{attrs.map { |name| "@#{name} = params[:#{name}]" }.join("\n")}
|
58
55
|
end
|
59
56
|
RUBY
|
60
57
|
|
61
58
|
self
|
62
59
|
end
|
63
|
-
|
64
60
|
end
|
65
|
-
|
66
61
|
end
|
67
|
-
|
68
62
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rom/mapper'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
# Abstract processor class
|
5
|
+
#
|
6
|
+
# Every ROM processor should inherit from this class
|
7
|
+
#
|
8
|
+
# @public
|
9
|
+
class Processor
|
10
|
+
# Hook used to auto-register a processor class
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
def self.inherited(processor)
|
14
|
+
Mapper.register_processor(processor)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Required interface to be implemented by descendants
|
18
|
+
#
|
19
|
+
# @return [Processor]
|
20
|
+
#
|
21
|
+
# @abstract
|
22
|
+
#
|
23
|
+
# @api private
|
24
|
+
def self.build
|
25
|
+
raise NotImplementedError, "+build+ must be implemented"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'transproc/all'
|
2
|
+
|
3
|
+
require 'rom/processor'
|
4
|
+
|
5
|
+
module ROM
|
6
|
+
class Processor
|
7
|
+
class Transproc < Processor
|
8
|
+
include ::Transproc::Composer
|
9
|
+
|
10
|
+
attr_reader :header, :model, :mapping, :tuple_proc
|
11
|
+
|
12
|
+
EMPTY_FN = -> tuple { tuple }.freeze
|
13
|
+
|
14
|
+
def self.build(header)
|
15
|
+
new(header).to_transproc
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(header)
|
19
|
+
@header = header
|
20
|
+
@model = header.model
|
21
|
+
@mapping = header.mapping
|
22
|
+
initialize_tuple_proc
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_transproc
|
26
|
+
compose(EMPTY_FN) do |ops|
|
27
|
+
ops << header.groups.map { |attr| visit_group(attr, true) }
|
28
|
+
ops << t(:map_array!, tuple_proc) if tuple_proc
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def visit(attribute)
|
35
|
+
type = attribute.class.name.split('::').last.downcase
|
36
|
+
send("visit_#{type}", attribute)
|
37
|
+
end
|
38
|
+
|
39
|
+
def visit_attribute(attribute)
|
40
|
+
if attribute.typed?
|
41
|
+
t(:map_key!, attribute.name, t(:"to_#{attribute.type}"))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def visit_hash(attribute)
|
46
|
+
with_tuple_proc(attribute) do |tuple_proc|
|
47
|
+
t(:map_key!, attribute.name, tuple_proc)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def visit_array(attribute)
|
52
|
+
with_tuple_proc(attribute) do |tuple_proc|
|
53
|
+
t(:map_key!, attribute.name, t(:map_array!, tuple_proc))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def visit_wrap(attribute)
|
58
|
+
name = attribute.name
|
59
|
+
keys = attribute.tuple_keys
|
60
|
+
|
61
|
+
compose do |ops|
|
62
|
+
ops << t(:nest!, name, keys)
|
63
|
+
ops << visit_hash(attribute)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def visit_group(attribute, preprocess = false)
|
68
|
+
if preprocess
|
69
|
+
name = attribute.name
|
70
|
+
header = attribute.header
|
71
|
+
keys = attribute.tuple_keys
|
72
|
+
|
73
|
+
other = header.groups
|
74
|
+
|
75
|
+
compose do |ops|
|
76
|
+
ops << t(:group, name, keys)
|
77
|
+
|
78
|
+
ops << other.map { |attr|
|
79
|
+
t(:map_array!, t(:map_key!, name, visit_group(attr, true)))
|
80
|
+
}
|
81
|
+
end
|
82
|
+
else
|
83
|
+
visit_array(attribute)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def initialize_tuple_proc
|
88
|
+
@tuple_proc = compose do |ops|
|
89
|
+
ops << t(:map_hash!, mapping) if header.aliased?
|
90
|
+
ops << header.map { |attr| visit(attr) }
|
91
|
+
ops << t(-> tuple { model.new(tuple) }) if model
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def with_tuple_proc(attribute)
|
96
|
+
tuple_proc = new(attribute.header).tuple_proc
|
97
|
+
yield(tuple_proc) if tuple_proc
|
98
|
+
end
|
99
|
+
|
100
|
+
def new(*args)
|
101
|
+
self.class.new(*args)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
data/lib/rom/reader.rb
CHANGED
@@ -1,27 +1,92 @@
|
|
1
1
|
module ROM
|
2
|
-
|
3
2
|
# Exposes mapped tuples via enumerable interface
|
4
3
|
#
|
5
4
|
# See example for each method
|
6
5
|
#
|
7
6
|
# @api public
|
8
7
|
class Reader
|
8
|
+
MapperMissingError = Class.new(StandardError)
|
9
|
+
|
9
10
|
include Enumerable
|
10
11
|
include Equalizer.new(:path, :relation, :mapper)
|
11
12
|
|
12
|
-
|
13
|
+
# @return [String] access path used to read a relation
|
14
|
+
#
|
15
|
+
# @api private
|
16
|
+
attr_reader :path
|
17
|
+
|
18
|
+
# @return [Relation] relation used by the reader
|
19
|
+
#
|
20
|
+
# @api private
|
21
|
+
attr_reader :relation
|
22
|
+
|
23
|
+
# @return [MapperRegistry] registry of mappers used by the reader
|
24
|
+
#
|
25
|
+
# @api private
|
26
|
+
attr_reader :mappers
|
27
|
+
|
28
|
+
# @return [Mapper] mapper to read the relation
|
29
|
+
#
|
30
|
+
# @api private
|
31
|
+
attr_reader :mapper
|
32
|
+
|
33
|
+
# Build a reader subclass for the relation and instantiate it
|
34
|
+
#
|
35
|
+
# This method defines public methods on the class narrowing down data access
|
36
|
+
# only to the methods exposed by a given relation
|
37
|
+
#
|
38
|
+
# @param [Symbol] name of the root relation
|
39
|
+
# @param [Relation] relation that the reader will use
|
40
|
+
# @param [MapperRegistry] registry of mappers
|
41
|
+
# @param [Array<Symbol>] a list of method names exposed by the relation
|
42
|
+
#
|
43
|
+
# @return [Reader]
|
44
|
+
#
|
45
|
+
# @api private
|
46
|
+
def self.build(name, relation, mappers, method_names = [])
|
47
|
+
klass = Class.new(self)
|
48
|
+
|
49
|
+
klass_name = "#{self.name}[#{Inflecto.camelize(relation.name)}]"
|
50
|
+
|
51
|
+
klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
52
|
+
def self.name
|
53
|
+
#{klass_name.inspect}
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.inspect
|
57
|
+
name
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.to_s
|
61
|
+
name
|
62
|
+
end
|
63
|
+
RUBY
|
64
|
+
|
65
|
+
method_names.each do |method_name|
|
66
|
+
klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
67
|
+
def #{method_name}(*args, &block)
|
68
|
+
new_relation = relation.send(#{method_name.inspect}, *args, &block)
|
69
|
+
self.class.new(
|
70
|
+
new_path(#{method_name.to_s.inspect}), new_relation, mappers
|
71
|
+
)
|
72
|
+
end
|
73
|
+
RUBY
|
74
|
+
end
|
75
|
+
|
76
|
+
klass.new(name, relation, mappers)
|
77
|
+
end
|
13
78
|
|
14
79
|
# @api private
|
15
80
|
def initialize(path, relation, mappers = {})
|
16
81
|
@path = path.to_s
|
17
82
|
@relation = relation
|
18
83
|
@mappers = mappers
|
84
|
+
@mapper = mappers.by_path(@path) || raise(MapperMissingError, path)
|
85
|
+
end
|
19
86
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
@mapper = mappers.fetch(mapper_key.to_sym)
|
24
|
-
@header = mapper.header
|
87
|
+
# @api private
|
88
|
+
def header
|
89
|
+
mapper.header
|
25
90
|
end
|
26
91
|
|
27
92
|
# Yields tuples mapped to objects
|
@@ -39,24 +104,19 @@ module ROM
|
|
39
104
|
mapper.process(relation) { |tuple| yield(tuple) }
|
40
105
|
end
|
41
106
|
|
42
|
-
# @api private
|
43
|
-
def respond_to_missing?(name, include_private = false)
|
44
|
-
relation.respond_to?(name)
|
45
|
-
end
|
46
|
-
|
47
107
|
private
|
48
108
|
|
49
109
|
# @api private
|
50
|
-
def method_missing(name
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
new_path = splits.join('.')
|
56
|
-
|
57
|
-
self.class.new(new_path, new_relation, mappers)
|
110
|
+
def method_missing(name)
|
111
|
+
raise(
|
112
|
+
NoRelationError,
|
113
|
+
"undefined relation #{name.inspect} within #{path.inspect}"
|
114
|
+
)
|
58
115
|
end
|
59
116
|
|
117
|
+
# @api private
|
118
|
+
def new_path(name)
|
119
|
+
path.dup << ".#{name}"
|
120
|
+
end
|
60
121
|
end
|
61
|
-
|
62
122
|
end
|
data/lib/rom/reader_builder.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
|
1
|
+
require 'rom/mapper_registry'
|
2
2
|
|
3
|
+
module ROM
|
3
4
|
# @api private
|
4
5
|
class ReaderBuilder
|
5
6
|
DEFAULT_OPTIONS = { inherit_header: true }.freeze
|
@@ -21,10 +22,20 @@ module ROM
|
|
21
22
|
builder.instance_exec(&block) if block
|
22
23
|
mapper = builder.call
|
23
24
|
|
24
|
-
mappers =
|
25
|
+
mappers =
|
26
|
+
if options[:parent]
|
27
|
+
readers.fetch(parent.name).mappers
|
28
|
+
else
|
29
|
+
MapperRegistry.new
|
30
|
+
end
|
25
31
|
|
26
32
|
mappers[name] = mapper
|
27
|
-
|
33
|
+
|
34
|
+
unless options[:parent]
|
35
|
+
readers[name] = Reader.build(
|
36
|
+
name, parent, mappers, parent.class.relation_methods
|
37
|
+
)
|
38
|
+
end
|
28
39
|
end
|
29
40
|
end
|
30
41
|
|
@@ -33,6 +44,5 @@ module ROM
|
|
33
44
|
def with_options(options)
|
34
45
|
yield(DEFAULT_OPTIONS.merge(options))
|
35
46
|
end
|
36
|
-
|
37
47
|
end
|
38
48
|
end
|
data/lib/rom/relation.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
module ROM
|
2
|
-
|
3
2
|
# Base relation class
|
4
3
|
#
|
5
4
|
# Relation is a proxy for the dataset object provided by the adapter, it
|
6
5
|
# forwards every method to the dataset that's why "native" interface of the
|
7
6
|
# underlying adapter is available in the relation. This interface, however, is
|
8
|
-
# considered
|
7
|
+
# considered private and should not be used outside of the relation instance.
|
9
8
|
#
|
10
9
|
# ROM builds sub-classes of this class for every relation defined in the env
|
11
10
|
# for easy inspection and extensibility - every adapter can provide extensions
|
@@ -21,11 +20,24 @@ module ROM
|
|
21
20
|
include Charlatan.new(:dataset)
|
22
21
|
include Equalizer.new(:header, :dataset)
|
23
22
|
|
23
|
+
class << self
|
24
|
+
# Relation methods that were defined inside setup.relation DSL
|
25
|
+
#
|
26
|
+
# @return [Array<Symbol>]
|
27
|
+
#
|
28
|
+
# @api private
|
29
|
+
attr_accessor :relation_methods
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Array] relation base header
|
33
|
+
#
|
24
34
|
# @api private
|
25
35
|
attr_reader :header
|
26
36
|
|
37
|
+
# Hook to finalize a relation after its instance was created
|
38
|
+
#
|
27
39
|
# @api private
|
28
|
-
def self.finalize(
|
40
|
+
def self.finalize(_env, _relation)
|
29
41
|
# noop
|
30
42
|
end
|
31
43
|
|
@@ -35,12 +47,14 @@ module ROM
|
|
35
47
|
@header = header.dup.freeze
|
36
48
|
end
|
37
49
|
|
50
|
+
# Yield dataset tuples
|
51
|
+
#
|
52
|
+
# @yield [Hash]
|
53
|
+
#
|
38
54
|
# @api private
|
39
55
|
def each(&block)
|
40
56
|
return to_enum unless block
|
41
57
|
dataset.each(&block)
|
42
58
|
end
|
43
|
-
|
44
59
|
end
|
45
|
-
|
46
60
|
end
|