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,38 @@
1
+ require 'rom/support/enumerable_dataset'
2
+
3
+ module ROM
4
+ # A helper module that adds data-proxy behavior to an array-like object
5
+ #
6
+ # @see EnumerableDataset
7
+ #
8
+ # @api public
9
+ module ArrayDataset
10
+ extend DataProxy::ClassMethods
11
+ include EnumerableDataset
12
+
13
+ # Extends the class with data-proxy behavior
14
+ #
15
+ # @api private
16
+ def self.included(klass)
17
+ klass.send(:include, DataProxy)
18
+ end
19
+
20
+ forward(
21
+ :*, :+, :-, :compact, :compact!, :flatten, :flatten!, :length, :pop,
22
+ :reverse, :reverse!, :sample, :select!, :size, :shift, :shuffle, :shuffle!,
23
+ :slice, :slice!, :sort!, :sort_by!, :uniq, :uniq!, :unshift, :values_at
24
+ )
25
+
26
+ [
27
+ :map!, :combination, :cycle, :delete_if, :keep_if, :permutation, :reject!,
28
+ :select!, :sort_by!
29
+ ].each do |method|
30
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
31
+ def #{method}(*args, &block)
32
+ return to_enum unless block
33
+ self.class.new(data.send(:#{method}, *args, &block))
34
+ end
35
+ RUBY
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,44 @@
1
+ module ROM
2
+ # Internal support class for generating classes
3
+ #
4
+ # @private
5
+ class ClassBuilder
6
+ include Options
7
+
8
+ option :name, type: String, reader: true
9
+ option :parent, type: Class, reader: true, parent: Object
10
+
11
+ # Generate a class based on options
12
+ #
13
+ # @example
14
+ # builder = ROM::ClasBuilder.new(name: 'MyClass')
15
+ #
16
+ # klass = builder.call
17
+ # klass.name # => "MyClass"
18
+ #
19
+ # @return [Class]
20
+ #
21
+ # @api private
22
+ def call
23
+ klass = Class.new(parent)
24
+
25
+ klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
26
+ def self.name
27
+ #{name.inspect}
28
+ end
29
+
30
+ def self.inspect
31
+ name
32
+ end
33
+
34
+ def self.to_s
35
+ name
36
+ end
37
+ RUBY
38
+
39
+ yield(klass) if block_given?
40
+
41
+ klass
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,56 @@
1
+ module ROM
2
+ # Internal support module for class-level settings
3
+ #
4
+ # @private
5
+ module ClassMacros
6
+ # Specify what macros a class will use
7
+ #
8
+ # @example
9
+ # class MyClass
10
+ # extend ROM::ClassMacros
11
+ #
12
+ # defines :one, :two
13
+ #
14
+ # one 1
15
+ # two 2
16
+ # end
17
+ #
18
+ # class OtherClass < MyClass
19
+ # two 'two'
20
+ # end
21
+ #
22
+ # MyClass.one # => 1
23
+ # MyClass.two # => 2
24
+ #
25
+ # OtherClass.one # => 1
26
+ # OtherClass.two # => 'two'
27
+ #
28
+ # @api private
29
+ def defines(*args)
30
+ mod = Module.new
31
+
32
+ args.each do |name|
33
+ mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
34
+ def #{name}(value = Undefined)
35
+ if value == Undefined
36
+ defined?(@#{name}) && @#{name}
37
+ else
38
+ @#{name} = value
39
+ end
40
+ end
41
+ RUBY
42
+ end
43
+
44
+ delegates = args.map { |name| "klass.#{name}(#{name})" }.join("\n")
45
+
46
+ mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
47
+ def inherited(klass)
48
+ super
49
+ #{delegates}
50
+ end
51
+ RUBY
52
+
53
+ extend(mod)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,102 @@
1
+ require 'equalizer'
2
+
3
+ module ROM
4
+ # Helper module for dataset classes
5
+ #
6
+ # It provides a constructor accepting data, header and an optional row_proc.
7
+ # This module is used internally by EnumerableDataset and ArrayDataset.
8
+ #
9
+ # @private
10
+ module DataProxy
11
+ NON_FORWARDABLE = [
12
+ :each, :to_a, :to_ary, :kind_of?, :instance_of?, :is_a?
13
+ ].freeze
14
+
15
+ # @return [Object] Data object for the iterator
16
+ #
17
+ # @api private
18
+ attr_reader :data
19
+
20
+ # @return [Proc] tuple processing proc
21
+ #
22
+ # @api private
23
+ attr_reader :row_proc
24
+
25
+ # Extends the class with `forward` DSL and Equalizer using `data` attribute
26
+ #
27
+ # @see ClassMethods#forward
28
+ #
29
+ # @api private
30
+ def self.included(klass)
31
+ klass.class_eval do
32
+ extend ClassMethods
33
+ include Equalizer.new(:data)
34
+ end
35
+ end
36
+
37
+ # Constructor for dataset objects
38
+ #
39
+ # @param [Object] data
40
+ # @param [Proc] row_proc processing proc
41
+ #
42
+ # @api private
43
+ def initialize(data, row_proc = self.class.row_proc)
44
+ @data = data
45
+ @row_proc = row_proc
46
+ end
47
+
48
+ # Iterate over data using row_proc
49
+ #
50
+ # @return [Enumerator] if block is not given
51
+ #
52
+ # @api private
53
+ def each
54
+ return to_enum unless block_given?
55
+ data.each { |tuple| yield(row_proc[tuple]) }
56
+ end
57
+
58
+ module ClassMethods
59
+ # Default no-op tuple proc
60
+ #
61
+ # @return [Proc]
62
+ #
63
+ # @api private
64
+ def row_proc
65
+ -> tuple { tuple }
66
+ end
67
+
68
+ # Forward provided methods to the underlaying data object
69
+ #
70
+ # @example
71
+ #
72
+ # class MyDataset
73
+ # include DataProxy
74
+ #
75
+ # forward(:find_all, :map)
76
+ # end
77
+ #
78
+ # @return [undefined]
79
+ #
80
+ # @api public
81
+ def forward(*methods)
82
+ # FIXME: we should probably raise if one of the non-forwardable methods
83
+ # was provided
84
+ (methods - NON_FORWARDABLE).each do |method_name|
85
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
86
+ def #{method_name}(*args, &block)
87
+ response = data.public_send(#{method_name.inspect}, *args, &block)
88
+
89
+ if response.equal?(data)
90
+ self
91
+ elsif response.is_a?(data.class)
92
+ self.class.new(response)
93
+ else
94
+ response
95
+ end
96
+ end
97
+ RUBY
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,58 @@
1
+ require 'rom/support/data_proxy'
2
+
3
+ module ROM
4
+ # A helper module that adds data-proxy behavior to an enumerable object
5
+ #
6
+ # This module is intended to be used by repositories
7
+ #
8
+ # Class that includes this module can define `row_proc` class method which
9
+ # must return a proc-like object which will be used to process each element
10
+ # in the enumerable
11
+ #
12
+ # @example
13
+ # class MyDataset
14
+ # include ROM::EnumerableDataset
15
+ #
16
+ # def self.row_proc
17
+ # -> tuple { tuple.each_with_object({}) { |(k,v), h| h[k.to_sym] = v } }
18
+ # end
19
+ # end
20
+ #
21
+ # ds = MyDataset.new([{ 'name' => 'Jane' }, [:name])
22
+ # ds.to_a # => { :name => 'Jane' }
23
+ #
24
+ # @api public
25
+ module EnumerableDataset
26
+ include Enumerable
27
+
28
+ # Coerce a dataset to an array
29
+ #
30
+ # @return [Array]
31
+ #
32
+ # @api public
33
+ alias_method :to_ary, :to_a
34
+
35
+ # Included hook which extends a class with DataProxy behavior
36
+ #
37
+ # This module can also be included into other modules so we apply the
38
+ # extension only for classes
39
+ #
40
+ # @api private
41
+ def self.included(klass)
42
+ return unless klass.is_a?(Class)
43
+ klass.send(:include, DataProxy)
44
+ end
45
+
46
+ [
47
+ :chunk, :collect, :collect_concat, :drop_while, :find_all, :flat_map,
48
+ :grep, :map, :reject, :select, :sort, :sort_by, :take, :take_while
49
+ ].each do |method|
50
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
51
+ def #{method}(*args, &block)
52
+ return to_enum unless block
53
+ self.class.new(super(*args, &block))
54
+ end
55
+ RUBY
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,73 @@
1
+ module ROM
2
+ # Helper module providing thin interface around an inflection backend.
3
+ #
4
+ # @private
5
+ module Inflector
6
+ BACKENDS = {
7
+ activesupport: [
8
+ 'active_support/inflector',
9
+ proc { ::ActiveSupport::Inflector }
10
+ ],
11
+ inflecto: [
12
+ 'inflecto',
13
+ proc { ::Inflecto }
14
+ ]
15
+ }.freeze
16
+
17
+ def self.realize_backend(path, inflector_backend_factory)
18
+ require path
19
+ inflector_backend_factory.call
20
+ rescue LoadError
21
+ nil
22
+ end
23
+
24
+ def self.detect_backend
25
+ BACKENDS.find do |_, (path, inflector_class)|
26
+ backend = realize_backend(path, inflector_class)
27
+ break backend if backend
28
+ end ||
29
+ raise(LoadError,
30
+ "No inflector library could be found: "\
31
+ "please install either the `inflecto` or `activesupport` gem.")
32
+ end
33
+
34
+ def self.select_backend(name = nil)
35
+ if name && !BACKENDS.key?(name)
36
+ raise NameError, "Invalid inflector library selection: '#{name}'"
37
+ end
38
+ @inflector = name ? realize_backend(*BACKENDS[name]) : detect_backend
39
+ end
40
+
41
+ def self.inflector
42
+ @inflector || select_backend
43
+ end
44
+
45
+ def self.camelize(input)
46
+ inflector.camelize(input)
47
+ end
48
+
49
+ def self.underscore(input)
50
+ inflector.underscore(input)
51
+ end
52
+
53
+ def self.singularize(input)
54
+ inflector.singularize(input)
55
+ end
56
+
57
+ def self.pluralize(input)
58
+ inflector.pluralize(input)
59
+ end
60
+
61
+ def self.demodulize(input)
62
+ inflector.demodulize(input)
63
+ end
64
+
65
+ def self.constantize(input)
66
+ inflector.constantize(input)
67
+ end
68
+
69
+ def self.classify(input)
70
+ inflector.classify(input)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,188 @@
1
+ module ROM
2
+ # Helper module for classes with a constructor accepting option hash
3
+ #
4
+ # This allows us to DRY up code as option hash is a very common pattern used
5
+ # across the codebase. It is an internal implementation detail not meant to
6
+ # be used outside of ROM
7
+ #
8
+ # @example
9
+ # class User
10
+ # include Options
11
+ #
12
+ # option :name, type: String, reader: true
13
+ # option :admin, allow: [true, false], reader: true, default: false
14
+ #
15
+ # def initialize(options={})
16
+ # super
17
+ # end
18
+ # end
19
+ #
20
+ # user = User.new(name: 'Piotr')
21
+ # user.name # => "Piotr"
22
+ # user.admin # => false
23
+ #
24
+ # @api public
25
+ module Options
26
+ # @return [Hash<Option>] Option definitions
27
+ #
28
+ # @api public
29
+ attr_reader :options
30
+
31
+ def self.included(klass)
32
+ klass.extend ClassMethods
33
+ klass.option_definitions = Definitions.new
34
+ end
35
+
36
+ # Defines a single option
37
+ #
38
+ # @api private
39
+ class Option
40
+ attr_reader :name, :type, :allow, :default
41
+
42
+ def initialize(name, options = {})
43
+ @name = name
44
+ @type = options.fetch(:type) { Object }
45
+ @reader = options.fetch(:reader) { false }
46
+ @allow = options.fetch(:allow) { [] }
47
+ @default = options.fetch(:default) { Undefined }
48
+ end
49
+
50
+ def reader?
51
+ @reader
52
+ end
53
+
54
+ def default?
55
+ @default != Undefined
56
+ end
57
+
58
+ def default_value(object)
59
+ default.is_a?(Proc) ? default.call(object) : default
60
+ end
61
+
62
+ def type_matches?(value)
63
+ value.is_a?(type)
64
+ end
65
+
66
+ def allow?(value)
67
+ allow.none? || allow.include?(value)
68
+ end
69
+ end
70
+
71
+ # Manage all available options
72
+ #
73
+ # @api private
74
+ class Definitions
75
+ def initialize
76
+ @options = {}
77
+ end
78
+
79
+ def initialize_copy(source)
80
+ super
81
+ @options = @options.dup
82
+ end
83
+
84
+ def define(option)
85
+ @options[option.name] = option
86
+ end
87
+
88
+ def validate_options(options)
89
+ options.each do |name, value|
90
+ validate_option_value(name, value)
91
+ end
92
+ end
93
+
94
+ def set_defaults(object, options)
95
+ each do |name, option|
96
+ next unless option.default? && !options.key?(name)
97
+ options[name] = option.default_value(object)
98
+ end
99
+ end
100
+
101
+ def set_option_values(object, options)
102
+ each do |name, option|
103
+ object.instance_variable_set("@#{name}", options[name]) if option.reader?
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ def each(&block)
110
+ @options.each(&block)
111
+ end
112
+
113
+ def validate_option_value(name, value)
114
+ option = @options.fetch(name) do
115
+ raise InvalidOptionKeyError,
116
+ "#{name.inspect} is not a valid option"
117
+ end
118
+
119
+ unless option.type_matches?(value)
120
+ raise InvalidOptionValueError,
121
+ "#{name.inspect}:#{value.inspect} has incorrect type"
122
+ end
123
+
124
+ unless option.allow?(value)
125
+ raise InvalidOptionValueError,
126
+ "#{name.inspect}:#{value.inspect} has incorrect value"
127
+ end
128
+ end
129
+ end
130
+
131
+ # @api private
132
+ module ClassMethods
133
+ # Available options
134
+ #
135
+ # @return [Definitions]
136
+ #
137
+ # @api private
138
+ attr_accessor :option_definitions
139
+
140
+ # Defines an option
141
+ #
142
+ # @param [Symbol] name option name
143
+ #
144
+ # @param [Hash] settings option settings
145
+ # @option settings [Class] :type Restrict option type. Default: +Object+
146
+ # @option settings [Boolean] :reader Define a reader? Default: +false+
147
+ # @option settings [Array] :allow Allow certain values. Default: Allow anything
148
+ # @option settings [Object] :default Set default value for missing option
149
+ #
150
+ # @api public
151
+ def option(name, settings = {})
152
+ option = Option.new(name, settings)
153
+ option_definitions.define(option)
154
+ attr_reader(name) if option.reader?
155
+ end
156
+
157
+ # @api private
158
+ def inherited(descendant)
159
+ descendant.option_definitions = option_definitions.dup
160
+ super
161
+ end
162
+ end
163
+
164
+ # Initialize options provided as optional last argument hash
165
+ #
166
+ # @example
167
+ # class Commands
168
+ # include Options
169
+ #
170
+ # # ...
171
+ #
172
+ # def initialize(relations, options={})
173
+ # @relation = relation
174
+ # super
175
+ # end
176
+ # end
177
+ #
178
+ # @param [Array] args
179
+ def initialize(*args)
180
+ options = args.last ? args.last.dup : {}
181
+ definitions = self.class.option_definitions
182
+ definitions.set_defaults(self, options)
183
+ definitions.validate_options(options)
184
+ definitions.set_option_values(self, options)
185
+ @options = options.freeze
186
+ end
187
+ end
188
+ end