rom 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +1 -1
- data/.travis.yml +5 -3
- data/CHANGELOG.md +38 -0
- data/Gemfile +2 -14
- data/README.md +11 -17
- data/lib/rom.rb +2 -0
- data/lib/rom/association_set.rb +26 -0
- data/lib/rom/command.rb +50 -45
- data/lib/rom/command_registry.rb +26 -3
- data/lib/rom/commands/class_interface.rb +52 -19
- data/lib/rom/commands/composite.rb +5 -0
- data/lib/rom/commands/delete.rb +1 -5
- data/lib/rom/commands/graph.rb +11 -0
- data/lib/rom/commands/lazy.rb +2 -0
- data/lib/rom/commands/update.rb +1 -5
- data/lib/rom/configuration.rb +2 -0
- data/lib/rom/container.rb +3 -3
- data/lib/rom/global.rb +1 -23
- data/lib/rom/memory/commands.rb +2 -0
- data/lib/rom/memory/relation.rb +3 -0
- data/lib/rom/memory/storage.rb +4 -7
- data/lib/rom/memory/types.rb +9 -0
- data/lib/rom/pipeline.rb +26 -12
- data/lib/rom/plugin_registry.rb +2 -2
- data/lib/rom/plugins/command/schema.rb +26 -0
- data/lib/rom/plugins/configuration/configuration_dsl.rb +2 -1
- data/lib/rom/plugins/relation/key_inference.rb +18 -3
- data/lib/rom/plugins/relation/registry_reader.rb +3 -1
- data/lib/rom/plugins/relation/view.rb +11 -6
- data/lib/rom/relation.rb +76 -16
- data/lib/rom/relation/class_interface.rb +44 -3
- data/lib/rom/relation/curried.rb +13 -4
- data/lib/rom/relation/graph.rb +15 -5
- data/lib/rom/relation/loaded.rb +42 -6
- data/lib/rom/relation/name.rb +102 -0
- data/lib/rom/relation_registry.rb +5 -0
- data/lib/rom/schema.rb +87 -0
- data/lib/rom/schema/dsl.rb +58 -0
- data/lib/rom/setup/auto_registration.rb +2 -2
- data/lib/rom/setup/finalize.rb +5 -5
- data/lib/rom/setup/finalize/{commands.rb → finalize_commands.rb} +2 -22
- data/lib/rom/setup/finalize/{mappers.rb → finalize_mappers.rb} +0 -0
- data/lib/rom/setup/finalize/finalize_relations.rb +60 -0
- data/lib/rom/types.rb +18 -0
- data/lib/rom/version.rb +1 -1
- data/log/.gitkeep +0 -0
- data/rom.gemspec +4 -2
- data/spec/integration/command_registry_spec.rb +13 -0
- data/spec/integration/commands/delete_spec.rb +0 -17
- data/spec/integration/commands/graph_builder_spec.rb +1 -1
- data/spec/integration/commands/graph_spec.rb +1 -1
- data/spec/integration/commands/update_spec.rb +0 -19
- data/spec/integration/commands_spec.rb +10 -3
- data/spec/integration/multi_repo_spec.rb +1 -1
- data/spec/integration/relations/default_dataset_spec.rb +27 -4
- data/spec/integration/setup_spec.rb +1 -4
- data/spec/shared/command_behavior.rb +17 -7
- data/spec/shared/container.rb +2 -2
- data/spec/shared/gateway_only.rb +1 -1
- data/spec/spec_helper.rb +5 -6
- data/spec/unit/rom/association_set_spec.rb +23 -0
- data/spec/unit/rom/auto_registration_spec.rb +1 -1
- data/spec/unit/rom/commands/lazy_spec.rb +8 -0
- data/spec/unit/rom/commands_spec.rb +45 -7
- data/spec/unit/rom/configurable_spec.rb +1 -1
- data/spec/unit/rom/container_spec.rb +6 -0
- data/spec/unit/rom/create_container_spec.rb +1 -1
- data/spec/unit/rom/environment_spec.rb +1 -1
- data/spec/unit/rom/memory/commands_spec.rb +43 -0
- data/spec/unit/rom/plugins/relation/key_inference_spec.rb +70 -12
- data/spec/unit/rom/plugins/relation/view_spec.rb +4 -0
- data/spec/unit/rom/relation/graph_spec.rb +10 -0
- data/spec/unit/rom/relation/lazy_spec.rb +3 -3
- data/spec/unit/rom/relation/loaded_spec.rb +15 -0
- data/spec/unit/rom/relation/name_spec.rb +51 -0
- data/spec/unit/rom/relation/schema_spec.rb +117 -0
- data/spec/unit/rom/relation_spec.rb +37 -7
- data/spec/unit/rom/schema_spec.rb +10 -0
- metadata +51 -12
- data/lib/rom/setup/finalize/relations.rb +0 -53
- data/spec/unit/rom/global_spec.rb +0 -18
- data/spec/unit/rom/registry_spec.rb +0 -38
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'dry/equalizer'
|
2
|
+
require 'concurrent/map'
|
3
|
+
|
4
|
+
module ROM
|
5
|
+
class Relation
|
6
|
+
# Relation name container
|
7
|
+
#
|
8
|
+
# This is a simple struct with two fields.
|
9
|
+
# It handles both relation registration name (i.e. Symbol) and dataset name.
|
10
|
+
# The reason we need it is a simplification of passing around these two objects.
|
11
|
+
# It is quite common to have a dataset named differently from a relation
|
12
|
+
# built on top if you are dealing with a legacy DB and often you need both
|
13
|
+
# to support things such as associations (rom-sql as an example).
|
14
|
+
#
|
15
|
+
# @api private
|
16
|
+
class Name
|
17
|
+
include Dry::Equalizer(:relation, :dataset)
|
18
|
+
|
19
|
+
# Coerce an object to a Name instance
|
20
|
+
#
|
21
|
+
# @return [ROM::Relation::Name]
|
22
|
+
#
|
23
|
+
# @api private
|
24
|
+
def self.[](*args)
|
25
|
+
cache.fetch_or_store(args.hash) do
|
26
|
+
relation, dataset = args
|
27
|
+
|
28
|
+
if relation.is_a?(Name)
|
29
|
+
relation
|
30
|
+
else
|
31
|
+
new(relation, dataset)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# @api private
|
37
|
+
def self.cache
|
38
|
+
@cache ||= Concurrent::Map.new
|
39
|
+
end
|
40
|
+
|
41
|
+
# Relation registration name
|
42
|
+
#
|
43
|
+
# @return [Symbol]
|
44
|
+
#
|
45
|
+
# @api private
|
46
|
+
attr_reader :relation
|
47
|
+
|
48
|
+
# Underlying dataset name
|
49
|
+
#
|
50
|
+
# @return [Symbol]
|
51
|
+
#
|
52
|
+
# @api private
|
53
|
+
attr_reader :dataset
|
54
|
+
|
55
|
+
# @api private
|
56
|
+
def initialize(relation, dataset = nil)
|
57
|
+
@relation = relation
|
58
|
+
@dataset = dataset || relation
|
59
|
+
end
|
60
|
+
|
61
|
+
# Return relation name
|
62
|
+
#
|
63
|
+
# @return [String]
|
64
|
+
#
|
65
|
+
# @api private
|
66
|
+
def to_s
|
67
|
+
if relation == dataset
|
68
|
+
relation
|
69
|
+
else
|
70
|
+
"#{relation} on #{dataset}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Alias for registration key implicitly called by ROM::Registry
|
75
|
+
#
|
76
|
+
# @return [Symbol]
|
77
|
+
#
|
78
|
+
# @api private
|
79
|
+
def to_sym
|
80
|
+
relation
|
81
|
+
end
|
82
|
+
|
83
|
+
# Return inspected relation
|
84
|
+
#
|
85
|
+
# @return [String]
|
86
|
+
#
|
87
|
+
# @api private
|
88
|
+
def inspect
|
89
|
+
"#{self.class.name}(#{to_s})"
|
90
|
+
end
|
91
|
+
|
92
|
+
# Build a new name. Useful for Curried and other relation proxies
|
93
|
+
#
|
94
|
+
# @return [ROM::Relation::Name]
|
95
|
+
#
|
96
|
+
# @api private
|
97
|
+
def with(relation)
|
98
|
+
self.class[relation, dataset]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/lib/rom/schema.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'dry-equalizer'
|
2
|
+
|
3
|
+
require 'rom/support/constants'
|
4
|
+
require 'rom/schema/dsl'
|
5
|
+
require 'rom/association_set'
|
6
|
+
|
7
|
+
module ROM
|
8
|
+
# Relation schema
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
class Schema
|
12
|
+
EMPTY_ASSOCIATION_SET = AssociationSet.new(EMPTY_HASH).freeze
|
13
|
+
|
14
|
+
include Dry::Equalizer(:name, :attributes, :associations)
|
15
|
+
include Enumerable
|
16
|
+
|
17
|
+
# @!attribute [r] name
|
18
|
+
# @return [Symbol] The name of this schema
|
19
|
+
attr_reader :name
|
20
|
+
|
21
|
+
# @!attribute [r] attributes
|
22
|
+
# @return [Hash] The hash with schema attribute types
|
23
|
+
attr_reader :attributes
|
24
|
+
|
25
|
+
# @!attribute [r] associations
|
26
|
+
# @return [AssociationSet] Optional association set (this is adapter-specific)
|
27
|
+
attr_reader :associations
|
28
|
+
|
29
|
+
# @!attribute [r] inferrer
|
30
|
+
# @return [#call] An optional inferrer object used in `finalize!`
|
31
|
+
attr_reader :inferrer
|
32
|
+
|
33
|
+
# @!attribute [r] primary_key
|
34
|
+
# @return [Array<Dry::Types::Definition] Primary key array
|
35
|
+
attr_reader :primary_key
|
36
|
+
|
37
|
+
alias_method :to_h, :attributes
|
38
|
+
|
39
|
+
# @api private
|
40
|
+
def initialize(name, attributes, inferrer: nil, associations: EMPTY_ASSOCIATION_SET)
|
41
|
+
@name = name
|
42
|
+
@attributes = attributes
|
43
|
+
@associations = associations
|
44
|
+
@inferrer = inferrer
|
45
|
+
end
|
46
|
+
|
47
|
+
# Iterate over schema's attributes
|
48
|
+
#
|
49
|
+
# @yield [Dry::Data::Type]
|
50
|
+
#
|
51
|
+
# @api public
|
52
|
+
def each(&block)
|
53
|
+
attributes.each_value(&block)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Return attribute
|
57
|
+
#
|
58
|
+
# @api public
|
59
|
+
def [](name)
|
60
|
+
attributes.fetch(name)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Return FK attribute for a given relation name
|
64
|
+
#
|
65
|
+
# @return [Dry::Types::Definition]
|
66
|
+
#
|
67
|
+
# @api public
|
68
|
+
def foreign_key(relation)
|
69
|
+
detect { |attr| attr.meta[:foreign_key] && attr.meta[:relation] == relation }
|
70
|
+
end
|
71
|
+
|
72
|
+
# This hook is called when relation is being build during container finalization
|
73
|
+
#
|
74
|
+
# When block is provided it'll be called just before freezing the instance
|
75
|
+
# so that additional ivars can be set
|
76
|
+
#
|
77
|
+
# @return [self]
|
78
|
+
#
|
79
|
+
# @api private
|
80
|
+
def finalize!(gateway = nil, &block)
|
81
|
+
@attributes = inferrer.call(name.dataset, gateway) if inferrer
|
82
|
+
@primary_key = select { |attr| attr.meta[:primary_key] == true }
|
83
|
+
block.call if block
|
84
|
+
freeze
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'dry-equalizer'
|
2
|
+
require 'rom/types'
|
3
|
+
|
4
|
+
module ROM
|
5
|
+
# Relation schema
|
6
|
+
#
|
7
|
+
# @api public
|
8
|
+
class Schema
|
9
|
+
include Dry::Equalizer(:name, :attributes)
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
attr_reader :name, :attributes, :inferrer
|
13
|
+
|
14
|
+
# @api public
|
15
|
+
class DSL < BasicObject
|
16
|
+
attr_reader :name, :attributes, :inferrer
|
17
|
+
|
18
|
+
# @api private
|
19
|
+
def initialize(name, inferrer, &block)
|
20
|
+
@name = name
|
21
|
+
@inferrer = inferrer
|
22
|
+
@attributes = nil
|
23
|
+
|
24
|
+
if block
|
25
|
+
instance_exec(&block)
|
26
|
+
elsif inferrer.nil?
|
27
|
+
raise ArgumentError,
|
28
|
+
'You must pass a block to define a schema or set an inferrer for automatic inferring'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Defines a relation attribute with its type
|
33
|
+
#
|
34
|
+
# @see Relation.schema
|
35
|
+
#
|
36
|
+
# @api public
|
37
|
+
def attribute(name, type)
|
38
|
+
@attributes ||= {}
|
39
|
+
@attributes[name] = type.meta(name: name)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Specify which key(s) should be the primary key
|
43
|
+
#
|
44
|
+
# @api public
|
45
|
+
def primary_key(*names)
|
46
|
+
names.each do |name|
|
47
|
+
attributes[name] = attributes[name].meta(primary_key: true)
|
48
|
+
end
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
# @api private
|
53
|
+
def call
|
54
|
+
Schema.new(name, attributes, inferrer: inferrer && inferrer.new(self))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -14,9 +14,9 @@ module ROM
|
|
14
14
|
|
15
15
|
def initialize(directory, options = EMPTY_HASH)
|
16
16
|
super
|
17
|
-
@directory = directory
|
17
|
+
@directory = Pathname(directory)
|
18
18
|
@globs = Hash[[:relations, :commands, :mappers].map { |name|
|
19
|
-
[name,
|
19
|
+
[name, @directory.join("#{name}/**/*.rb")]
|
20
20
|
}]
|
21
21
|
end
|
22
22
|
|
data/lib/rom/setup/finalize.rb
CHANGED
@@ -6,9 +6,9 @@ require 'rom/command_registry'
|
|
6
6
|
require 'rom/mapper_registry'
|
7
7
|
|
8
8
|
require 'rom/container'
|
9
|
-
require 'rom/setup/finalize/
|
10
|
-
require 'rom/setup/finalize/
|
11
|
-
require 'rom/setup/finalize/
|
9
|
+
require 'rom/setup/finalize/finalize_commands'
|
10
|
+
require 'rom/setup/finalize/finalize_relations'
|
11
|
+
require 'rom/setup/finalize/finalize_mappers'
|
12
12
|
|
13
13
|
# temporary
|
14
14
|
require 'rom/configuration_dsl/relation'
|
@@ -57,7 +57,7 @@ module ROM
|
|
57
57
|
#
|
58
58
|
# @api private
|
59
59
|
def run!
|
60
|
-
|
60
|
+
infer_relations
|
61
61
|
|
62
62
|
relations = load_relations
|
63
63
|
mappers = load_mappers
|
@@ -112,7 +112,7 @@ module ROM
|
|
112
112
|
# Relations explicitly defined are being skipped
|
113
113
|
#
|
114
114
|
# @api private
|
115
|
-
def
|
115
|
+
def infer_relations
|
116
116
|
datasets.each do |gateway, schema|
|
117
117
|
schema.each do |name|
|
118
118
|
if infer_relation?(gateway, name)
|
@@ -31,37 +31,17 @@ module ROM
|
|
31
31
|
gateway = @gateways[relation.class.gateway]
|
32
32
|
gateway.extend_command_class(klass, relation.dataset)
|
33
33
|
|
34
|
-
klass.
|
34
|
+
klass.extend_for_relation(relation) if klass.restrictable
|
35
35
|
|
36
36
|
(h[rel_name] ||= {})[name] = klass.build(relation)
|
37
37
|
end
|
38
38
|
|
39
39
|
commands = registry.each_with_object({}) do |(name, rel_commands), h|
|
40
|
-
h[name] = CommandRegistry.new(rel_commands)
|
40
|
+
h[name] = CommandRegistry.new(name, rel_commands)
|
41
41
|
end
|
42
42
|
|
43
43
|
Registry.new(commands)
|
44
44
|
end
|
45
|
-
|
46
|
-
# @api private
|
47
|
-
def relation_methods_mod(relation_class)
|
48
|
-
mod = Module.new
|
49
|
-
relation_class.view_methods.each do |meth|
|
50
|
-
mod.module_eval <<-RUBY
|
51
|
-
def #{meth}(*args)
|
52
|
-
response = relation.public_send(:#{meth}, *args)
|
53
|
-
|
54
|
-
if response.is_a?(relation.class)
|
55
|
-
new(response)
|
56
|
-
else
|
57
|
-
response
|
58
|
-
end
|
59
|
-
end
|
60
|
-
RUBY
|
61
|
-
end
|
62
|
-
|
63
|
-
mod
|
64
|
-
end
|
65
45
|
end
|
66
46
|
end
|
67
47
|
end
|
File without changes
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'rom/relation_registry'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
class Finalize
|
5
|
+
class FinalizeRelations
|
6
|
+
# Build relation registry of specified descendant classes
|
7
|
+
#
|
8
|
+
# This is used by the setup
|
9
|
+
#
|
10
|
+
# @param [Hash] gateways
|
11
|
+
# @param [Array] relation_classes a list of relation descendants
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
def initialize(gateways, relation_classes)
|
15
|
+
@gateways = gateways
|
16
|
+
@relation_classes = relation_classes
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Hash]
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
def run!
|
23
|
+
RelationRegistry.new do |registry, relations|
|
24
|
+
@relation_classes.each do |klass|
|
25
|
+
relation = build_relation(klass, registry)
|
26
|
+
|
27
|
+
key = relation.name.to_sym
|
28
|
+
|
29
|
+
if registry.key?(key)
|
30
|
+
raise RelationAlreadyDefinedError,
|
31
|
+
"Relation with `register_as #{key.inspect}` registered more " \
|
32
|
+
"than once"
|
33
|
+
end
|
34
|
+
|
35
|
+
relations[key] = relation
|
36
|
+
end
|
37
|
+
|
38
|
+
relations.each_value do |relation|
|
39
|
+
relation.class.finalize(registry, relation)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [ROM::Relation]
|
45
|
+
#
|
46
|
+
# @api private
|
47
|
+
def build_relation(klass, registry)
|
48
|
+
# TODO: raise a meaningful error here and add spec covering the case
|
49
|
+
# where klass' gateway points to non-existant repo
|
50
|
+
gateway = @gateways.fetch(klass.gateway)
|
51
|
+
ds_proc = klass.dataset_proc || -> _ { self }
|
52
|
+
|
53
|
+
klass.schema.finalize!(gateway) if klass.schema
|
54
|
+
dataset = gateway.dataset(klass.dataset).instance_exec(klass, &ds_proc)
|
55
|
+
|
56
|
+
klass.new(dataset, __registry__: registry)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/rom/types.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'dry-types'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
module Types
|
5
|
+
include Dry::Types.module
|
6
|
+
|
7
|
+
def self.included(other)
|
8
|
+
other.extend(Methods)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
module Methods
|
13
|
+
def ForeignKey(relation, type = Types::Int)
|
14
|
+
type.meta(foreign_key: true, relation: relation)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/rom/version.rb
CHANGED
data/log/.gitkeep
ADDED
File without changes
|
data/rom.gemspec
CHANGED
@@ -15,9 +15,11 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.test_files = `git ls-files -- {spec}/*`.split("\n")
|
16
16
|
gem.license = 'MIT'
|
17
17
|
|
18
|
+
gem.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
|
18
19
|
gem.add_runtime_dependency 'dry-equalizer', '~> 0.2'
|
19
|
-
gem.add_runtime_dependency '
|
20
|
-
gem.add_runtime_dependency 'rom-
|
20
|
+
gem.add_runtime_dependency 'dry-types', '~> 0.8'
|
21
|
+
gem.add_runtime_dependency 'rom-support', '~> 2.0'
|
22
|
+
gem.add_runtime_dependency 'rom-mapper', '~> 0.4.0'
|
21
23
|
|
22
24
|
gem.add_development_dependency 'rake', '~> 10.3'
|
23
25
|
gem.add_development_dependency 'rspec', '~> 3.3'
|