rom 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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'