rom 0.5.0 → 0.6.0.beta1
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 +19 -15
- data/.rubocop_todo.yml +28 -0
- data/.travis.yml +8 -1
- data/CHANGELOG.md +40 -0
- data/Gemfile +10 -2
- data/Guardfile +12 -10
- data/README.md +42 -43
- data/Rakefile +13 -23
- data/lib/rom.rb +19 -27
- data/lib/rom/command.rb +118 -0
- data/lib/rom/command_registry.rb +13 -27
- data/lib/rom/commands.rb +1 -59
- data/lib/rom/commands/abstract.rb +147 -0
- data/lib/rom/commands/composite.rb +47 -0
- data/lib/rom/commands/create.rb +2 -17
- data/lib/rom/commands/delete.rb +5 -25
- data/lib/rom/commands/result.rb +5 -5
- data/lib/rom/commands/update.rb +3 -27
- data/lib/rom/constants.rb +19 -0
- data/lib/rom/env.rb +85 -35
- data/lib/rom/global.rb +173 -42
- data/lib/rom/header.rb +5 -5
- data/lib/rom/header/attribute.rb +2 -2
- data/lib/rom/lint/enumerable_dataset.rb +52 -0
- data/lib/rom/lint/linter.rb +64 -0
- data/lib/rom/lint/repository.rb +78 -0
- data/lib/rom/lint/spec.rb +20 -0
- data/lib/rom/lint/test.rb +98 -0
- data/lib/rom/mapper.rb +32 -5
- data/lib/rom/mapper/attribute_dsl.rb +240 -0
- data/lib/rom/mapper/dsl.rb +100 -0
- data/lib/rom/mapper/model_dsl.rb +55 -0
- data/lib/rom/mapper_registry.rb +8 -1
- data/lib/rom/memory.rb +4 -0
- data/lib/rom/memory/commands.rb +46 -0
- data/lib/rom/memory/dataset.rb +72 -0
- data/lib/rom/memory/relation.rb +44 -0
- data/lib/rom/memory/repository.rb +62 -0
- data/lib/rom/memory/storage.rb +57 -0
- data/lib/rom/model_builder.rb +44 -5
- data/lib/rom/processor.rb +1 -1
- data/lib/rom/processor/transproc.rb +109 -16
- data/lib/rom/reader.rb +91 -39
- data/lib/rom/relation.rb +165 -26
- data/lib/rom/relation/composite.rb +132 -0
- data/lib/rom/relation/curried.rb +48 -0
- data/lib/rom/relation/lazy.rb +173 -0
- data/lib/rom/relation/loaded.rb +75 -0
- data/lib/rom/relation/registry_reader.rb +23 -0
- data/lib/rom/repository.rb +93 -34
- data/lib/rom/setup.rb +54 -98
- data/lib/rom/setup/finalize.rb +85 -76
- data/lib/rom/setup_dsl/command.rb +36 -0
- data/lib/rom/setup_dsl/command_dsl.rb +34 -0
- data/lib/rom/setup_dsl/mapper.rb +32 -0
- data/lib/rom/setup_dsl/mapper_dsl.rb +30 -0
- data/lib/rom/setup_dsl/relation.rb +21 -0
- data/lib/rom/setup_dsl/setup.rb +75 -0
- data/lib/rom/support/array_dataset.rb +38 -0
- data/lib/rom/support/class_builder.rb +44 -0
- data/lib/rom/support/class_macros.rb +56 -0
- data/lib/rom/support/data_proxy.rb +102 -0
- data/lib/rom/support/enumerable_dataset.rb +58 -0
- data/lib/rom/support/inflector.rb +73 -0
- data/lib/rom/support/options.rb +188 -0
- data/lib/rom/support/registry.rb +4 -8
- data/lib/rom/version.rb +1 -1
- data/rakelib/benchmark.rake +13 -0
- data/rakelib/mutant.rake +16 -0
- data/rakelib/rubocop.rake +18 -0
- data/rom.gemspec +4 -7
- data/spec/integration/commands/create_spec.rb +32 -24
- data/spec/integration/commands/delete_spec.rb +15 -7
- data/spec/integration/commands/update_spec.rb +13 -11
- data/spec/integration/mappers/deep_embedded_spec.rb +4 -11
- data/spec/integration/mappers/definition_dsl_spec.rb +31 -44
- data/spec/integration/mappers/embedded_spec.rb +9 -24
- data/spec/integration/mappers/group_spec.rb +22 -30
- data/spec/integration/mappers/prefixing_attributes_spec.rb +18 -23
- data/spec/integration/mappers/renaming_attributes_spec.rb +23 -38
- data/spec/integration/mappers/symbolizing_attributes_spec.rb +18 -24
- data/spec/integration/mappers/wrap_spec.rb +22 -30
- data/spec/integration/multi_repo_spec.rb +15 -37
- data/spec/integration/relations/reading_spec.rb +82 -14
- data/spec/integration/repositories/extending_relations_spec.rb +50 -0
- data/spec/integration/{adapters → repositories}/setting_logger_spec.rb +6 -5
- data/spec/integration/setup_spec.rb +59 -62
- data/spec/shared/enumerable_dataset.rb +49 -0
- data/spec/shared/one_behavior.rb +26 -0
- data/spec/shared/users_and_tasks.rb +11 -23
- data/spec/spec_helper.rb +16 -7
- data/spec/support/constant_leak_finder.rb +14 -0
- data/spec/test/memory_repository_lint_test.rb +27 -0
- data/spec/unit/rom/command_registry_spec.rb +44 -0
- data/spec/unit/rom/commands/result_spec.rb +14 -0
- data/spec/unit/rom/commands_spec.rb +174 -0
- data/spec/unit/rom/env_spec.rb +40 -7
- data/spec/unit/rom/global_spec.rb +14 -0
- data/spec/unit/rom/{mapper_builder_spec.rb → mapper/dsl_spec.rb} +52 -38
- data/spec/unit/rom/mapper_spec.rb +51 -10
- data/spec/unit/rom/{adapter/memory → memory}/dataset_spec.rb +6 -4
- data/spec/unit/rom/memory/repository_spec.rb +12 -0
- data/spec/unit/rom/memory/storage_spec.rb +45 -0
- data/spec/unit/rom/model_builder_spec.rb +4 -3
- data/spec/unit/rom/processor/transproc_spec.rb +1 -0
- data/spec/unit/rom/reader_spec.rb +97 -24
- data/spec/unit/rom/relation/composite_spec.rb +65 -0
- data/spec/unit/rom/relation/lazy_spec.rb +145 -0
- data/spec/unit/rom/relation/loaded_spec.rb +28 -0
- data/spec/unit/rom/relation_spec.rb +111 -6
- data/spec/unit/rom/repository_spec.rb +59 -9
- data/spec/unit/rom/setup_spec.rb +99 -11
- data/spec/unit/rom/support/array_dataset_spec.rb +59 -0
- data/spec/unit/rom/support/class_builder_spec.rb +42 -0
- data/spec/unit/rom/support/enumerable_dataset_spec.rb +17 -0
- data/spec/unit/rom/support/inflector_spec.rb +89 -0
- data/spec/unit/rom/support/options_spec.rb +119 -0
- metadata +74 -112
- data/lib/rom/adapter.rb +0 -191
- data/lib/rom/adapter/memory.rb +0 -32
- data/lib/rom/adapter/memory/commands.rb +0 -31
- data/lib/rom/adapter/memory/dataset.rb +0 -67
- data/lib/rom/adapter/memory/storage.rb +0 -26
- data/lib/rom/commands/with_options.rb +0 -18
- data/lib/rom/config.rb +0 -70
- data/lib/rom/mapper_builder.rb +0 -52
- data/lib/rom/mapper_builder/mapper_dsl.rb +0 -114
- data/lib/rom/mapper_builder/model_dsl.rb +0 -29
- data/lib/rom/reader_builder.rb +0 -48
- data/lib/rom/relation_builder.rb +0 -62
- data/lib/rom/setup/base_relation_dsl.rb +0 -46
- data/lib/rom/setup/command_dsl.rb +0 -46
- data/lib/rom/setup/mapper_dsl.rb +0 -19
- data/lib/rom/setup/relation_dsl.rb +0 -20
- data/lib/rom/setup/schema_dsl.rb +0 -33
- data/spec/integration/adapters/extending_relations_spec.rb +0 -41
- data/spec/integration/commands/try_spec.rb +0 -27
- data/spec/integration/schema_spec.rb +0 -77
- data/spec/unit/config_spec.rb +0 -60
- data/spec/unit/rom/adapter_spec.rb +0 -79
- data/spec/unit/rom_spec.rb +0 -14
data/lib/rom/header.rb
CHANGED
@@ -31,9 +31,9 @@ module ROM
|
|
31
31
|
|
32
32
|
# Coerce array with attribute definitions into a header object
|
33
33
|
#
|
34
|
-
# @param [Array<Array>] attribute name/option pairs
|
34
|
+
# @param [Array<Array>] input attribute name/option pairs
|
35
35
|
#
|
36
|
-
# @param [Class] optional
|
36
|
+
# @param [Class] model optional
|
37
37
|
#
|
38
38
|
# @return [Header]
|
39
39
|
#
|
@@ -63,8 +63,8 @@ module ROM
|
|
63
63
|
# @yield [Attribute]
|
64
64
|
#
|
65
65
|
# @api private
|
66
|
-
def each
|
67
|
-
attributes.
|
66
|
+
def each
|
67
|
+
attributes.each_value { |attribute| yield(attribute) }
|
68
68
|
end
|
69
69
|
|
70
70
|
# Return if there are any aliased attributes
|
@@ -150,7 +150,7 @@ module ROM
|
|
150
150
|
#
|
151
151
|
# @api private
|
152
152
|
def initialize_tuple_keys
|
153
|
-
@tuple_keys = mapping.keys + non_primitives.
|
153
|
+
@tuple_keys = mapping.keys + non_primitives.flat_map(&:tuple_keys)
|
154
154
|
end
|
155
155
|
end
|
156
156
|
end
|
data/lib/rom/header/attribute.rb
CHANGED
@@ -33,7 +33,7 @@ module ROM
|
|
33
33
|
|
34
34
|
# Return attribute class for a give meta hash
|
35
35
|
#
|
36
|
-
# @param [Hash] hash with type information and optional transformation info
|
36
|
+
# @param [Hash] meta hash with type information and optional transformation info
|
37
37
|
#
|
38
38
|
# @return [Class]
|
39
39
|
#
|
@@ -52,7 +52,7 @@ module ROM
|
|
52
52
|
|
53
53
|
# Coerce an array with attribute meta-data into an attribute object
|
54
54
|
#
|
55
|
-
# @param [Array<Symbol,Hash>] name/options pair
|
55
|
+
# @param [Array<Symbol,Hash>] input attribute name/options pair
|
56
56
|
#
|
57
57
|
# @return [Attribute]
|
58
58
|
#
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rom/lint/linter'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
module Lint
|
5
|
+
# Ensures that a [ROM::EnumerableDataset] extension correctly yields
|
6
|
+
# arrays and tuples
|
7
|
+
#
|
8
|
+
# @api public
|
9
|
+
class EnumerableDataset < ROM::Lint::Linter
|
10
|
+
# The linted subject
|
11
|
+
#
|
12
|
+
# @api public
|
13
|
+
attr_reader :dataset
|
14
|
+
|
15
|
+
# The expected data
|
16
|
+
#
|
17
|
+
# @api public
|
18
|
+
attr_reader :data
|
19
|
+
|
20
|
+
# Create a linter for EnumerableDataset
|
21
|
+
#
|
22
|
+
# @param [EnumerableDataset] dataset the linted subject
|
23
|
+
# @param [Object] data the expected data
|
24
|
+
#
|
25
|
+
# @api public
|
26
|
+
def initialize(dataset, data)
|
27
|
+
@dataset = dataset
|
28
|
+
@data = data
|
29
|
+
end
|
30
|
+
|
31
|
+
# Lint: Ensure that +dataset+ yield tuples via +each+
|
32
|
+
#
|
33
|
+
# @api public
|
34
|
+
def lint_each
|
35
|
+
result = []
|
36
|
+
dataset.each { |tuple| result << tuple }
|
37
|
+
return if result == data
|
38
|
+
|
39
|
+
complain "#{dataset.class}#each must yield tuples"
|
40
|
+
end
|
41
|
+
|
42
|
+
# Lint: Ensure that +dataset+'s array equals to expected +data+
|
43
|
+
#
|
44
|
+
# @api public
|
45
|
+
def lint_to_a
|
46
|
+
return if dataset.to_a == data
|
47
|
+
|
48
|
+
complain "#{dataset.class}#to_a must cast dataset to an array"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module ROM
|
2
|
+
module Lint
|
3
|
+
# Base class for building linters that check source code
|
4
|
+
#
|
5
|
+
# Linters are used by authors of ROM adapters to verify that their
|
6
|
+
# integration complies with the ROM api.
|
7
|
+
#
|
8
|
+
# Most of the time, authors won't need to construct linters directly
|
9
|
+
# because the provided test helpers will automatically run when required
|
10
|
+
# in tests and specs.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# require 'rom/lint/spec'
|
14
|
+
#
|
15
|
+
#
|
16
|
+
# @api public
|
17
|
+
class Linter
|
18
|
+
# A failure raised by +complain+
|
19
|
+
Failure = Class.new(StandardError)
|
20
|
+
|
21
|
+
# Iterate over all lint methods
|
22
|
+
#
|
23
|
+
# @yield [String, ROM::Lint]
|
24
|
+
#
|
25
|
+
# @api public
|
26
|
+
def self.each_lint
|
27
|
+
return to_enum unless block_given?
|
28
|
+
lints.each { |lint| yield lint, self }
|
29
|
+
end
|
30
|
+
|
31
|
+
# Run a lint method
|
32
|
+
#
|
33
|
+
# @param [String] name
|
34
|
+
#
|
35
|
+
# @raise [ROM::Lint::Linter::Failure] if linting fails
|
36
|
+
#
|
37
|
+
# @api public
|
38
|
+
def lint(name)
|
39
|
+
public_send name
|
40
|
+
true # for assertions
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
# Return a list a lint methods
|
46
|
+
#
|
47
|
+
# @return [String]
|
48
|
+
#
|
49
|
+
# @api private
|
50
|
+
def self.lints
|
51
|
+
public_instance_methods(true).grep(/^lint_/).map(&:to_s)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Raise a failure if a lint verification fails
|
55
|
+
#
|
56
|
+
# @raise [ROM::Lint::Linter::Failure]
|
57
|
+
#
|
58
|
+
# @api private
|
59
|
+
def complain(*args)
|
60
|
+
raise Failure, *args
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'rom/lint/linter'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
module Lint
|
5
|
+
# Ensures that a [ROM::Repository] extension provides datasets through the
|
6
|
+
# expected methods
|
7
|
+
#
|
8
|
+
# @api public
|
9
|
+
class Repository < ROM::Lint::Linter
|
10
|
+
# The repository identifier e.g. +:memory+
|
11
|
+
#
|
12
|
+
# @api public
|
13
|
+
attr_reader :identifier
|
14
|
+
|
15
|
+
# The repository class
|
16
|
+
#
|
17
|
+
# @api public
|
18
|
+
attr_reader :repository
|
19
|
+
|
20
|
+
# The optional URI
|
21
|
+
#
|
22
|
+
# @api public
|
23
|
+
attr_reader :uri
|
24
|
+
|
25
|
+
# Create a repository linter
|
26
|
+
#
|
27
|
+
# @param [Symbol] identifier
|
28
|
+
# @param [Class] repository
|
29
|
+
# @param [String] uri optional
|
30
|
+
def initialize(identifier, repository, uri = nil)
|
31
|
+
@identifier = identifier
|
32
|
+
@repository = repository
|
33
|
+
@uri = uri
|
34
|
+
end
|
35
|
+
|
36
|
+
# Lint: Ensure that +repository+ setups up its instance
|
37
|
+
#
|
38
|
+
# @api public
|
39
|
+
def lint_repository_setup
|
40
|
+
return if repository_instance.instance_of? repository
|
41
|
+
|
42
|
+
complain <<-STRING
|
43
|
+
#{repository}.setup must return a repository instance but
|
44
|
+
returned #{repository_instance.inspect}
|
45
|
+
STRING
|
46
|
+
end
|
47
|
+
|
48
|
+
# Lint: Ensure that +repository_instance+ responds to +[]+
|
49
|
+
#
|
50
|
+
# @api public
|
51
|
+
def lint_dataset_reader
|
52
|
+
return if repository_instance.respond_to? :[]
|
53
|
+
|
54
|
+
complain "#{repository_instance} must respond to []"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Lint: Ensure that +repository_instance+ responds to +dataset?+
|
58
|
+
#
|
59
|
+
# @api public
|
60
|
+
def lint_dataset_predicate
|
61
|
+
return if repository_instance.respond_to? :dataset?
|
62
|
+
|
63
|
+
complain "#{repository_instance} must respond to dataset?"
|
64
|
+
end
|
65
|
+
|
66
|
+
# Setup repository instance
|
67
|
+
#
|
68
|
+
# @api public
|
69
|
+
def repository_instance
|
70
|
+
if uri
|
71
|
+
ROM::Repository.setup(identifier, uri)
|
72
|
+
else
|
73
|
+
ROM::Repository.setup(identifier)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rom/lint/repository'
|
2
|
+
require 'rom/lint/enumerable_dataset'
|
3
|
+
|
4
|
+
RSpec.shared_examples "a rom repository" do
|
5
|
+
ROM::Lint::Repository.each_lint do |name, linter|
|
6
|
+
it name do
|
7
|
+
result = linter.new(identifier, repository, uri).lint(name)
|
8
|
+
expect(result).to be_truthy
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
RSpec.shared_examples "a rom enumerable dataset" do
|
14
|
+
ROM::Lint::EnumerableDataset.each_lint do |name, linter|
|
15
|
+
it name do
|
16
|
+
result = linter.new(dataset, data).lint(name)
|
17
|
+
expect(result).to be_truthy
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'rom/lint/repository'
|
2
|
+
require 'rom/lint/enumerable_dataset'
|
3
|
+
|
4
|
+
module ROM
|
5
|
+
module Lint
|
6
|
+
# A module that helps to define test methods
|
7
|
+
module Test
|
8
|
+
# Defines a test method converting lint failures to assertions
|
9
|
+
#
|
10
|
+
# @param [String] name
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
def define_test_method(name, &block)
|
14
|
+
define_method "test_#{name}" do
|
15
|
+
begin
|
16
|
+
instance_eval(&block)
|
17
|
+
rescue ROM::Lint::Linter::Failure => f
|
18
|
+
raise Minitest::Assertion, f.message
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# This is a simple lint-test for repository class to ensure the
|
25
|
+
# basic interfaces are in place
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
#
|
29
|
+
# class MyRepoTest < Minitest::Test
|
30
|
+
# include ROM::Lint::TestRepository
|
31
|
+
#
|
32
|
+
# def setup
|
33
|
+
# @repository = MyRepository
|
34
|
+
# @uri = "super_db://something"
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# @api public
|
39
|
+
module TestRepository
|
40
|
+
extend ROM::Lint::Test
|
41
|
+
|
42
|
+
# Returns the repository identifier e.g. +:memory+
|
43
|
+
#
|
44
|
+
# @api public
|
45
|
+
attr_reader :identifier
|
46
|
+
|
47
|
+
# Returns the repository class
|
48
|
+
#
|
49
|
+
# @api public
|
50
|
+
attr_reader :repository
|
51
|
+
|
52
|
+
# Returns repostory's URI e.g. "super_db://something"
|
53
|
+
#
|
54
|
+
# @api public
|
55
|
+
attr_reader :uri
|
56
|
+
|
57
|
+
ROM::Lint::Repository.each_lint do |name, linter|
|
58
|
+
define_test_method name do
|
59
|
+
assert linter.new(identifier, repository, uri).lint(name)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# This is a simple lint-test for an repository dataset class to ensure the
|
65
|
+
# basic behavior is correct
|
66
|
+
#
|
67
|
+
# @example
|
68
|
+
#
|
69
|
+
# class MyDatasetLintTest < Minitest::Test
|
70
|
+
# include ROM::Repository::Lint::TestEnumerableDataset
|
71
|
+
#
|
72
|
+
# def setup
|
73
|
+
# @data = [{ name: 'Jane', age: 24 }, { name: 'Joe', age: 25 }]
|
74
|
+
# @dataset = MyDataset.new(@data, [:name, :age])
|
75
|
+
# end
|
76
|
+
# end
|
77
|
+
# @api public
|
78
|
+
module TestEnumerableDataset
|
79
|
+
extend ROM::Lint::Test
|
80
|
+
|
81
|
+
# Returns the dataset instance
|
82
|
+
#
|
83
|
+
# @api public
|
84
|
+
attr_reader :dataset
|
85
|
+
|
86
|
+
# Returns the expected data
|
87
|
+
#
|
88
|
+
# @api public
|
89
|
+
attr_reader :data
|
90
|
+
|
91
|
+
ROM::Lint::EnumerableDataset.each_lint do |name, linter|
|
92
|
+
define_test_method name do
|
93
|
+
assert linter.new(dataset, data).lint(name)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
data/lib/rom/mapper.rb
CHANGED
@@ -1,8 +1,19 @@
|
|
1
|
+
require 'rom/mapper/dsl'
|
2
|
+
|
1
3
|
module ROM
|
2
4
|
# Mapper is a simple object that uses a transformer to load relations
|
3
5
|
#
|
4
6
|
# @private
|
5
7
|
class Mapper
|
8
|
+
include DSL
|
9
|
+
include Equalizer.new(:transformer, :header)
|
10
|
+
|
11
|
+
defines :relation, :register_as, :symbolize_keys,
|
12
|
+
:prefix, :prefix_separator, :inherit_header
|
13
|
+
|
14
|
+
inherit_header true
|
15
|
+
prefix_separator '_'.freeze
|
16
|
+
|
6
17
|
# @return [Object] transformer object built by a processor
|
7
18
|
#
|
8
19
|
# @api private
|
@@ -13,6 +24,14 @@ module ROM
|
|
13
24
|
# @api private
|
14
25
|
attr_reader :header
|
15
26
|
|
27
|
+
# Register suclasses during setup phase
|
28
|
+
#
|
29
|
+
# @api private
|
30
|
+
def self.inherited(klass)
|
31
|
+
super
|
32
|
+
ROM.register_mapper(klass)
|
33
|
+
end
|
34
|
+
|
16
35
|
# @return [Hash] registered processors
|
17
36
|
#
|
18
37
|
# @api private
|
@@ -35,8 +54,16 @@ module ROM
|
|
35
54
|
# @return [Mapper]
|
36
55
|
#
|
37
56
|
# @api private
|
38
|
-
def self.build(header, processor = :transproc)
|
39
|
-
new(processors.fetch(processor).build(header), header)
|
57
|
+
def self.build(header = self.header, processor = :transproc)
|
58
|
+
new(Mapper.processors.fetch(processor).build(header), header)
|
59
|
+
end
|
60
|
+
|
61
|
+
# @api private
|
62
|
+
def self.registry(descendants)
|
63
|
+
descendants.each_with_object({}) do |klass, h|
|
64
|
+
name = klass.register_as || klass.relation
|
65
|
+
(h[klass.base_relation] ||= {})[name] = klass.build
|
66
|
+
end
|
40
67
|
end
|
41
68
|
|
42
69
|
# @api private
|
@@ -52,11 +79,11 @@ module ROM
|
|
52
79
|
header.model
|
53
80
|
end
|
54
81
|
|
55
|
-
# Process a relation using the
|
82
|
+
# Process a relation using the transformer
|
56
83
|
#
|
57
84
|
# @api private
|
58
|
-
def
|
59
|
-
transformer[relation.to_a]
|
85
|
+
def call(relation)
|
86
|
+
transformer[relation.to_a]
|
60
87
|
end
|
61
88
|
end
|
62
89
|
end
|
@@ -0,0 +1,240 @@
|
|
1
|
+
require 'rom/header'
|
2
|
+
require 'rom/mapper/model_dsl'
|
3
|
+
|
4
|
+
module ROM
|
5
|
+
class Mapper
|
6
|
+
# Mapper attribute DSL exposed by mapper subclasses
|
7
|
+
#
|
8
|
+
# This class is private even though its methods are exposed by mappers.
|
9
|
+
# Typically it's not meant to be used directly.
|
10
|
+
#
|
11
|
+
# @private
|
12
|
+
class AttributeDSL
|
13
|
+
include ModelDSL
|
14
|
+
|
15
|
+
attr_reader :attributes, :options, :symbolize_keys, :prefix, :prefix_separator
|
16
|
+
|
17
|
+
# @param [Array] attributes accumulator array
|
18
|
+
# @param [Hash] options
|
19
|
+
#
|
20
|
+
# @api private
|
21
|
+
def initialize(attributes, options)
|
22
|
+
@attributes = attributes
|
23
|
+
@options = options
|
24
|
+
@symbolize_keys = options.fetch(:symbolize_keys)
|
25
|
+
@prefix = options.fetch(:prefix)
|
26
|
+
@prefix_separator = options.fetch(:prefix_separator)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Define a mapping attribute with its options
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# dsl = AttributeDSL.new([])
|
33
|
+
#
|
34
|
+
# dsl.attribute(:name)
|
35
|
+
# dsl.attribute(:email, from: 'user_email')
|
36
|
+
#
|
37
|
+
# @api public
|
38
|
+
def attribute(name, options = EMPTY_HASH)
|
39
|
+
with_attr_options(name, options) do |attr_options|
|
40
|
+
add_attribute(name, attr_options)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Remove an attribute
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# dsl = AttributeDSL.new([[:name]])
|
48
|
+
#
|
49
|
+
# dsl.exclude(:name)
|
50
|
+
# dsl.attributes # => []
|
51
|
+
#
|
52
|
+
# @api public
|
53
|
+
def exclude(*names)
|
54
|
+
attributes.delete_if { |attr| names.include?(attr.first) }
|
55
|
+
end
|
56
|
+
|
57
|
+
# Define an embedded attribute
|
58
|
+
#
|
59
|
+
# Block exposes the attribute dsl too
|
60
|
+
#
|
61
|
+
# @example
|
62
|
+
# dsl = AttributeDSL.new([])
|
63
|
+
#
|
64
|
+
# dsl.embedded :tags, type: :array do
|
65
|
+
# attribute :name
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# dsl.embedded :address, type: :hash do
|
69
|
+
# model Address
|
70
|
+
# attribute :name
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# @param [Symbol] name attribute
|
74
|
+
#
|
75
|
+
# @param [Hash] options
|
76
|
+
# @option options [Symbol] :type Embedded type can be :hash or :array
|
77
|
+
# @option options [Symbol] :prefix Prefix that should be used for
|
78
|
+
# its attributes
|
79
|
+
#
|
80
|
+
# @api public
|
81
|
+
def embedded(name, options, &block)
|
82
|
+
with_attr_options(name) do |attr_options|
|
83
|
+
dsl = new(options, &block)
|
84
|
+
|
85
|
+
attr_options.update(options)
|
86
|
+
|
87
|
+
add_attribute(
|
88
|
+
name, { header: dsl.header, type: :array }.update(attr_options)
|
89
|
+
)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Define an embedded hash attribute that requires "wrapping" transformation
|
94
|
+
#
|
95
|
+
# Typically this is used in sql context when relation is a join.
|
96
|
+
#
|
97
|
+
# @example
|
98
|
+
# dsl = AttributeDSL.new([])
|
99
|
+
#
|
100
|
+
# dsl.wrap(address: [:street, :zipcode, :city])
|
101
|
+
#
|
102
|
+
# dsl.wrap(:address) do
|
103
|
+
# model Address
|
104
|
+
# attribute :street
|
105
|
+
# attribute :zipcode
|
106
|
+
# attribute :city
|
107
|
+
# end
|
108
|
+
#
|
109
|
+
# @see AttributeDSL#embedded
|
110
|
+
#
|
111
|
+
# @api public
|
112
|
+
def wrap(*args, &block)
|
113
|
+
with_name_or_options(*args) do |name, options|
|
114
|
+
dsl(name, { type: :hash, wrap: true }.update(options), &block)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Define an embedded hash attribute that requires "grouping" transformation
|
119
|
+
#
|
120
|
+
# Typically this is used in sql context when relation is a join.
|
121
|
+
#
|
122
|
+
# @example
|
123
|
+
# dsl = AttributeDSL.new([])
|
124
|
+
#
|
125
|
+
# dsl.group(tags: [:name])
|
126
|
+
#
|
127
|
+
# dsl.group(:tags) do
|
128
|
+
# model Tag
|
129
|
+
# attribute :name
|
130
|
+
# end
|
131
|
+
#
|
132
|
+
# @see AttributeDSL#embedded
|
133
|
+
#
|
134
|
+
# @api public
|
135
|
+
def group(*args, &block)
|
136
|
+
with_name_or_options(*args) do |name, options|
|
137
|
+
dsl(name, { type: :array, group: true }.update(options), &block)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Generate a header from attribute definitions
|
142
|
+
#
|
143
|
+
# @return [Header]
|
144
|
+
#
|
145
|
+
# @api private
|
146
|
+
def header
|
147
|
+
Header.coerce(attributes, model)
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
# Handle attribute options common for all definitions
|
153
|
+
#
|
154
|
+
# @api private
|
155
|
+
def with_attr_options(name, options = EMPTY_HASH)
|
156
|
+
attr_options = options.dup
|
157
|
+
|
158
|
+
attr_options[:from] ||= :"#{prefix}#{prefix_separator}#{name}" if prefix
|
159
|
+
|
160
|
+
if symbolize_keys
|
161
|
+
attr_options.update(from: attr_options.fetch(:from) { name }.to_s)
|
162
|
+
end
|
163
|
+
|
164
|
+
yield(attr_options)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Handle "name or options" syntax used by `wrap` and `group`
|
168
|
+
#
|
169
|
+
# @api private
|
170
|
+
def with_name_or_options(*args)
|
171
|
+
name, options =
|
172
|
+
if args.size > 1
|
173
|
+
args
|
174
|
+
else
|
175
|
+
[args.first, {}]
|
176
|
+
end
|
177
|
+
|
178
|
+
yield(name, options)
|
179
|
+
end
|
180
|
+
|
181
|
+
# Create another instance of the dsl for nested definitions
|
182
|
+
#
|
183
|
+
# This is used by embedded, wrap and group
|
184
|
+
#
|
185
|
+
# @api private
|
186
|
+
def dsl(name_or_attrs, options, &block)
|
187
|
+
if block
|
188
|
+
attributes_from_block(name_or_attrs, options, &block)
|
189
|
+
else
|
190
|
+
attributes_from_hash(name_or_attrs, options)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Define attributes from a nested block
|
195
|
+
#
|
196
|
+
# Used by embedded, wrap and group
|
197
|
+
#
|
198
|
+
# @api private
|
199
|
+
def attributes_from_block(name, options, &block)
|
200
|
+
dsl = new(options, &block)
|
201
|
+
header = dsl.header
|
202
|
+
add_attribute(name, options.update(header: header))
|
203
|
+
header.each { |attr| exclude(attr.key) }
|
204
|
+
end
|
205
|
+
|
206
|
+
# Define attributes from the `name => attributes` hash syntax
|
207
|
+
#
|
208
|
+
# Used by wrap and group
|
209
|
+
#
|
210
|
+
# @api private
|
211
|
+
def attributes_from_hash(hash, options)
|
212
|
+
hash.each do |name, header|
|
213
|
+
with_attr_options(name, options) do |attr_options|
|
214
|
+
add_attribute(name, attr_options.update(header: header.zip))
|
215
|
+
header.each { |attr| exclude(attr) }
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# Add a new attribute and make sure it overrides previous definition
|
221
|
+
#
|
222
|
+
# @api private
|
223
|
+
def add_attribute(name, options)
|
224
|
+
exclude(name, name.to_s)
|
225
|
+
attributes << [name, options]
|
226
|
+
end
|
227
|
+
|
228
|
+
# Create a new dsl instance of potentially overidden options
|
229
|
+
#
|
230
|
+
# Embedded, wrap and group can override top-level options like `prefix`
|
231
|
+
#
|
232
|
+
# @api private
|
233
|
+
def new(options, &block)
|
234
|
+
dsl = self.class.new([], @options.merge(options))
|
235
|
+
dsl.instance_exec(&block)
|
236
|
+
dsl
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|