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
@@ -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