rom 0.4.2 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,148 @@
|
|
1
|
+
module ROM
|
2
|
+
class Header
|
3
|
+
# An attribute provides information about a specific attribute in a tuple
|
4
|
+
#
|
5
|
+
# This may include information about how an attribute should be renamed,
|
6
|
+
# or how its value should coerced.
|
7
|
+
#
|
8
|
+
# More complex attributes describe how an attribute should be transformed.
|
9
|
+
#
|
10
|
+
# @private
|
11
|
+
class Attribute
|
12
|
+
include Equalizer.new(:name, :key, :type)
|
13
|
+
|
14
|
+
# @return [Symbol] name of an attribute
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
attr_reader :name
|
18
|
+
|
19
|
+
# @return [Symbol] key of an attribute that corresponds to tuple attribute
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
attr_reader :key
|
23
|
+
|
24
|
+
# @return [Symbol] type identifier (defaults to :object)
|
25
|
+
#
|
26
|
+
# @api private
|
27
|
+
attr_reader :type
|
28
|
+
|
29
|
+
# @return [Hash] additional meta information
|
30
|
+
#
|
31
|
+
# @api private
|
32
|
+
attr_reader :meta
|
33
|
+
|
34
|
+
# Return attribute class for a give meta hash
|
35
|
+
#
|
36
|
+
# @param [Hash] hash with type information and optional transformation info
|
37
|
+
#
|
38
|
+
# @return [Class]
|
39
|
+
#
|
40
|
+
# @api private
|
41
|
+
def self.[](meta)
|
42
|
+
type = meta[:type]
|
43
|
+
|
44
|
+
if type.equal?(:hash)
|
45
|
+
meta[:wrap] ? Wrap : Hash
|
46
|
+
elsif type.equal?(:array)
|
47
|
+
meta[:group] ? Group : Array
|
48
|
+
else
|
49
|
+
self
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Coerce an array with attribute meta-data into an attribute object
|
54
|
+
#
|
55
|
+
# @param [Array<Symbol,Hash>] name/options pair
|
56
|
+
#
|
57
|
+
# @return [Attribute]
|
58
|
+
#
|
59
|
+
# @api private
|
60
|
+
def self.coerce(input)
|
61
|
+
name = input[0]
|
62
|
+
meta = (input[1] || {}).dup
|
63
|
+
|
64
|
+
meta[:type] ||= :object
|
65
|
+
|
66
|
+
if meta.key?(:header)
|
67
|
+
meta[:header] = Header.coerce(meta[:header], meta[:model])
|
68
|
+
end
|
69
|
+
|
70
|
+
self[meta].new(name, meta)
|
71
|
+
end
|
72
|
+
|
73
|
+
# @api private
|
74
|
+
def initialize(name, meta)
|
75
|
+
@name = name
|
76
|
+
@meta = meta
|
77
|
+
@key = meta.fetch(:from) { name }
|
78
|
+
@type = meta.fetch(:type)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Return if an attribute has a specific type identifier
|
82
|
+
#
|
83
|
+
# @api private
|
84
|
+
def typed?
|
85
|
+
type != :object
|
86
|
+
end
|
87
|
+
|
88
|
+
# Return if an attribute should be aliased
|
89
|
+
#
|
90
|
+
# @api private
|
91
|
+
def aliased?
|
92
|
+
key != name
|
93
|
+
end
|
94
|
+
|
95
|
+
# Return :key-to-:name mapping hash
|
96
|
+
#
|
97
|
+
# @return [Hash]
|
98
|
+
#
|
99
|
+
# @api private
|
100
|
+
def mapping
|
101
|
+
{ key => name }
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Embedded attribute is a special attribute type that has a header
|
106
|
+
#
|
107
|
+
# This is the base of complex attributes like Hash or Group
|
108
|
+
#
|
109
|
+
# @private
|
110
|
+
class Embedded < Attribute
|
111
|
+
include Equalizer.new(:name, :key, :type, :header)
|
112
|
+
|
113
|
+
# return [Header] header of an attribute
|
114
|
+
#
|
115
|
+
# @api private
|
116
|
+
attr_reader :header
|
117
|
+
|
118
|
+
# @api private
|
119
|
+
def initialize(*)
|
120
|
+
super
|
121
|
+
@header = meta.fetch(:header)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Return tuple keys from the header
|
125
|
+
#
|
126
|
+
# @return [Array<Symbol>]
|
127
|
+
#
|
128
|
+
# @api private
|
129
|
+
def tuple_keys
|
130
|
+
header.tuple_keys
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Array is an embedded attribute type
|
135
|
+
Array = Class.new(Embedded)
|
136
|
+
|
137
|
+
# Hash is an embedded attribute type
|
138
|
+
Hash = Class.new(Embedded)
|
139
|
+
|
140
|
+
# Wrap is a special type of Hash attribute that requires wrapping
|
141
|
+
# transformation
|
142
|
+
Wrap = Class.new(Hash)
|
143
|
+
|
144
|
+
# Group is a special type of Array attribute that requires grouping
|
145
|
+
# transformation
|
146
|
+
Group = Class.new(Array)
|
147
|
+
end
|
148
|
+
end
|
data/lib/rom/mapper.rb
CHANGED
@@ -1,83 +1,62 @@
|
|
1
1
|
module ROM
|
2
|
-
|
3
|
-
#
|
2
|
+
# Mapper is a simple object that uses a transformer to load relations
|
3
|
+
#
|
4
|
+
# @private
|
4
5
|
class Mapper
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
tuple.map { |key, value| [header.mapping[key], value] }
|
21
|
-
end
|
6
|
+
# @return [Object] transformer object built by a processor
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
attr_reader :transformer
|
10
|
+
|
11
|
+
# @return [Header] header that was used to build the transformer
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
attr_reader :header
|
15
|
+
|
16
|
+
# @return [Hash] registered processors
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
def self.processors
|
20
|
+
@_processors ||= {}
|
22
21
|
end
|
23
22
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
def process(relation)
|
33
|
-
transformer.call(relation.to_a).each { |tuple| yield(load(tuple)) }
|
34
|
-
end
|
35
|
-
|
36
|
-
def call(tuple, header = self.header)
|
37
|
-
mapping = header.mapping
|
38
|
-
|
39
|
-
tuple.map do |key, value|
|
40
|
-
case value
|
41
|
-
when Hash
|
42
|
-
[key, loader[Hash[call(value, header[key])], header[key].model]]
|
43
|
-
when Array
|
44
|
-
[key, value.map { |v| loader[Hash[call(v, header[key])], header[key].model] }]
|
45
|
-
else
|
46
|
-
[mapping[key], value]
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
23
|
+
# Register a processor class
|
24
|
+
#
|
25
|
+
# @return [Hash]
|
26
|
+
#
|
27
|
+
# @api private
|
28
|
+
def self.register_processor(processor)
|
29
|
+
name = processor.name.split('::').last.downcase.to_sym
|
30
|
+
processors.update(name => processor)
|
50
31
|
end
|
51
32
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
self
|
60
|
-
end
|
61
|
-
|
62
|
-
loader = Proc.new { |tuple, m| m ? m.new(tuple) : tuple }
|
63
|
-
|
64
|
-
klass.new(header, model, loader)
|
33
|
+
# Build a mapper using provided processor type
|
34
|
+
#
|
35
|
+
# @return [Mapper]
|
36
|
+
#
|
37
|
+
# @api private
|
38
|
+
def self.build(header, processor = :transproc)
|
39
|
+
new(processors.fetch(processor).build(header), header)
|
65
40
|
end
|
66
41
|
|
67
|
-
|
42
|
+
# @api private
|
43
|
+
def initialize(transformer, header)
|
44
|
+
@transformer = transformer
|
68
45
|
@header = header
|
69
|
-
@model = model
|
70
|
-
@loader = loader
|
71
46
|
end
|
72
47
|
|
73
|
-
|
74
|
-
|
48
|
+
# @return [Class] optional model that is instantiated by a mapper
|
49
|
+
#
|
50
|
+
# @api private
|
51
|
+
def model
|
52
|
+
header.model
|
75
53
|
end
|
76
54
|
|
77
|
-
|
78
|
-
|
55
|
+
# Process a relation using the transfomer
|
56
|
+
#
|
57
|
+
# @api private
|
58
|
+
def process(relation, &block)
|
59
|
+
transformer[relation.to_a].each(&block)
|
79
60
|
end
|
80
|
-
|
81
61
|
end
|
82
|
-
|
83
62
|
end
|
data/lib/rom/mapper_builder.rb
CHANGED
@@ -1,105 +1,52 @@
|
|
1
|
-
require 'rom/
|
1
|
+
require 'rom/mapper_builder/model_dsl'
|
2
|
+
require 'rom/mapper_builder/mapper_dsl'
|
2
3
|
|
3
4
|
module ROM
|
4
|
-
|
5
5
|
# @api private
|
6
6
|
class MapperBuilder
|
7
|
+
attr_reader :name, :root, :options, :prefix, :symbolize_keys, :dsl
|
7
8
|
|
8
|
-
|
9
|
-
attr_reader :attributes, :model_class, :model_builder
|
10
|
-
|
11
|
-
def initialize
|
12
|
-
@attributes = []
|
13
|
-
end
|
14
|
-
|
15
|
-
def header
|
16
|
-
Header.coerce(attributes)
|
17
|
-
end
|
18
|
-
|
19
|
-
def model(options = nil)
|
20
|
-
if options.is_a?(Class)
|
21
|
-
@model_class = options
|
22
|
-
elsif options
|
23
|
-
@model_builder = ModelBuilder[options.fetch(:type) { :poro }].new(options)
|
24
|
-
end
|
25
|
-
|
26
|
-
if options
|
27
|
-
self
|
28
|
-
else
|
29
|
-
model_class || (model_builder && model_builder.call(header))
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def attribute(name, options = {})
|
34
|
-
attributes << [name, options]
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
attr_reader :name, :root, :prefix,
|
39
|
-
:model_builder, :model_class, :attributes
|
9
|
+
DEFAULT_PROCESSOR = :transproc
|
40
10
|
|
41
11
|
def initialize(name, root, options = {})
|
42
12
|
@name = name
|
13
|
+
@options = options
|
43
14
|
@root = root
|
44
15
|
@prefix = options[:prefix]
|
16
|
+
@symbolize_keys = options[:symbolize_keys]
|
45
17
|
|
46
|
-
|
18
|
+
attributes =
|
47
19
|
if options[:inherit_header]
|
48
20
|
root.header.map { |attr| [prefix ? :"#{prefix}_#{attr}" : attr] }
|
49
21
|
else
|
50
22
|
[]
|
51
23
|
end
|
52
|
-
end
|
53
24
|
|
54
|
-
|
55
|
-
if options.is_a?(Class)
|
56
|
-
@model_class = options
|
57
|
-
else
|
58
|
-
@model_builder = ModelBuilder[options.fetch(:type) { :poro }].new(options)
|
59
|
-
end
|
60
|
-
|
61
|
-
self
|
62
|
-
end
|
63
|
-
|
64
|
-
def attribute(name, options = {})
|
65
|
-
options[:from] = :"#{prefix}_#{name}" if prefix
|
66
|
-
attributes << [name, options]
|
67
|
-
end
|
68
|
-
|
69
|
-
def exclude(name)
|
70
|
-
attributes.delete([name])
|
71
|
-
end
|
25
|
+
@dsl = MapperDSL.new(attributes, options)
|
72
26
|
|
73
|
-
|
74
|
-
attribute_dsl(options, Array, &block)
|
27
|
+
@processor = DEFAULT_PROCESSOR
|
75
28
|
end
|
76
29
|
|
77
|
-
def
|
78
|
-
|
30
|
+
def processor(identifier = nil)
|
31
|
+
if identifier
|
32
|
+
@processor = identifier
|
33
|
+
else
|
34
|
+
@processor
|
35
|
+
end
|
79
36
|
end
|
80
37
|
|
81
38
|
def call
|
82
|
-
header
|
83
|
-
|
84
|
-
@model_class = model_builder.call(header) if model_builder
|
85
|
-
|
86
|
-
Mapper.build(header, model_class)
|
39
|
+
Mapper.build(dsl.header, processor)
|
87
40
|
end
|
88
41
|
|
89
42
|
private
|
90
43
|
|
91
|
-
def
|
92
|
-
if
|
93
|
-
dsl
|
94
|
-
dsl.instance_exec(&block)
|
95
|
-
attributes << [options, header: dsl.header, type: type, model: dsl.model]
|
44
|
+
def method_missing(name, *args, &block)
|
45
|
+
if dsl.respond_to?(name)
|
46
|
+
dsl.public_send(name, *args, &block)
|
96
47
|
else
|
97
|
-
|
98
|
-
attributes << [name, header: header.zip, type: type]
|
99
|
-
end
|
48
|
+
super
|
100
49
|
end
|
101
50
|
end
|
102
|
-
|
103
51
|
end
|
104
|
-
|
105
52
|
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'rom/mapper_builder/model_dsl'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
class MapperBuilder
|
5
|
+
# @api private
|
6
|
+
class MapperDSL
|
7
|
+
include ModelDSL
|
8
|
+
|
9
|
+
attr_reader :attributes, :options, :symbolize_keys, :prefix
|
10
|
+
|
11
|
+
def initialize(attributes, options)
|
12
|
+
@attributes = attributes
|
13
|
+
@options = options
|
14
|
+
@symbolize_keys = options[:symbolize_keys]
|
15
|
+
@prefix = options[:prefix]
|
16
|
+
end
|
17
|
+
|
18
|
+
def attribute(name, options = EMPTY_HASH)
|
19
|
+
with_attr_options(name, options) do |attr_options|
|
20
|
+
add_attribute(name, attr_options)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def exclude(*names)
|
25
|
+
names.each { |name| attributes.delete([name]) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def embedded(name, options, &block)
|
29
|
+
with_attr_options(name) do |attr_options|
|
30
|
+
dsl = new(options, &block)
|
31
|
+
|
32
|
+
attr_options.update(options)
|
33
|
+
|
34
|
+
add_attribute(
|
35
|
+
name, { header: dsl.header, type: :array }.update(attr_options)
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def wrap(*args, &block)
|
41
|
+
with_name_or_options(*args) do |name, options|
|
42
|
+
dsl(name, { type: :hash, wrap: true }.update(options), &block)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def group(*args, &block)
|
47
|
+
with_name_or_options(*args) do |name, options|
|
48
|
+
dsl(name, { type: :array, group: true }.update(options), &block)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def header
|
53
|
+
Header.coerce(attributes, model)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def with_attr_options(name, options = EMPTY_HASH)
|
59
|
+
attr_options = options.dup
|
60
|
+
|
61
|
+
attr_options[:from] ||= :"#{prefix}_#{name}" if prefix
|
62
|
+
|
63
|
+
if symbolize_keys
|
64
|
+
attr_options.update(from: attr_options.fetch(:from) { name }.to_s)
|
65
|
+
end
|
66
|
+
|
67
|
+
yield(attr_options)
|
68
|
+
end
|
69
|
+
|
70
|
+
def with_name_or_options(*args)
|
71
|
+
name, options =
|
72
|
+
if args.size > 1
|
73
|
+
args
|
74
|
+
else
|
75
|
+
[args.first, {}]
|
76
|
+
end
|
77
|
+
|
78
|
+
yield(name, options)
|
79
|
+
end
|
80
|
+
|
81
|
+
def dsl(name_or_attrs, options, &block)
|
82
|
+
if block
|
83
|
+
attributes_from_block(name_or_attrs, options, &block)
|
84
|
+
else
|
85
|
+
attributes_from_hash(name_or_attrs, options)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def attributes_from_block(name, options, &block)
|
90
|
+
dsl = new(options, &block)
|
91
|
+
add_attribute(name, options.update(header: dsl.header))
|
92
|
+
end
|
93
|
+
|
94
|
+
def attributes_from_hash(hash, options)
|
95
|
+
hash.each do |name, header|
|
96
|
+
with_attr_options(name, options) do |attr_options|
|
97
|
+
add_attribute(name, attr_options.update(header: header.zip))
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def add_attribute(name, options)
|
103
|
+
exclude(name, name.to_s)
|
104
|
+
attributes << [name, options]
|
105
|
+
end
|
106
|
+
|
107
|
+
def new(options, &block)
|
108
|
+
dsl = self.class.new([], @options.merge(options))
|
109
|
+
dsl.instance_exec(&block)
|
110
|
+
dsl
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|