rom 0.5.0 → 0.6.0.beta1

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