rom 1.0.0 → 2.0.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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -1
  3. data/.travis.yml +5 -3
  4. data/CHANGELOG.md +38 -0
  5. data/Gemfile +2 -14
  6. data/README.md +11 -17
  7. data/lib/rom.rb +2 -0
  8. data/lib/rom/association_set.rb +26 -0
  9. data/lib/rom/command.rb +50 -45
  10. data/lib/rom/command_registry.rb +26 -3
  11. data/lib/rom/commands/class_interface.rb +52 -19
  12. data/lib/rom/commands/composite.rb +5 -0
  13. data/lib/rom/commands/delete.rb +1 -5
  14. data/lib/rom/commands/graph.rb +11 -0
  15. data/lib/rom/commands/lazy.rb +2 -0
  16. data/lib/rom/commands/update.rb +1 -5
  17. data/lib/rom/configuration.rb +2 -0
  18. data/lib/rom/container.rb +3 -3
  19. data/lib/rom/global.rb +1 -23
  20. data/lib/rom/memory/commands.rb +2 -0
  21. data/lib/rom/memory/relation.rb +3 -0
  22. data/lib/rom/memory/storage.rb +4 -7
  23. data/lib/rom/memory/types.rb +9 -0
  24. data/lib/rom/pipeline.rb +26 -12
  25. data/lib/rom/plugin_registry.rb +2 -2
  26. data/lib/rom/plugins/command/schema.rb +26 -0
  27. data/lib/rom/plugins/configuration/configuration_dsl.rb +2 -1
  28. data/lib/rom/plugins/relation/key_inference.rb +18 -3
  29. data/lib/rom/plugins/relation/registry_reader.rb +3 -1
  30. data/lib/rom/plugins/relation/view.rb +11 -6
  31. data/lib/rom/relation.rb +76 -16
  32. data/lib/rom/relation/class_interface.rb +44 -3
  33. data/lib/rom/relation/curried.rb +13 -4
  34. data/lib/rom/relation/graph.rb +15 -5
  35. data/lib/rom/relation/loaded.rb +42 -6
  36. data/lib/rom/relation/name.rb +102 -0
  37. data/lib/rom/relation_registry.rb +5 -0
  38. data/lib/rom/schema.rb +87 -0
  39. data/lib/rom/schema/dsl.rb +58 -0
  40. data/lib/rom/setup/auto_registration.rb +2 -2
  41. data/lib/rom/setup/finalize.rb +5 -5
  42. data/lib/rom/setup/finalize/{commands.rb → finalize_commands.rb} +2 -22
  43. data/lib/rom/setup/finalize/{mappers.rb → finalize_mappers.rb} +0 -0
  44. data/lib/rom/setup/finalize/finalize_relations.rb +60 -0
  45. data/lib/rom/types.rb +18 -0
  46. data/lib/rom/version.rb +1 -1
  47. data/log/.gitkeep +0 -0
  48. data/rom.gemspec +4 -2
  49. data/spec/integration/command_registry_spec.rb +13 -0
  50. data/spec/integration/commands/delete_spec.rb +0 -17
  51. data/spec/integration/commands/graph_builder_spec.rb +1 -1
  52. data/spec/integration/commands/graph_spec.rb +1 -1
  53. data/spec/integration/commands/update_spec.rb +0 -19
  54. data/spec/integration/commands_spec.rb +10 -3
  55. data/spec/integration/multi_repo_spec.rb +1 -1
  56. data/spec/integration/relations/default_dataset_spec.rb +27 -4
  57. data/spec/integration/setup_spec.rb +1 -4
  58. data/spec/shared/command_behavior.rb +17 -7
  59. data/spec/shared/container.rb +2 -2
  60. data/spec/shared/gateway_only.rb +1 -1
  61. data/spec/spec_helper.rb +5 -6
  62. data/spec/unit/rom/association_set_spec.rb +23 -0
  63. data/spec/unit/rom/auto_registration_spec.rb +1 -1
  64. data/spec/unit/rom/commands/lazy_spec.rb +8 -0
  65. data/spec/unit/rom/commands_spec.rb +45 -7
  66. data/spec/unit/rom/configurable_spec.rb +1 -1
  67. data/spec/unit/rom/container_spec.rb +6 -0
  68. data/spec/unit/rom/create_container_spec.rb +1 -1
  69. data/spec/unit/rom/environment_spec.rb +1 -1
  70. data/spec/unit/rom/memory/commands_spec.rb +43 -0
  71. data/spec/unit/rom/plugins/relation/key_inference_spec.rb +70 -12
  72. data/spec/unit/rom/plugins/relation/view_spec.rb +4 -0
  73. data/spec/unit/rom/relation/graph_spec.rb +10 -0
  74. data/spec/unit/rom/relation/lazy_spec.rb +3 -3
  75. data/spec/unit/rom/relation/loaded_spec.rb +15 -0
  76. data/spec/unit/rom/relation/name_spec.rb +51 -0
  77. data/spec/unit/rom/relation/schema_spec.rb +117 -0
  78. data/spec/unit/rom/relation_spec.rb +37 -7
  79. data/spec/unit/rom/schema_spec.rb +10 -0
  80. metadata +51 -12
  81. data/lib/rom/setup/finalize/relations.rb +0 -53
  82. data/spec/unit/rom/global_spec.rb +0 -18
  83. 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
@@ -1,4 +1,9 @@
1
1
  module ROM
2
2
  class RelationRegistry < Registry
3
+ def initialize(elements = {}, name = self.class.name)
4
+ super
5
+
6
+ yield(self, elements) if block_given?
7
+ end
3
8
  end
4
9
  end
@@ -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, Pathname(directory).join("#{name}/**/*.rb")]
19
+ [name, @directory.join("#{name}/**/*.rb")]
20
20
  }]
21
21
  end
22
22
 
@@ -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/commands'
10
- require 'rom/setup/finalize/relations'
11
- require 'rom/setup/finalize/mappers'
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
- infer_relations_relations
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 infer_relations_relations
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.send(:include, relation_methods_mod(relation.class))
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
@@ -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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module ROM
2
- VERSION = '1.0.0'.freeze
2
+ VERSION = '2.0.0'.freeze
3
3
  end
File without changes
@@ -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 'rom-support', '~> 1.0.0'
20
- gem.add_runtime_dependency 'rom-mapper', '~> 0.3.0'
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'