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
data/lib/rom/relation.rb CHANGED
@@ -1,50 +1,163 @@
1
+ require 'set'
2
+ require 'rom/relation/registry_reader'
3
+ require 'rom/relation/lazy'
4
+ require 'rom/relation/curried'
5
+
1
6
  module ROM
2
7
  # Base relation class
3
8
  #
4
- # Relation is a proxy for the dataset object provided by the adapter, it
5
- # forwards every method to the dataset that's why "native" interface of the
6
- # underlying adapter is available in the relation. This interface, however, is
7
- # considered private and should not be used outside of the relation instance.
9
+ # Relation is a proxy for the dataset object provided by the repository. It
10
+ # forwards every method to the dataset, which is why the "native" interface of
11
+ # the underlying repository is available in the relation. This interface,
12
+ # however, is considered private and should not be used outside of the
13
+ # relation instance.
8
14
  #
9
15
  # ROM builds sub-classes of this class for every relation defined in the env
10
- # for easy inspection and extensibility - every adapter can provide extensions
16
+ # for easy inspection and extensibility - every repository can provide extensions
11
17
  # for those sub-classes but there is always a vanilla relation instance stored
12
18
  # in the schema registry.
13
19
  #
14
- # Relation instances also have access to the experimental ROM::RA interface
15
- # giving in-memory relational operations that are very handy, especially when
16
- # dealing with joined relations or data coming from different sources.
17
- #
18
20
  # @api public
19
21
  class Relation
20
- include Charlatan.new(:dataset)
21
- include Equalizer.new(:header, :dataset)
22
+ extend ClassMacros
23
+
24
+ include Options
25
+ include Equalizer.new(:dataset)
26
+
27
+ defines :repository, :dataset, :register_as, :exposed_relations
28
+
29
+ repository :default
30
+
31
+ attr_reader :name, :dataset, :exposed_relations
32
+
33
+ # Register adapter relation subclasses during setup phase
34
+ #
35
+ # In adition those subclasses are extended with an interface for accessing
36
+ # relation registry and to define `register_as` setting
37
+ #
38
+ # @api private
39
+ def self.inherited(klass)
40
+ super
41
+
42
+ return if self == ROM::Relation
43
+
44
+ klass.class_eval do
45
+ include ROM::Relation::RegistryReader
46
+
47
+ dataset(default_name)
48
+ exposed_relations Set.new
49
+
50
+ def self.register_as(value = Undefined)
51
+ if value == Undefined
52
+ @register_as || dataset
53
+ else
54
+ super
55
+ end
56
+ end
22
57
 
23
- class << self
24
- # Relation methods that were defined inside setup.relation DSL
25
- #
26
- # @return [Array<Symbol>]
27
- #
28
- # @api private
29
- attr_accessor :relation_methods
58
+ def self.method_added(name)
59
+ super
60
+ exposed_relations << name if public_instance_methods.include?(name)
61
+ end
62
+ end
63
+
64
+ ROM.register_relation(klass)
65
+ end
66
+
67
+ # Return adapter-specific relation subclass
68
+ #
69
+ # @example
70
+ # ROM::Relation[:memory]
71
+ # # => ROM::Memory::Relation
72
+ #
73
+ # @return [Class]
74
+ #
75
+ # @api public
76
+ def self.[](type)
77
+ ROM.adapters.fetch(type).const_get(:Relation)
30
78
  end
31
79
 
32
- # @return [Array] relation base header
80
+ # Dynamically define a method that will forward to the dataset and wrap
81
+ # response in the relation itself
82
+ #
83
+ # @example
84
+ # class SomeAdapterRelation < ROM::Relation
85
+ # forward :super_query
86
+ # end
87
+ #
88
+ # @api public
89
+ def self.forward(*methods)
90
+ methods.each do |method|
91
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
92
+ def #{method}(*args, &block)
93
+ __new__(dataset.__send__(:#{method}, *args, &block))
94
+ end
95
+ RUBY
96
+ end
97
+ end
98
+
99
+ # Return default relation name used for `register_as` setting
100
+ #
101
+ # @return [Symbol]
33
102
  #
34
103
  # @api private
35
- attr_reader :header
104
+ def self.default_name
105
+ return unless name
106
+ Inflector.underscore(name).gsub('/', '_').to_sym
107
+ end
36
108
 
37
- # Hook to finalize a relation after its instance was created
109
+ # Build relation registry of specified descendant classes
110
+ #
111
+ # This is used by the setup
112
+ #
113
+ # @param [Hash] repositories
114
+ # @param [Array] descendants a list of relation descendants
115
+ #
116
+ # @return [Hash]
38
117
  #
39
118
  # @api private
40
- def self.finalize(_env, _relation)
41
- # noop
119
+ def self.registry(repositories, descendants)
120
+ registry = {}
121
+
122
+ descendants.each do |klass|
123
+ # TODO: raise a meaningful error here and add spec covering the case
124
+ # where klass' repository points to non-existant repo
125
+ repository = repositories.fetch(klass.repository)
126
+ dataset = repository.dataset(klass.dataset)
127
+
128
+ relation = klass.new(dataset, __registry__: registry)
129
+
130
+ name = klass.register_as
131
+
132
+ if registry.key?(name)
133
+ raise RelationAlreadyDefinedError,
134
+ "Relation with `register_as #{name.inspect}` registered more " \
135
+ "than once"
136
+ end
137
+
138
+ registry[name] = relation
139
+ end
140
+
141
+ registry.each_value do |relation|
142
+ relation.class.finalize(registry, relation)
143
+ end
144
+
145
+ registry
42
146
  end
43
147
 
44
148
  # @api private
45
- def initialize(dataset, header = dataset.header)
149
+ def initialize(dataset, options = {})
150
+ @dataset = dataset
151
+ @name = self.class.dataset
152
+ @exposed_relations = self.class.exposed_relations
46
153
  super
47
- @header = header.dup.freeze
154
+ end
155
+
156
+ # Hook to finalize a relation after its instance was created
157
+ #
158
+ # @api private
159
+ def self.finalize(_env, _relation)
160
+ # noop
48
161
  end
49
162
 
50
163
  # Yield dataset tuples
@@ -54,7 +167,33 @@ module ROM
54
167
  # @api private
55
168
  def each(&block)
56
169
  return to_enum unless block
57
- dataset.each(&block)
170
+ dataset.each { |tuple| yield(tuple) }
171
+ end
172
+
173
+ # Materialize relation into an array
174
+ #
175
+ # @return [Array<Hash>]
176
+ #
177
+ # @api public
178
+ def to_a
179
+ to_enum.to_a
180
+ end
181
+
182
+ # @api private
183
+ def repository
184
+ self.class.repository
185
+ end
186
+
187
+ # @api public
188
+ def to_lazy(*args)
189
+ Lazy.new(self, *args)
190
+ end
191
+
192
+ private
193
+
194
+ # @api private
195
+ def __new__(dataset, new_opts = {})
196
+ self.class.new(dataset, options.merge(new_opts))
58
197
  end
59
198
  end
60
199
  end
@@ -0,0 +1,132 @@
1
+ require 'rom/relation/loaded'
2
+
3
+ module ROM
4
+ class Relation
5
+ # Left-to-right relation composition used for data-pipelining
6
+ #
7
+ # @api public
8
+ class Composite
9
+ include Equalizer.new(:left, :right)
10
+
11
+ # @return [Lazy,Curried,Composite,#call]
12
+ #
13
+ # @api private
14
+ attr_reader :left
15
+
16
+ # @return [Lazy,Curried,Composite,#call]
17
+ #
18
+ # @api private
19
+ attr_reader :right
20
+
21
+ # @api private
22
+ def initialize(left, right)
23
+ @left = left
24
+ @right = right
25
+ end
26
+
27
+ # Compose with another callable object
28
+ #
29
+ # @param [#call]
30
+ #
31
+ # @return [Composite]
32
+ #
33
+ # @api public
34
+ def >>(other)
35
+ self.class.new(self, other)
36
+ end
37
+
38
+ # Call the pipeline by passing results from left to right
39
+ #
40
+ # Optional args are passed to the left object
41
+ #
42
+ # @return [Loaded]
43
+ #
44
+ # @api public
45
+ def call(*args)
46
+ relation = left.call(*args)
47
+ response = right.call(relation)
48
+
49
+ if relation.is_a?(Loaded)
50
+ relation.new(response)
51
+ else
52
+ Loaded.new(relation, response)
53
+ end
54
+ end
55
+ alias_method :[], :call
56
+
57
+ # Coerce composite relation to an array
58
+ #
59
+ # @return [Array]
60
+ #
61
+ # @api public
62
+ def to_a
63
+ call.to_a
64
+ end
65
+ alias_method :to_ary, :to_a
66
+
67
+ # Delegate to loaded relation and return one object
68
+ #
69
+ # @return [Object]
70
+ #
71
+ # @see Loaded#one
72
+ #
73
+ # @api public
74
+ def one
75
+ call.one
76
+ end
77
+
78
+ # Delegate to loaded relation and return one object
79
+ #
80
+ # @return [Object]
81
+ #
82
+ # @see Loaded#one
83
+ #
84
+ # @api public
85
+ def one!
86
+ call.one!
87
+ end
88
+
89
+ # Yield composite relation objects
90
+ #
91
+ # @yield [Object]
92
+ #
93
+ # @api public
94
+ def each(&block)
95
+ return to_enum unless block
96
+ call.each { |object| yield(object) }
97
+ end
98
+
99
+ # Return first object from the called relation
100
+ #
101
+ # @return [Object]
102
+ #
103
+ # @api public
104
+ def first
105
+ call.first
106
+ end
107
+
108
+ # @api private
109
+ def respond_to_missing?(name, include_private = false)
110
+ left.respond_to?(name) || super
111
+ end
112
+
113
+ private
114
+
115
+ # Allow calling methods on the left side object
116
+ #
117
+ # @api private
118
+ def method_missing(name, *args, &block)
119
+ if left.respond_to?(name)
120
+ response = left.__send__(name, *args, &block)
121
+ if response.is_a?(left.class)
122
+ self.class.new(response, right)
123
+ else
124
+ response
125
+ end
126
+ else
127
+ super
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,48 @@
1
+ require 'rom/relation/lazy'
2
+
3
+ module ROM
4
+ class Relation
5
+ class Curried < Lazy
6
+ option :name, type: Symbol, reader: true
7
+ option :arity, type: Integer, reader: true, default: -1
8
+ option :curry_args, type: Array, reader: true, default: EMPTY_ARRAY
9
+
10
+ # Load relation if args match the arity
11
+ #
12
+ # @return [Loaded,Lazy,Curried]
13
+ # @see Lazy#call
14
+ #
15
+ # @api public
16
+ def call(*args)
17
+ if arity != -1
18
+ all_args = curry_args + args
19
+
20
+ if arity == all_args.size
21
+ Loaded.new(relation.__send__(name, *all_args))
22
+ else
23
+ __new__(relation, curry_args: all_args)
24
+ end
25
+ else
26
+ super
27
+ end
28
+ end
29
+ alias_method :[], :call
30
+
31
+ # Return if this lazy relation is curried
32
+ #
33
+ # @return [true]
34
+ #
35
+ # @api private
36
+ def curried?
37
+ true
38
+ end
39
+
40
+ private
41
+
42
+ # @api private
43
+ def __new__(relation, new_opts = {})
44
+ Curried.new(relation, options.merge(new_opts))
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,173 @@
1
+ require 'rom/relation/loaded'
2
+ require 'rom/relation/composite'
3
+
4
+ module ROM
5
+ class Relation
6
+ # Lazy relation wraps canonical relation for data-pipelining
7
+ #
8
+ # @example
9
+ # ROM.setup(:memory)
10
+ #
11
+ # class Users < ROM::Relation[:memory]
12
+ # def by_name(name)
13
+ # restrict(name: name)
14
+ # end
15
+ # end
16
+ #
17
+ # rom = ROM.finalize.env
18
+ #
19
+ # rom.relations.users << { name: 'Jane' }
20
+ # rom.relations.users << { name: 'Joe' }
21
+ #
22
+ # mapper = proc { |users| users.map { |user| user[:name] } }
23
+ # users = rom.relation(:users)
24
+ #
25
+ # (users.by_name >> mapper)['Jane'].inspect # => ["Jane"]
26
+ #
27
+ # @api public
28
+ class Lazy
29
+ include Equalizer.new(:relation, :options)
30
+ include Options
31
+
32
+ option :mappers, reader: true, default: EMPTY_HASH
33
+
34
+ # @return [Relation]
35
+ #
36
+ # @api private
37
+ attr_reader :relation
38
+
39
+ # Map of exposed relation methods
40
+ #
41
+ # @return [Hash<Symbol=>TrueClass>]
42
+ #
43
+ # @api private
44
+ attr_reader :methods
45
+
46
+ # @api private
47
+ def initialize(relation, options = {})
48
+ super
49
+ @relation = relation
50
+ @methods = @relation.exposed_relations
51
+ end
52
+
53
+ # Compose two relation with a left-to-right composition
54
+ #
55
+ # @example
56
+ # users.by_name('Jane') >> tasks.for_users
57
+ #
58
+ # @param [Relation] other The right relation
59
+ #
60
+ # @return [Relation::Composite]
61
+ #
62
+ # @api public
63
+ def >>(other)
64
+ Composite.new(self, other)
65
+ end
66
+
67
+ # Build a relation pipeline using registered mappers
68
+ #
69
+ # @example
70
+ # rom.relation(:users).map_with(:json_serializer)
71
+ #
72
+ # @return [Relation::Composite]
73
+ #
74
+ # @api public
75
+ def map_with(*names)
76
+ [self, *names.map { |name| mappers[name] }]
77
+ .reduce { |a, e| Composite.new(a, e) }
78
+ end
79
+ alias_method :as, :map_with
80
+
81
+ # Coerce lazy relation to an array
82
+ #
83
+ # @return [Array]
84
+ #
85
+ # @api public
86
+ def to_a
87
+ call.to_a
88
+ end
89
+ alias_method :to_ary, :to_a
90
+
91
+ # Load relation
92
+ #
93
+ # @return [Relation::Loaded]
94
+ #
95
+ # @api public
96
+ def call
97
+ Loaded.new(relation)
98
+ end
99
+ alias_method :[], :call
100
+
101
+ # Delegate to loaded relation and return one object
102
+ #
103
+ # @return [Object]
104
+ #
105
+ # @see Loaded#one
106
+ #
107
+ # @api public
108
+ def one
109
+ call.one
110
+ end
111
+
112
+ # Delegate to loaded relation and return one object
113
+ #
114
+ # @return [Object]
115
+ #
116
+ # @see Loaded#one
117
+ #
118
+ # @api public
119
+ def one!
120
+ call.one!
121
+ end
122
+
123
+ # @api private
124
+ def respond_to_missing?(name, include_private = false)
125
+ methods.include?(name) || super
126
+ end
127
+
128
+ # Return if this lazy relation is curried
129
+ #
130
+ # @return [false]
131
+ #
132
+ # @api private
133
+ def curried?
134
+ false
135
+ end
136
+
137
+ private
138
+
139
+ # Forward methods to the underlaying relation
140
+ #
141
+ # Auto-curry relations when args size doesn't match arity
142
+ #
143
+ # @return [Lazy,Curried]
144
+ #
145
+ # @api private
146
+ def method_missing(meth, *args, &block)
147
+ if !methods.include?(meth) || (curried? && name != meth)
148
+ super
149
+ else
150
+ arity = relation.method(meth).arity
151
+
152
+ if arity == -1 || arity == args.size
153
+ response = relation.__send__(meth, *args, &block)
154
+ if response.is_a?(Relation)
155
+ __new__(response)
156
+ else
157
+ response
158
+ end
159
+ else
160
+ Curried.new(relation, name: meth, curry_args: args, arity: arity)
161
+ end
162
+ end
163
+ end
164
+
165
+ # Return new lazy relation with updated options
166
+ #
167
+ # @api private
168
+ def __new__(relation, new_opts = {})
169
+ Lazy.new(relation, options.merge(new_opts))
170
+ end
171
+ end
172
+ end
173
+ end