activerecord-futures 0.2.0 → 0.3.0
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.
- data/README.md +34 -8
- data/lib/active_record/connection_adapters/future_enabled.rb +6 -5
- data/lib/active_record/connection_adapters/future_enabled_mysql2_adapter.rb +2 -1
- data/lib/active_record/connection_adapters/future_enabled_postgresql_adapter.rb +21 -7
- data/lib/active_record/futures.rb +11 -23
- data/lib/active_record/futures/calculation_methods.rb +34 -0
- data/lib/active_record/futures/delegation.rb +1 -1
- data/lib/active_record/futures/finder_methods.rb +59 -0
- data/lib/active_record/futures/future.rb +21 -57
- data/lib/active_record/futures/future_array.rb +23 -0
- data/lib/active_record/futures/future_registry.rb +33 -0
- data/lib/active_record/futures/future_value.rb +20 -0
- data/lib/active_record/futures/query_recording.rb +11 -4
- data/lib/activerecord-futures.rb +5 -4
- data/lib/activerecord-futures/version.rb +1 -1
- data/spec/active_record/futures/future_array_spec.rb +26 -0
- data/spec/active_record/futures/future_registry_spec.rb +86 -0
- data/spec/active_record/futures/future_spec.rb +42 -69
- data/spec/active_record/futures/future_value_spec.rb +26 -0
- data/spec/in_action/combination_of_futures_spec.rb +13 -9
- data/spec/in_action/future_all_execution_spec.rb +20 -0
- data/spec/in_action/future_count_execution_spec.rb +8 -56
- data/spec/in_action/future_exists_execution_spec.rb +22 -0
- data/spec/in_action/future_find_execution_spec.rb +68 -0
- data/spec/in_action/future_first_execution_spec.rb +20 -0
- data/spec/in_action/future_fulfillment_spec.rb +10 -7
- data/spec/in_action/future_last_execution_spec.rb +21 -0
- data/spec/in_action/future_pluck_execution_spec.rb +4 -27
- data/spec/in_action/future_relation_execution_spec.rb +8 -30
- data/spec/spec_helper.rb +2 -1
- data/spec/support/futurized_method_examples.rb +43 -0
- metadata +25 -12
- data/lib/active_record/futures/future_calculation.rb +0 -30
- data/lib/active_record/futures/future_calculation_array.rb +0 -8
- data/lib/active_record/futures/future_calculation_value.rb +0 -7
- data/lib/active_record/futures/future_relation.rb +0 -34
- data/spec/active_record/futures/future_calculation_array_spec.rb +0 -29
- data/spec/active_record/futures/future_calculation_value_spec.rb +0 -29
- data/spec/active_record/futures/future_relation_spec.rb +0 -81
data/README.md
CHANGED
@@ -68,15 +68,41 @@ both count and page queries at once.
|
|
68
68
|
|
69
69
|
### Methods
|
70
70
|
|
71
|
-
|
72
|
-
|
71
|
+
#### #future method
|
72
|
+
ActiveRecord::Relation instances get a `future` method that futurizes a normal
|
73
|
+
relation. The future gets executed whenever `#to_a` gets executed. Note that, as ActiveRecord does, enumerable methods get delegated to `#to_a` also,
|
73
74
|
so things like `#each`, `#map`, `#collect` all trigger the future.
|
74
75
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
76
|
+
#### Calculation methods
|
77
|
+
You also get all the calculation methods provided by the ActiveRecord::Calculations module
|
78
|
+
"futurized". More specifically you get:
|
79
|
+
* future_count
|
80
|
+
* future_average
|
81
|
+
* future_minimum
|
82
|
+
* future_maximum
|
83
|
+
* future_sum
|
84
|
+
* future_calculate
|
85
|
+
* future_pluck
|
86
|
+
|
87
|
+
All future
|
88
|
+
calculations are triggered with the `#value` method, except for the `#future_pluck` method, that returns an array, and is
|
89
|
+
triggered with a `#to_a` method (or any other method that delegates to it).
|
90
|
+
|
91
|
+
#### Finder methods
|
92
|
+
|
93
|
+
Lastly, you also get finder methods futurized, which are:
|
94
|
+
|
95
|
+
* future_find
|
96
|
+
* future_first
|
97
|
+
* future_last
|
98
|
+
* future_exists?
|
99
|
+
* future_all
|
100
|
+
|
101
|
+
As with the other future methods, those which return an array get triggered with
|
102
|
+
the `to_a` method, or the delegated ones, and those that return a value or a hash
|
103
|
+
are triggered with the `value` method. Note that the `find` method returns an
|
104
|
+
array or a value depending on the parameters provided, and so will the futurized
|
105
|
+
version of the method.
|
80
106
|
|
81
107
|
## Database support
|
82
108
|
|
@@ -89,7 +115,7 @@ it will execute the future's query whenever the future is triggered, but it will
|
|
89
115
|
|
90
116
|
Multi statement queries are supported by the mysql2 gem since version 0.3.12b1, so you'll need to use that one or a newer
|
91
117
|
one.
|
92
|
-
Currently the adapter provided inherits the built-in one in Rails, and it also sets the MULTI_STATEMENTS flag to allow
|
118
|
+
Currently the adapter provided inherits the built-in one in Rails, and it also sets the MULTI_STATEMENTS flag to allow
|
93
119
|
multiple queries in a single command.
|
94
120
|
If you have an older version of the gem, ActiveRecord::Futures will fall back to normal query execution.
|
95
121
|
|
@@ -6,21 +6,22 @@ module ActiveRecord
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def exec_query(sql, name = 'SQL', binds = [])
|
9
|
-
my_future = Futures::
|
9
|
+
my_future = Futures::FutureRegistry.current
|
10
10
|
|
11
11
|
# default behavior if not a current future or not executing
|
12
12
|
# the current future's sql (some adapters like PostgreSQL
|
13
13
|
# may execute some attribute queries during a relation evaluation)
|
14
|
-
return super
|
14
|
+
return super if !my_future || to_sql(my_future.query, my_future.binds.try(:dup)) != sql
|
15
15
|
|
16
16
|
# return fulfilled result, if exists, to load the relation
|
17
17
|
return my_future.result if my_future.fulfilled?
|
18
18
|
|
19
|
-
futures = Futures::
|
20
|
-
|
19
|
+
futures = Futures::FutureRegistry.all
|
20
|
+
future_arels = futures.map(&:query)
|
21
|
+
future_binds = futures.map(&:binds)
|
21
22
|
name = "#{name} (fetching Futures)"
|
22
23
|
|
23
|
-
result = future_execute(
|
24
|
+
result = future_execute(future_arels, future_binds, name)
|
24
25
|
|
25
26
|
futures.each do |future|
|
26
27
|
future.fulfill(build_active_record_result(result))
|
@@ -36,7 +36,8 @@ module ActiveRecord
|
|
36
36
|
@connection.respond_to?(:store_result)
|
37
37
|
end
|
38
38
|
|
39
|
-
def future_execute(
|
39
|
+
def future_execute(arels, binds, name)
|
40
|
+
sql = arels.zip(binds).map { |arel, bind| to_sql(arel, bind.try(:dup)) }.join(';')
|
40
41
|
execute(sql, name)
|
41
42
|
end
|
42
43
|
|
@@ -27,13 +27,27 @@ module ActiveRecord
|
|
27
27
|
class FutureEnabledPostgreSQLAdapter < PostgreSQLAdapter
|
28
28
|
include FutureEnabled
|
29
29
|
|
30
|
-
def future_execute(
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
30
|
+
def future_execute(arels, binds, name)
|
31
|
+
sql = arels.zip(binds).map { |arel, bind| to_sql(arel, bind.try(:dup)) }.join(';')
|
32
|
+
binds = binds.flatten(1).compact
|
33
|
+
|
34
|
+
log(sql, name, binds) do
|
35
|
+
|
36
|
+
if binds.empty?
|
37
|
+
# Clear the queue
|
38
|
+
@connection.get_last_result
|
39
|
+
@connection.send_query(sql)
|
40
|
+
@connection.block
|
41
|
+
@connection.get_result
|
42
|
+
else
|
43
|
+
# Clear the queue
|
44
|
+
@connection.get_last_result
|
45
|
+
@connection.send_query(sql, binds.map { |col, val|
|
46
|
+
type_cast(val, col)
|
47
|
+
})
|
48
|
+
@connection.block
|
49
|
+
@connection.get_result
|
50
|
+
end
|
37
51
|
end
|
38
52
|
end
|
39
53
|
|
@@ -1,35 +1,23 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Futures
|
3
|
-
include QueryRecording
|
4
3
|
|
5
|
-
def self.
|
6
|
-
|
4
|
+
def self.futurize(method)
|
5
|
+
"future_#{method}"
|
7
6
|
end
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
include QueryRecording
|
9
|
+
include FinderMethods
|
10
|
+
include CalculationMethods
|
12
11
|
|
13
12
|
def future
|
14
|
-
|
13
|
+
FutureArray.new(record_future(:to_a))
|
15
14
|
end
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
method_table = Hash[future_calculation_methods.zip(original_calculation_methods)]
|
24
|
-
|
25
|
-
# define a "future_" method for each calculation method
|
26
|
-
#
|
27
|
-
method_table.each do |future_method, method|
|
28
|
-
define_method(future_method) do |*args, &block|
|
29
|
-
exec = lambda { send(method, *args, &block) }
|
30
|
-
query = record_query(&exec)
|
31
|
-
FutureCalculationValue.new(self, query, exec)
|
32
|
-
end
|
16
|
+
private
|
17
|
+
def record_future(method, *args, &block)
|
18
|
+
exec = -> { send(method, *args, &block) }
|
19
|
+
query, binds = record_query(&exec)
|
20
|
+
Future.new(self, query, binds, exec)
|
33
21
|
end
|
34
22
|
end
|
35
23
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Futures
|
3
|
+
module CalculationMethods
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
def future_pluck(*args, &block)
|
7
|
+
FutureArray.new(record_future(:pluck, *args, &block))
|
8
|
+
end
|
9
|
+
|
10
|
+
included do
|
11
|
+
methods = original_calculation_methods - [:pluck]
|
12
|
+
|
13
|
+
# define a "future_" method for each calculation method
|
14
|
+
#
|
15
|
+
methods.each do |method|
|
16
|
+
define_method(futurize(method)) do |*args, &block|
|
17
|
+
FutureValue.new(record_future(method, *args, &block))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
|
24
|
+
def original_calculation_methods
|
25
|
+
[:count, :average, :minimum, :maximum, :sum, :calculate, :pluck]
|
26
|
+
end
|
27
|
+
|
28
|
+
def future_calculation_methods
|
29
|
+
original_calculation_methods.map { |method| futurize(method) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Futures
|
3
|
+
module FinderMethods
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
def future_find(*args, &block)
|
7
|
+
exec = -> { find(*args, &block) }
|
8
|
+
query, binds = record_query do
|
9
|
+
begin
|
10
|
+
exec.call
|
11
|
+
rescue RecordNotFound
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
args = args.dup
|
17
|
+
args.extract_options!
|
18
|
+
|
19
|
+
# Only expect an array if a block is given, finding :all,
|
20
|
+
# finding by id passing an array or passing multiple ids
|
21
|
+
expects_array = block_given? || args.first == :all ||
|
22
|
+
args.first.kind_of?(Array) || args.size > 1
|
23
|
+
|
24
|
+
future = Future.new(self, query, binds, exec)
|
25
|
+
if expects_array
|
26
|
+
FutureArray.new(future)
|
27
|
+
else
|
28
|
+
FutureValue.new(future)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def future_all(*args, &block)
|
33
|
+
FutureArray.new(record_future(:all, *args, &block))
|
34
|
+
end
|
35
|
+
|
36
|
+
included do
|
37
|
+
methods = original_finder_methods - [:find, :all]
|
38
|
+
|
39
|
+
# define a "future_" method for each finder method
|
40
|
+
#
|
41
|
+
methods.each do |method|
|
42
|
+
define_method(futurize(method)) do |*args, &block|
|
43
|
+
FutureValue.new(record_future(method, *args, &block))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module ClassMethods
|
49
|
+
def original_finder_methods
|
50
|
+
[:find, :first, :last, :exists?, :all]
|
51
|
+
end
|
52
|
+
|
53
|
+
def future_finder_methods
|
54
|
+
original_finder_methods.map { |method| futurize(method) }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -1,54 +1,15 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Futures
|
3
3
|
class Future
|
4
|
-
|
5
|
-
|
6
|
-
Thread.current["#{self.name}_futures"] ||= []
|
7
|
-
end
|
8
|
-
alias_method :all, :futures
|
9
|
-
|
10
|
-
def current
|
11
|
-
Thread.current["#{self.name}_current"]
|
12
|
-
end
|
13
|
-
|
14
|
-
def current=(future)
|
15
|
-
Thread.current["#{self.name}_current"] = future
|
16
|
-
end
|
17
|
-
|
18
|
-
def clear
|
19
|
-
all.clear
|
20
|
-
end
|
21
|
-
|
22
|
-
def register(future)
|
23
|
-
self.futures << future
|
24
|
-
end
|
25
|
-
|
26
|
-
def flush
|
27
|
-
self.futures.each(&:load)
|
28
|
-
clear
|
29
|
-
end
|
30
|
-
|
31
|
-
private
|
32
|
-
def fetch_with(method)
|
33
|
-
define_method(method) do
|
34
|
-
# Flush all the futures upon first attempt to exec a future
|
35
|
-
Future.flush unless executed?
|
36
|
-
execute
|
37
|
-
end
|
4
|
+
attr_reader :result, :relation, :query, :binds, :execution
|
5
|
+
private :relation, :execution
|
38
6
|
|
39
|
-
|
40
|
-
send(method).inspect
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
|
46
|
-
attr_reader :result, :relation
|
47
|
-
private :relation
|
48
|
-
|
49
|
-
def initialize(relation)
|
7
|
+
def initialize(relation, query, binds, execution)
|
50
8
|
@relation = relation
|
51
|
-
|
9
|
+
@query = query
|
10
|
+
@binds = binds
|
11
|
+
@execution = execution
|
12
|
+
FutureRegistry.register(self)
|
52
13
|
end
|
53
14
|
|
54
15
|
def fulfill(result)
|
@@ -64,29 +25,32 @@ module ActiveRecord
|
|
64
25
|
# This allows to fallback to normal query execution in futures
|
65
26
|
# when the adapter does not support futures.
|
66
27
|
return unless connection_supports_futures?
|
67
|
-
|
68
|
-
execute
|
69
|
-
|
28
|
+
FutureRegistry.current = self
|
29
|
+
execute(false)
|
30
|
+
FutureRegistry.current = nil
|
70
31
|
end
|
71
32
|
|
72
|
-
def
|
73
|
-
|
74
|
-
|
33
|
+
def execute(flush = true)
|
34
|
+
# Flush all the futures upon first attempt to exec a future
|
35
|
+
FutureRegistry.flush if flush && !executed?
|
75
36
|
|
76
|
-
|
77
|
-
|
37
|
+
unless executed?
|
38
|
+
@value = execution.call
|
39
|
+
@executed = true
|
40
|
+
end
|
41
|
+
|
42
|
+
@value
|
78
43
|
end
|
79
|
-
undef_method :execute
|
80
44
|
|
81
45
|
def executed?
|
46
|
+
@executed
|
82
47
|
end
|
83
|
-
undef_method :executed?
|
84
48
|
|
49
|
+
private
|
85
50
|
def connection_supports_futures?
|
86
51
|
conn = relation.connection
|
87
52
|
conn.respond_to?(:supports_futures?) && conn.supports_futures?
|
88
53
|
end
|
89
|
-
|
90
54
|
end
|
91
55
|
end
|
92
56
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Futures
|
3
|
+
class FutureArray
|
4
|
+
attr_reader :future_execution
|
5
|
+
private :future_execution
|
6
|
+
|
7
|
+
delegate :to_xml, :to_yaml, :length, :collect, :map, :each,
|
8
|
+
:all?, :include?, :to_ary, to: :to_a
|
9
|
+
|
10
|
+
def initialize(future_execution)
|
11
|
+
@future_execution = future_execution
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_a
|
15
|
+
future_execution.execute
|
16
|
+
end
|
17
|
+
|
18
|
+
def inspect
|
19
|
+
to_a
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Futures
|
3
|
+
module FutureRegistry
|
4
|
+
def futures
|
5
|
+
Thread.current["#{self.name}_futures"] ||= []
|
6
|
+
end
|
7
|
+
alias_method :all, :futures
|
8
|
+
|
9
|
+
def current
|
10
|
+
Thread.current["#{self.name}_current"]
|
11
|
+
end
|
12
|
+
|
13
|
+
def current=(future)
|
14
|
+
Thread.current["#{self.name}_current"] = future
|
15
|
+
end
|
16
|
+
|
17
|
+
def clear
|
18
|
+
all.clear
|
19
|
+
end
|
20
|
+
|
21
|
+
def register(future)
|
22
|
+
self.futures << future
|
23
|
+
end
|
24
|
+
|
25
|
+
def flush
|
26
|
+
self.futures.each(&:load)
|
27
|
+
clear
|
28
|
+
end
|
29
|
+
|
30
|
+
extend self
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|