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
@@ -0,0 +1,118 @@
1
+ require 'rom/commands/abstract'
2
+
3
+ module ROM
4
+ # Base command class with factory class-level interface and setup-related logic
5
+ #
6
+ # @private
7
+ class Command < Commands::Abstract
8
+ extend ClassMacros
9
+
10
+ include Equalizer.new(:relation, :options)
11
+
12
+ defines :relation, :result, :input, :validator, :register_as
13
+
14
+ input Hash
15
+ validator proc {}
16
+ result :many
17
+
18
+ # Registers Create/Update/Delete descendant classes during the setup phase
19
+ #
20
+ # @api private
21
+ def self.inherited(klass)
22
+ super
23
+ return if klass.superclass == ROM::Command
24
+ ROM.register_command(klass)
25
+ end
26
+
27
+ # Return adapter specific sub-class based on the adapter identifier
28
+ #
29
+ # This is a syntax sugar to make things consistent
30
+ #
31
+ # @example
32
+ # ROM::Commands::Create[:memory]
33
+ # # => ROM::Memory::Commands::Create
34
+ #
35
+ # @param [Symbol] adapter identifier
36
+ #
37
+ # @return [Class]
38
+ #
39
+ # @api public
40
+ def self.[](adapter)
41
+ adapter_namespace(adapter).const_get(Inflector.demodulize(name))
42
+ end
43
+
44
+ # Return namespaces that contains command subclasses of a specific adapter
45
+ #
46
+ # @param [Symbol] adapter identifier
47
+ #
48
+ # @return [Module]
49
+ #
50
+ # @api private
51
+ def self.adapter_namespace(adapter)
52
+ ROM.adapters.fetch(adapter).const_get(:Commands)
53
+ end
54
+
55
+ # Build a command class for a specific relation with options
56
+ #
57
+ # @example
58
+ # class CreateUser < ROM::Commands::Create[:memory]
59
+ # end
60
+ #
61
+ # command = CreateUser.build(rom.relations[:users])
62
+ #
63
+ # @param [Relation] relation
64
+ # @param [Hash] options
65
+ #
66
+ # @return [Command]
67
+ #
68
+ # @api public
69
+ def self.build(relation, options = {})
70
+ new(relation, self.options.merge(options))
71
+ end
72
+
73
+ # Build command registry hash for provided relations
74
+ #
75
+ # @param [RelationRegistry] relations registry
76
+ # @param [Hash] repositories
77
+ # @param [Array] descendants a list of command subclasses
78
+ #
79
+ # @return [Hash]
80
+ #
81
+ # @api private
82
+ def self.registry(relations, repositories, descendants)
83
+ descendants.each_with_object({}) do |klass, h|
84
+ rel_name = klass.relation
85
+
86
+ next unless rel_name
87
+
88
+ relation = relations[rel_name]
89
+ name = klass.register_as || klass.default_name
90
+
91
+ repository = repositories[relation.class.repository]
92
+ repository.extend_command_class(klass, relation.dataset)
93
+
94
+ (h[rel_name] ||= {})[name] = klass.build(relation)
95
+ end
96
+ end
97
+
98
+ # Return default name of the command class based on its name
99
+ #
100
+ # During setup phase this is used by defalut as `register_as` option
101
+ #
102
+ # @return [Symbol]
103
+ #
104
+ # @api private
105
+ def self.default_name
106
+ Inflector.underscore(Inflector.demodulize(name)).to_sym
107
+ end
108
+
109
+ # Return default options based on class macros
110
+ #
111
+ # @return [Hash]
112
+ #
113
+ # @api private
114
+ def self.options
115
+ { input: input, validator: validator, result: result }
116
+ end
117
+ end
118
+ end
@@ -3,34 +3,14 @@ require 'rom/commands/result'
3
3
  module ROM
4
4
  # Command registry exposes "try" interface for executing commands
5
5
  #
6
- # @public
6
+ # @api public
7
7
  class CommandRegistry < Registry
8
- class Evaluator
9
- include Concord.new(:registry)
10
-
11
- private
12
-
13
- # Call a command when method is matching command name
14
- #
15
- # TODO: this will be replaced by explicit definition of methods for all
16
- # registered commands
17
- #
18
- # @api public
19
- def method_missing(name, *args, &block)
20
- command = registry[name]
21
-
22
- super unless command
23
-
24
- if args.size > 1
25
- command.new(*args, &block)
26
- else
27
- command.call(*args, &block)
28
- end
29
- end
30
- end
8
+ include Commands
31
9
 
32
10
  # Try to execute a command in a block
33
11
  #
12
+ # @yield [command] Passes command to the block
13
+ #
34
14
  # @example
35
15
  #
36
16
  # rom.command(:users).try { create(name: 'Jane') }
@@ -40,10 +20,16 @@ module ROM
40
20
  # @return [Commands::Result]
41
21
  #
42
22
  # @api public
43
- def try(&f)
44
- Commands::Result::Success.new(Evaluator.new(self).instance_exec(&f))
23
+ def try(&block)
24
+ response = block.call
25
+
26
+ if response.is_a?(Command) || response.is_a?(Composite)
27
+ try { response.call }
28
+ else
29
+ Result::Success.new(response)
30
+ end
45
31
  rescue CommandError => e
46
- Commands::Result::Failure.new(e)
32
+ Result::Failure.new(e)
47
33
  end
48
34
  end
49
35
  end
data/lib/rom/commands.rb CHANGED
@@ -1,62 +1,4 @@
1
- module ROM
2
- module Commands
3
- class AbstractCommand
4
- VALID_RESULTS = [:one, :many].freeze
5
-
6
- attr_reader :relation, :options, :result
7
-
8
- # @api private
9
- def initialize(relation, options)
10
- @relation = relation
11
- @options = options
12
-
13
- @result = options[:result] || :many
14
-
15
- unless VALID_RESULTS.include?(result)
16
- raise InvalidOptionError.new(:result, VALID_RESULTS)
17
- end
18
- end
19
-
20
- # Call the command and return one or many tuples
21
- #
22
- # @api public
23
- def call(*args)
24
- tuples = execute(*args)
25
-
26
- if result == :one
27
- tuples.first
28
- else
29
- tuples
30
- end
31
- end
32
-
33
- # Target relation on which the command will operate
34
- #
35
- # By default this is set to the relation that's passed to the constructor.
36
- # Specialized commands like Delete may set the target to a different
37
- # relation.
38
- #
39
- # @return [Relation]
40
- #
41
- # @api public
42
- def target
43
- relation
44
- end
45
-
46
- # Assert that tuple count in the target relation corresponds to :result
47
- # setting
48
- #
49
- # @raises TupleCountMismatchError
50
- #
51
- # @api private
52
- def assert_tuple_count
53
- if result == :one && target.size > 1
54
- raise TupleCountMismatchError, "#{inspect} expects one tuple"
55
- end
56
- end
57
- end
58
- end
59
- end
1
+ require 'rom/support/options'
60
2
 
61
3
  require 'rom/commands/create'
62
4
  require 'rom/commands/update'
@@ -0,0 +1,147 @@
1
+ require 'rom/commands/composite'
2
+
3
+ module ROM
4
+ module Commands
5
+ # Abstract command class
6
+ #
7
+ # Provides a constructor accepting relation with options and basic behavior
8
+ # for calling, currying and composing commands.
9
+ #
10
+ # Typically command subclasses should inherit from specialized
11
+ # Create/Update/Delete, not this one.
12
+ #
13
+ # @abstract
14
+ #
15
+ # @private
16
+ class Abstract
17
+ include Options
18
+
19
+ option :type, allow: [:create, :update, :delete]
20
+ option :result, reader: true, allow: [:one, :many]
21
+ option :target
22
+ option :validator, reader: true
23
+ option :input, reader: true
24
+ option :curry_args, type: Array, reader: true, default: []
25
+
26
+ attr_reader :relation
27
+
28
+ # @api private
29
+ def initialize(relation, options = {})
30
+ @relation = relation
31
+ super
32
+ end
33
+
34
+ # Execute the command
35
+ #
36
+ # @abstract
37
+ #
38
+ # @return [Array] an array with inserted tuples
39
+ #
40
+ # @api private
41
+ def execute(*)
42
+ raise(
43
+ NotImplementedError,
44
+ "#{self.class}##{__method__} must be implemented"
45
+ )
46
+ end
47
+
48
+ # Call the command and return one or many tuples
49
+ #
50
+ # @api public
51
+ def call(*args)
52
+ tuples = execute(*(args + curry_args))
53
+
54
+ if result == :one
55
+ tuples.first
56
+ else
57
+ tuples
58
+ end
59
+ end
60
+
61
+ # Curry this command with provided args
62
+ #
63
+ # Curried command can be called without args
64
+ #
65
+ # @return [Command]
66
+ #
67
+ # @api public
68
+ def curry(*args)
69
+ self.class.new(relation, options.merge(curry_args: args))
70
+ end
71
+ alias_method :with, :curry
72
+
73
+ # Compose a command with another one
74
+ #
75
+ # The other one will be called with the result from the first one
76
+ #
77
+ # @example
78
+ #
79
+ # command = users.create.curry(name: 'Jane')
80
+ # command >>= tasks.create.curry(title: 'Task One')
81
+ #
82
+ # command.call # creates user, passes it to tasks and creates task
83
+ #
84
+ # @return [Composite]
85
+ #
86
+ # @api public
87
+ def >>(other)
88
+ Composite.new(self, other)
89
+ end
90
+
91
+ # Return new update command with new relation
92
+ #
93
+ # @api private
94
+ def new(*args, &block)
95
+ self.class.build(relation.public_send(*args, &block), options)
96
+ end
97
+
98
+ # Target relation on which the command will operate
99
+ #
100
+ # By default this is set to the relation that's passed to the constructor.
101
+ # Specialized commands like Delete may set the target to a different
102
+ # relation.
103
+ #
104
+ # @return [Relation]
105
+ #
106
+ # @api public
107
+ def target
108
+ relation
109
+ end
110
+
111
+ # Assert that tuple count in the target relation corresponds to :result
112
+ # setting
113
+ #
114
+ # @raise TupleCountMismatchError
115
+ #
116
+ # @api private
117
+ def assert_tuple_count
118
+ if result == :one && tuple_count > 1
119
+ raise TupleCountMismatchError, "#{inspect} expects one tuple"
120
+ end
121
+ end
122
+
123
+ # Return number of tuples in the target relation
124
+ #
125
+ # This should be overridden by repositories when `#count` is not available
126
+ # in the relation objects
127
+ #
128
+ # @return [Fixnum]
129
+ #
130
+ # @api private
131
+ def tuple_count
132
+ target.count
133
+ end
134
+
135
+ private
136
+
137
+ # @api private
138
+ def method_missing(name, *args, &block)
139
+ if relation.respond_to?(name)
140
+ new(name, *args, &block)
141
+ else
142
+ super
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,47 @@
1
+ module ROM
2
+ module Commands
3
+ # Composite command that consists of left and right commands
4
+ #
5
+ # @api public
6
+ class Composite
7
+ include Equalizer.new(:left, :right)
8
+
9
+ # @return [Proc,Command] left command
10
+ #
11
+ # @api private
12
+ attr_reader :left
13
+
14
+ # @return [Proc,Command] right command
15
+ #
16
+ # @api private
17
+ attr_reader :right
18
+
19
+ # @api private
20
+ def initialize(left, right)
21
+ @left, @right = left, right
22
+ end
23
+
24
+ # Calls the composite command
25
+ #
26
+ # Right command is called with a result from the left one
27
+ #
28
+ # @return [Object]
29
+ #
30
+ # @api public
31
+ def call(*args)
32
+ right.call(left.call(*args))
33
+ end
34
+
35
+ # Compose another composite command from self and other
36
+ #
37
+ # @param [Proc, Command] other command
38
+ #
39
+ # @return [Composite]
40
+ #
41
+ # @api public
42
+ def >>(other)
43
+ self.class.new(self, other)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -1,4 +1,4 @@
1
- require 'rom/commands/with_options'
1
+ require 'rom/command'
2
2
 
3
3
  module ROM
4
4
  module Commands
@@ -7,22 +7,7 @@ module ROM
7
7
  # This command inserts a new tuple into a relation
8
8
  #
9
9
  # @abstract
10
- class Create < AbstractCommand
11
- include WithOptions
12
-
13
- # Execute the command
14
- #
15
- # @abstract
16
- #
17
- # @return [Array] an array with inserted tuples
18
- #
19
- # @api private
20
- def execute(_tuple)
21
- raise(
22
- NotImplementedError,
23
- "#{self.class}##{__method__} must be implemented"
24
- )
25
- end
10
+ class Create < Command
26
11
  end
27
12
  end
28
13
  end