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