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.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +19 -15
  3. data/.rubocop_todo.yml +28 -0
  4. data/.travis.yml +8 -1
  5. data/CHANGELOG.md +40 -0
  6. data/Gemfile +10 -2
  7. data/Guardfile +12 -10
  8. data/README.md +42 -43
  9. data/Rakefile +13 -23
  10. data/lib/rom.rb +19 -27
  11. data/lib/rom/command.rb +118 -0
  12. data/lib/rom/command_registry.rb +13 -27
  13. data/lib/rom/commands.rb +1 -59
  14. data/lib/rom/commands/abstract.rb +147 -0
  15. data/lib/rom/commands/composite.rb +47 -0
  16. data/lib/rom/commands/create.rb +2 -17
  17. data/lib/rom/commands/delete.rb +5 -25
  18. data/lib/rom/commands/result.rb +5 -5
  19. data/lib/rom/commands/update.rb +3 -27
  20. data/lib/rom/constants.rb +19 -0
  21. data/lib/rom/env.rb +85 -35
  22. data/lib/rom/global.rb +173 -42
  23. data/lib/rom/header.rb +5 -5
  24. data/lib/rom/header/attribute.rb +2 -2
  25. data/lib/rom/lint/enumerable_dataset.rb +52 -0
  26. data/lib/rom/lint/linter.rb +64 -0
  27. data/lib/rom/lint/repository.rb +78 -0
  28. data/lib/rom/lint/spec.rb +20 -0
  29. data/lib/rom/lint/test.rb +98 -0
  30. data/lib/rom/mapper.rb +32 -5
  31. data/lib/rom/mapper/attribute_dsl.rb +240 -0
  32. data/lib/rom/mapper/dsl.rb +100 -0
  33. data/lib/rom/mapper/model_dsl.rb +55 -0
  34. data/lib/rom/mapper_registry.rb +8 -1
  35. data/lib/rom/memory.rb +4 -0
  36. data/lib/rom/memory/commands.rb +46 -0
  37. data/lib/rom/memory/dataset.rb +72 -0
  38. data/lib/rom/memory/relation.rb +44 -0
  39. data/lib/rom/memory/repository.rb +62 -0
  40. data/lib/rom/memory/storage.rb +57 -0
  41. data/lib/rom/model_builder.rb +44 -5
  42. data/lib/rom/processor.rb +1 -1
  43. data/lib/rom/processor/transproc.rb +109 -16
  44. data/lib/rom/reader.rb +91 -39
  45. data/lib/rom/relation.rb +165 -26
  46. data/lib/rom/relation/composite.rb +132 -0
  47. data/lib/rom/relation/curried.rb +48 -0
  48. data/lib/rom/relation/lazy.rb +173 -0
  49. data/lib/rom/relation/loaded.rb +75 -0
  50. data/lib/rom/relation/registry_reader.rb +23 -0
  51. data/lib/rom/repository.rb +93 -34
  52. data/lib/rom/setup.rb +54 -98
  53. data/lib/rom/setup/finalize.rb +85 -76
  54. data/lib/rom/setup_dsl/command.rb +36 -0
  55. data/lib/rom/setup_dsl/command_dsl.rb +34 -0
  56. data/lib/rom/setup_dsl/mapper.rb +32 -0
  57. data/lib/rom/setup_dsl/mapper_dsl.rb +30 -0
  58. data/lib/rom/setup_dsl/relation.rb +21 -0
  59. data/lib/rom/setup_dsl/setup.rb +75 -0
  60. data/lib/rom/support/array_dataset.rb +38 -0
  61. data/lib/rom/support/class_builder.rb +44 -0
  62. data/lib/rom/support/class_macros.rb +56 -0
  63. data/lib/rom/support/data_proxy.rb +102 -0
  64. data/lib/rom/support/enumerable_dataset.rb +58 -0
  65. data/lib/rom/support/inflector.rb +73 -0
  66. data/lib/rom/support/options.rb +188 -0
  67. data/lib/rom/support/registry.rb +4 -8
  68. data/lib/rom/version.rb +1 -1
  69. data/rakelib/benchmark.rake +13 -0
  70. data/rakelib/mutant.rake +16 -0
  71. data/rakelib/rubocop.rake +18 -0
  72. data/rom.gemspec +4 -7
  73. data/spec/integration/commands/create_spec.rb +32 -24
  74. data/spec/integration/commands/delete_spec.rb +15 -7
  75. data/spec/integration/commands/update_spec.rb +13 -11
  76. data/spec/integration/mappers/deep_embedded_spec.rb +4 -11
  77. data/spec/integration/mappers/definition_dsl_spec.rb +31 -44
  78. data/spec/integration/mappers/embedded_spec.rb +9 -24
  79. data/spec/integration/mappers/group_spec.rb +22 -30
  80. data/spec/integration/mappers/prefixing_attributes_spec.rb +18 -23
  81. data/spec/integration/mappers/renaming_attributes_spec.rb +23 -38
  82. data/spec/integration/mappers/symbolizing_attributes_spec.rb +18 -24
  83. data/spec/integration/mappers/wrap_spec.rb +22 -30
  84. data/spec/integration/multi_repo_spec.rb +15 -37
  85. data/spec/integration/relations/reading_spec.rb +82 -14
  86. data/spec/integration/repositories/extending_relations_spec.rb +50 -0
  87. data/spec/integration/{adapters → repositories}/setting_logger_spec.rb +6 -5
  88. data/spec/integration/setup_spec.rb +59 -62
  89. data/spec/shared/enumerable_dataset.rb +49 -0
  90. data/spec/shared/one_behavior.rb +26 -0
  91. data/spec/shared/users_and_tasks.rb +11 -23
  92. data/spec/spec_helper.rb +16 -7
  93. data/spec/support/constant_leak_finder.rb +14 -0
  94. data/spec/test/memory_repository_lint_test.rb +27 -0
  95. data/spec/unit/rom/command_registry_spec.rb +44 -0
  96. data/spec/unit/rom/commands/result_spec.rb +14 -0
  97. data/spec/unit/rom/commands_spec.rb +174 -0
  98. data/spec/unit/rom/env_spec.rb +40 -7
  99. data/spec/unit/rom/global_spec.rb +14 -0
  100. data/spec/unit/rom/{mapper_builder_spec.rb → mapper/dsl_spec.rb} +52 -38
  101. data/spec/unit/rom/mapper_spec.rb +51 -10
  102. data/spec/unit/rom/{adapter/memory → memory}/dataset_spec.rb +6 -4
  103. data/spec/unit/rom/memory/repository_spec.rb +12 -0
  104. data/spec/unit/rom/memory/storage_spec.rb +45 -0
  105. data/spec/unit/rom/model_builder_spec.rb +4 -3
  106. data/spec/unit/rom/processor/transproc_spec.rb +1 -0
  107. data/spec/unit/rom/reader_spec.rb +97 -24
  108. data/spec/unit/rom/relation/composite_spec.rb +65 -0
  109. data/spec/unit/rom/relation/lazy_spec.rb +145 -0
  110. data/spec/unit/rom/relation/loaded_spec.rb +28 -0
  111. data/spec/unit/rom/relation_spec.rb +111 -6
  112. data/spec/unit/rom/repository_spec.rb +59 -9
  113. data/spec/unit/rom/setup_spec.rb +99 -11
  114. data/spec/unit/rom/support/array_dataset_spec.rb +59 -0
  115. data/spec/unit/rom/support/class_builder_spec.rb +42 -0
  116. data/spec/unit/rom/support/enumerable_dataset_spec.rb +17 -0
  117. data/spec/unit/rom/support/inflector_spec.rb +89 -0
  118. data/spec/unit/rom/support/options_spec.rb +119 -0
  119. metadata +74 -112
  120. data/lib/rom/adapter.rb +0 -191
  121. data/lib/rom/adapter/memory.rb +0 -32
  122. data/lib/rom/adapter/memory/commands.rb +0 -31
  123. data/lib/rom/adapter/memory/dataset.rb +0 -67
  124. data/lib/rom/adapter/memory/storage.rb +0 -26
  125. data/lib/rom/commands/with_options.rb +0 -18
  126. data/lib/rom/config.rb +0 -70
  127. data/lib/rom/mapper_builder.rb +0 -52
  128. data/lib/rom/mapper_builder/mapper_dsl.rb +0 -114
  129. data/lib/rom/mapper_builder/model_dsl.rb +0 -29
  130. data/lib/rom/reader_builder.rb +0 -48
  131. data/lib/rom/relation_builder.rb +0 -62
  132. data/lib/rom/setup/base_relation_dsl.rb +0 -46
  133. data/lib/rom/setup/command_dsl.rb +0 -46
  134. data/lib/rom/setup/mapper_dsl.rb +0 -19
  135. data/lib/rom/setup/relation_dsl.rb +0 -20
  136. data/lib/rom/setup/schema_dsl.rb +0 -33
  137. data/spec/integration/adapters/extending_relations_spec.rb +0 -41
  138. data/spec/integration/commands/try_spec.rb +0 -27
  139. data/spec/integration/schema_spec.rb +0 -77
  140. data/spec/unit/config_spec.rb +0 -60
  141. data/spec/unit/rom/adapter_spec.rb +0 -79
  142. data/spec/unit/rom_spec.rb +0 -14
@@ -1,125 +1,134 @@
1
- require 'rom/relation_builder'
2
- require 'rom/reader_builder'
1
+ require 'rom/relation'
2
+ require 'rom/mapper'
3
+ require 'rom/reader'
4
+ require 'rom/command'
5
+
6
+ require 'rom/support/registry'
3
7
  require 'rom/command_registry'
8
+ require 'rom/mapper_registry'
4
9
 
5
10
  require 'rom/env'
6
11
 
7
12
  module ROM
8
13
  class Setup
14
+ # This giant builds an environment using defined classes for core parts of ROM
15
+ #
16
+ # It is used by the setup object after it's done gathering class definitions
17
+ #
9
18
  # @private
10
19
  class Finalize
11
- attr_reader :repositories, :adapter_relation_map
20
+ attr_reader :repositories, :repo_adapter, :datasets,
21
+ :relation_classes, :mapper_classes, :command_classes
12
22
 
13
23
  # @api private
14
- def initialize(repositories, schema, relations, mappers, commands)
24
+ def initialize(repositories, relation_classes, mapper_classes, command_classes)
15
25
  @repositories = repositories
16
- @schema = schema
17
- @relations = relations
18
- @mappers = mappers
19
- @commands = commands
20
- @adapter_relation_map = {}
26
+ @repo_adapter_map = ROM.repositories
27
+ @relation_classes = relation_classes
28
+ @mapper_classes = mapper_classes
29
+ @command_classes = command_classes
30
+ initialize_datasets
21
31
  end
22
32
 
33
+ # Return adapter identifier for a given repository object
34
+ #
35
+ # @return [Symbol]
36
+ #
37
+ # @api private
38
+ def adapter_for(repository)
39
+ @repo_adapter_map.fetch(repositories[repository])
40
+ end
41
+
42
+ # Run the finalization process
43
+ #
44
+ # This creates relations, mappers and commands
45
+ #
46
+ # @return [Env]
47
+ #
23
48
  # @api private
24
49
  def run!
25
- schema = load_schema
26
- relations = load_relations(schema)
50
+ infer_schema_relations
51
+
52
+ relations = load_relations
27
53
  readers = load_readers(relations)
28
54
  commands = load_commands(relations)
29
55
 
30
- Env.new(repositories, schema, relations, readers, commands)
56
+ Env.new(repositories, relations, readers, commands)
31
57
  end
32
58
 
33
59
  private
34
60
 
61
+ # Infer all datasets using configured repositories
62
+ #
63
+ # Not all repositories can do that, by default an empty array is returned
64
+ #
65
+ # @return [Hash] repository name => array with datasets map
66
+ #
35
67
  # @api private
36
- def load_schema
37
- repositories.each_value do |repo|
38
- (@schema[repo] ||= []).concat(repo.schema)
68
+ def initialize_datasets
69
+ @datasets = repositories.each_with_object({}) do |(key, repository), h|
70
+ h[key] = repository.schema
39
71
  end
40
-
41
- base_relations = @schema.each_with_object({}) do |(repo, schema), h|
42
- schema.each do |name, dataset, header|
43
- adapter_relation_map[name] = repo.adapter
44
- h[name] = Relation.new(dataset, header)
45
- end
46
- end
47
-
48
- Schema.new(base_relations)
49
72
  end
50
73
 
74
+ # Build entire relation registry from all known relation subclasses
75
+ #
76
+ # This includes both classes created via DSL and explicit definitions
77
+ #
51
78
  # @api private
52
- def load_relations(schema)
53
- return RelationRegistry.new unless adapter_relation_map.any?
54
-
55
- relations = {}
56
- builder = RelationBuilder.new(schema, relations)
57
-
58
- @relations.each do |name, block|
59
- relations[name] = build_relation(name, builder, block)
60
- end
61
-
62
- (schema.elements.keys - relations.keys).each do |name|
63
- relations[name] = build_relation(name, builder)
64
- end
65
-
66
- relations.each_value do |relation|
67
- relation.class.finalize(relations, relation)
68
- end
69
-
79
+ def load_relations
80
+ relations = Relation.registry(repositories, relation_classes)
70
81
  RelationRegistry.new(relations)
71
82
  end
72
83
 
73
- # @api private
74
- def build_relation(name, builder, block = nil)
75
- adapter = adapter_relation_map[name]
76
-
77
- relation = builder.call(name) do |klass|
78
- adapter.extend_relation_class(klass)
79
- methods = klass.public_instance_methods
80
-
81
- klass.class_eval(&block) if block
82
-
83
- klass.relation_methods = klass.public_instance_methods - methods
84
- end
85
-
86
- adapter.extend_relation_instance(relation)
87
-
88
- relation
89
- end
90
-
84
+ # Build entire reader and mapper registries
85
+ #
91
86
  # @api private
92
87
  def load_readers(relations)
93
- return ReaderRegistry.new unless adapter_relation_map.any?
88
+ readers = {}
94
89
 
95
- reader_builder = ReaderBuilder.new(relations)
90
+ Mapper.registry(mapper_classes).each do |name, mappers|
91
+ relation = relations[name]
92
+ methods = relation.exposed_relations
96
93
 
97
- readers = @mappers.each_with_object({}) do |(name, options, block), h|
98
- h[name] = reader_builder.call(name, options, &block)
94
+ readers[name] = Reader.build(
95
+ name, relation, MapperRegistry.new(mappers), methods
96
+ )
99
97
  end
100
98
 
101
99
  ReaderRegistry.new(readers)
102
100
  end
103
101
 
102
+ # Build entire command registries
103
+ #
104
+ # This includes both classes created via DSL and explicit definitions
105
+ #
106
+ # @api private
104
107
  def load_commands(relations)
105
- return Registry.new unless relations.elements.any?
106
-
107
- commands = @commands.each_with_object({}) do |(name, definitions), h|
108
- adapter = adapter_relation_map[name]
109
-
110
- rel_commands = {}
111
-
112
- definitions.each do |command_name, definition|
113
- rel_commands[command_name] = adapter.command(
114
- command_name, relations[name], definition
115
- )
116
- end
108
+ registry = Command.registry(relations, repositories, command_classes)
117
109
 
110
+ commands = registry.each_with_object({}) do |(name, rel_commands), h|
118
111
  h[name] = CommandRegistry.new(rel_commands)
119
112
  end
120
113
 
121
114
  Registry.new(commands)
122
115
  end
116
+
117
+ # For every dataset infered from repositories we infer a relation
118
+ #
119
+ # Relations explicitly defined are being skipped
120
+ #
121
+ # @api private
122
+ def infer_schema_relations
123
+ datasets.each do |repository, schema|
124
+ schema.each do |name|
125
+ next if relation_classes.any? { |klass| klass.dataset == name }
126
+ klass = Relation.build_class(name, adapter: adapter_for(repository))
127
+ klass.repository(repository)
128
+ klass.dataset(name)
129
+ end
130
+ end
131
+ end
123
132
  end
124
133
  end
125
134
  end
@@ -0,0 +1,36 @@
1
+ module ROM
2
+ # Setup DSL-specific command extensions
3
+ #
4
+ # @private
5
+ class Command
6
+ # Generate a command subclass
7
+ #
8
+ # This is used by Setup#commands DSL and its `define` block
9
+ #
10
+ # @api private
11
+ def self.build_class(name, relation, options = {}, &block)
12
+ type = options.fetch(:type) { name }
13
+ command_type = Inflector.classify(type)
14
+ adapter = options.fetch(:adapter)
15
+ parent = adapter_namespace(adapter).const_get(command_type)
16
+ class_name = generate_class_name(adapter, command_type, relation)
17
+
18
+ ClassBuilder.new(name: class_name, parent: parent).call do |klass|
19
+ klass.register_as(name)
20
+ klass.relation(relation)
21
+ klass.class_eval(&block) if block
22
+ end
23
+ end
24
+
25
+ # Create a command subclass name based on adapter, type and relation
26
+ #
27
+ # @api private
28
+ def self.generate_class_name(adapter, command_type, relation)
29
+ pieces = ['ROM']
30
+ pieces << Inflector.classify(adapter)
31
+ pieces << 'Commands'
32
+ pieces << "#{command_type}[#{Inflector.classify(relation)}s]"
33
+ pieces.join('::')
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,34 @@
1
+ require 'rom/setup_dsl/command'
2
+
3
+ module ROM
4
+ class Setup
5
+ # Command `define` DSL used by Setup#commands
6
+ #
7
+ # @private
8
+ class CommandDSL
9
+ attr_reader :relation, :adapter
10
+
11
+ # @api private
12
+ def initialize(relation, adapter = nil, &block)
13
+ @relation = relation
14
+ @adapter = adapter
15
+ instance_exec(&block)
16
+ end
17
+
18
+ # Define a command class
19
+ #
20
+ # @param [Symbol] name of the command
21
+ # @param [Hash] options
22
+ # @option options [Symbol] :type The type of the command
23
+ #
24
+ # @return [Class] generated class
25
+ #
26
+ # @api public
27
+ def define(name, options = {}, &block)
28
+ Command.build_class(
29
+ name, relation, { adapter: adapter }.merge(options), &block
30
+ )
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,32 @@
1
+ module ROM
2
+ # Setup DSL-specific mapper extensions
3
+ #
4
+ # @private
5
+ class Mapper
6
+ # Generate a mapper subclass
7
+ #
8
+ # This is used by Setup#mappers DSL
9
+ #
10
+ # @api private
11
+ def self.build_class(name, options = {}, &block)
12
+ class_name = "ROM::Mapper[#{name}]"
13
+
14
+ parent = options[:parent]
15
+ inherit_header = options.fetch(:inherit_header) { Mapper.inherit_header }
16
+
17
+ parent_class =
18
+ if parent
19
+ ROM.boot.mapper_classes.detect { |klass| klass.relation == parent }
20
+ else
21
+ self
22
+ end
23
+
24
+ ClassBuilder.new(name: class_name, parent: parent_class).call do |klass|
25
+ klass.relation(name)
26
+ klass.inherit_header(inherit_header)
27
+
28
+ klass.class_eval(&block) if block
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,30 @@
1
+ require 'rom/setup_dsl/mapper'
2
+
3
+ module ROM
4
+ class Setup
5
+ # Mapper definition DSL used by Setup DSL
6
+ #
7
+ # @private
8
+ class MapperDSL
9
+ attr_reader :mappers
10
+
11
+ # @api private
12
+ def initialize(&block)
13
+ instance_exec(&block)
14
+ end
15
+
16
+ # Define a mapper class
17
+ #
18
+ # @param [Symbol] name of the mapper
19
+ # @param [Hash] options
20
+ #
21
+ # @return [Class]
22
+ #
23
+ # @api public
24
+ def define(name, options = {}, &block)
25
+ Mapper.build_class(name, options, &block)
26
+ self
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,21 @@
1
+ module ROM
2
+ # Setup DSL-specific relation extensions
3
+ #
4
+ # @private
5
+ class Relation
6
+ # Generate a relation subclass
7
+ #
8
+ # This is used by Setup#relation DSL
9
+ #
10
+ # @api private
11
+ def self.build_class(name, options = {})
12
+ class_name = "ROM::Relation[#{Inflector.camelize(name)}]"
13
+ adapter = options.fetch(:adapter)
14
+
15
+ ClassBuilder.new(name: class_name, parent: self[adapter]).call do |klass|
16
+ klass.repository(options.fetch(:repository) { :default })
17
+ klass.dataset(name)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,75 @@
1
+ require 'rom/setup_dsl/relation'
2
+
3
+ require 'rom/setup_dsl/mapper_dsl'
4
+ require 'rom/setup_dsl/command_dsl'
5
+
6
+ module ROM
7
+ # This extends Setup class with the DSL methods
8
+ #
9
+ # @api public
10
+ class Setup
11
+ # Relation definition DSL
12
+ #
13
+ # @example
14
+ #
15
+ # setup.relation(:users) do
16
+ # def names
17
+ # project(:name)
18
+ # end
19
+ # end
20
+ #
21
+ # @api public
22
+ def relation(name, options = {}, &block)
23
+ klass_opts = { adapter: default_adapter }.merge(options)
24
+ klass = Relation.build_class(name, klass_opts)
25
+ klass.class_eval(&block) if block
26
+ klass
27
+ end
28
+
29
+ # Mapper definition DSL
30
+ #
31
+ # @example
32
+ #
33
+ # setup.mappers do
34
+ # define(:users) do
35
+ # model name: 'User'
36
+ # end
37
+ #
38
+ # define(:names, parent: :users) do
39
+ # exclude :id
40
+ # end
41
+ # end
42
+ #
43
+ # @api public
44
+ def mappers(&block)
45
+ MapperDSL.new(&block)
46
+ end
47
+
48
+ # Command definition DSL
49
+ #
50
+ # @example
51
+ #
52
+ # setup.commands(:users) do
53
+ # define(:create) do
54
+ # input NewUserParams
55
+ # validator NewUserValidator
56
+ # result :one
57
+ # end
58
+ #
59
+ # define(:update) do
60
+ # input UserParams
61
+ # validator UserValidator
62
+ # result :many
63
+ # end
64
+ #
65
+ # define(:delete) do
66
+ # result :many
67
+ # end
68
+ # end
69
+ #
70
+ # @api public
71
+ def commands(name, &block)
72
+ CommandDSL.new(name, default_adapter, &block)
73
+ end
74
+ end
75
+ end