sunstone 2.0.0 → 2.0.1
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.
- checksums.yaml +4 -4
- data/README.md +9 -2
- data/ext/active_record/associations/builder/has_and_belongs_to_many.rb +52 -48
- data/ext/active_record/batches.rb +2 -2
- data/ext/active_record/calculations.rb +28 -28
- data/ext/active_record/finder_methods.rb +36 -22
- data/ext/active_record/query_methods.rb +23 -0
- data/ext/active_record/relation.rb +6 -8
- data/ext/active_record/relation/predicate_builder.rb +29 -23
- data/ext/active_record/statement_cache.rb +21 -8
- data/lib/active_record/connection_adapters/sunstone_adapter.rb +0 -2
- data/lib/arel/visitors/sunstone.rb +9 -5
- data/lib/sunstone.rb +9 -3
- data/lib/sunstone/connection.rb +2 -3
- data/lib/sunstone/version.rb +1 -1
- data/sunstone.gemspec +3 -3
- data/test/models/ship.rb +14 -0
- data/test/query_test.rb +128 -0
- data/test/sunstone_test.rb +296 -305
- data/test/test_helper.rb +11 -25
- metadata +13 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e7571ad32bf7197a480c1f59c5a5158ce0f1d492
|
4
|
+
data.tar.gz: d10ec4207fc78c2e6ec8d3dee815469a7b4b868a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 121532495ba520b5e66de7cc054e9fc1958076c6953c2dc10e710816d9944fc76e940a4dd6018cea3a5599aa43faecdee9c81259fd7d569ea659abcbc6ccb4d9
|
7
|
+
data.tar.gz: 815d5f4f6a5c89a1552b8dc8cde45ac8cf508608b5403c75f4516569fd834f2f3becf1fa3af3cf25c622cba2d3ade471dca215029d4aa2230a9b46fc916e730b
|
data/README.md
CHANGED
@@ -1,4 +1,11 @@
|
|
1
|
-
|
1
|
+
Sunstone
|
2
2
|
========
|
3
3
|
|
4
|
-
|
4
|
+
An [ActiveRecord](https://rubygems.org/gems/activerecord) adapter for quering
|
5
|
+
APIs over Standard API (https://github.com/waratuman/standardapi).
|
6
|
+
|
7
|
+
|
8
|
+
TODO:
|
9
|
+
=====
|
10
|
+
Make `cookie_store` and optional
|
11
|
+
stream building model instances with wankel
|
@@ -1,48 +1,52 @@
|
|
1
|
-
module ActiveRecord::Associations::Builder
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
1
|
+
# module ActiveRecord::Associations::Builder
|
2
|
+
# class HasAndBelongsToMany # :nodoc:
|
3
|
+
#
|
4
|
+
# def through_model
|
5
|
+
# habtm = JoinTableResolver.build lhs_model, association_name, options
|
6
|
+
#
|
7
|
+
# join_model = Class.new(lhs_model.base_class.superclass) { # Don't use ActiveRecord::Base here because that might not be the abstract class that we need and the connection may be different
|
8
|
+
# class << self;
|
9
|
+
# attr_accessor :left_model
|
10
|
+
# attr_accessor :name
|
11
|
+
# attr_accessor :table_name_resolver
|
12
|
+
# attr_accessor :left_reflection
|
13
|
+
# attr_accessor :right_reflection
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# def self.table_name
|
17
|
+
# table_name_resolver.join_table
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# def self.compute_type(class_name)
|
21
|
+
# left_model.compute_type class_name
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# def self.add_left_association(name, options)
|
25
|
+
# belongs_to name, options
|
26
|
+
# self.left_reflection = _reflect_on_association(name)
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# def self.add_right_association(name, options)
|
30
|
+
# rhs_name = name.to_s.singularize.to_sym
|
31
|
+
# belongs_to rhs_name, options
|
32
|
+
# self.right_reflection = _reflect_on_association(rhs_name)
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# def self.retrieve_connection
|
36
|
+
# left_model.retrieve_connection
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# }
|
40
|
+
#
|
41
|
+
# join_model.name = "HABTM_#{association_name.to_s.camelize}"
|
42
|
+
# join_model.table_name_resolver = habtm
|
43
|
+
# join_model.class_resolver = lhs_model
|
44
|
+
# join_model.primary_key = nil
|
45
|
+
#
|
46
|
+
# join_model.add_left_association :left_side, anonymous_class: lhs_model
|
47
|
+
# join_model.add_right_association association_name, belongs_to_options(options)
|
48
|
+
# join_model
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# end
|
52
|
+
# end
|
@@ -1,28 +1,28 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
28
|
-
|
1
|
+
# module ActiveRecord
|
2
|
+
# module Calculations
|
3
|
+
# def pluck(*column_names)
|
4
|
+
#
|
5
|
+
# # column_names.map! do |column_name|
|
6
|
+
# # if column_name.is_a?(Symbol) && attribute_alias?(column_name)
|
7
|
+
# # attribute_alias(column_name)
|
8
|
+
# # else
|
9
|
+
# # column_name.to_s
|
10
|
+
# # end
|
11
|
+
# # end
|
12
|
+
#
|
13
|
+
# if has_include?(column_names.first)
|
14
|
+
# construct_relation_for_association_calculations.pluck(*column_names)
|
15
|
+
# else
|
16
|
+
# relation = spawn
|
17
|
+
# relation.select_values = column_names.map { |cn|
|
18
|
+
# columns_hash.key?(cn) ? arel_table[cn] : cn
|
19
|
+
# }
|
20
|
+
#
|
21
|
+
# result = klass.connection.exec_query(relation.arel, nil, relation.arel.bind_values + bind_values)
|
22
|
+
# result.cast_values(klass.column_types)
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
@@ -1,22 +1,36 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module FinderMethods
|
3
|
-
|
4
|
-
def find_with_associations
|
5
|
-
arel.eager_load = Arel::Nodes::EagerLoad.new(eager_load_values)
|
6
3
|
|
4
|
+
def find_with_associations
|
5
|
+
join_dependency = nil
|
6
|
+
aliases = nil
|
7
|
+
relation = if connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
|
8
|
+
arel.eager_load = Arel::Nodes::EagerLoad.new(eager_load_values)
|
9
|
+
self
|
10
|
+
else
|
11
|
+
join_dependency = construct_join_dependency(joins_values)
|
12
|
+
aliases = join_dependency.aliases
|
13
|
+
apply_join_dependency(select(aliases.columns), join_dependency)
|
14
|
+
end
|
15
|
+
|
7
16
|
if block_given?
|
8
|
-
yield
|
17
|
+
yield relation
|
9
18
|
else
|
10
|
-
if ActiveRecord::NullRelation ===
|
19
|
+
if ActiveRecord::NullRelation === relation
|
11
20
|
[]
|
12
21
|
else
|
13
|
-
|
14
|
-
|
15
|
-
|
22
|
+
arel = relation.arel
|
23
|
+
rows = connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
|
24
|
+
if join_dependency
|
25
|
+
join_dependency.instantiate(rows, aliases)
|
26
|
+
else
|
27
|
+
instantiate_with_associations(rows, relation)
|
28
|
+
end
|
29
|
+
|
16
30
|
end
|
17
31
|
end
|
18
32
|
end
|
19
|
-
|
33
|
+
|
20
34
|
def instantiate_with_associations(result_set, klass)
|
21
35
|
seen = Hash.new { |h, parent_klass|
|
22
36
|
h[parent_klass] = Hash.new { |i, parent_id|
|
@@ -26,20 +40,20 @@ module ActiveRecord
|
|
26
40
|
|
27
41
|
model_cache = Hash.new { |h,klass| h[klass] = {} }
|
28
42
|
parents = model_cache[self.base_class]
|
29
|
-
|
43
|
+
|
30
44
|
result_set.each { |row_hash|
|
31
45
|
parent = parents[row_hash[primary_key]] ||= instantiate(row_hash.select{|k,v| column_names.include?(k.to_s) })
|
32
46
|
construct(parent, row_hash.select{|k,v| !column_names.include?(k.to_s) }, seen, model_cache)
|
33
47
|
}
|
34
|
-
|
48
|
+
|
35
49
|
parents.values
|
36
50
|
end
|
37
|
-
|
51
|
+
|
38
52
|
def construct(parent, relations, seen, model_cache)
|
39
53
|
relations.each do |key, attributes|
|
40
54
|
reflection = parent.class.reflect_on_association(key)
|
41
55
|
next unless reflection
|
42
|
-
|
56
|
+
|
43
57
|
if reflection.collection?
|
44
58
|
other = parent.association(reflection.name)
|
45
59
|
other.loaded!
|
@@ -49,7 +63,7 @@ module ActiveRecord
|
|
49
63
|
construct(model, attributes.select{|k,v| !reflection.klass.column_names.include?(k.to_s) }, seen, model_cache)
|
50
64
|
end
|
51
65
|
end
|
52
|
-
|
66
|
+
|
53
67
|
if !reflection.collection?
|
54
68
|
construct_association(parent, reflection, attributes, seen, model_cache)
|
55
69
|
else
|
@@ -57,13 +71,13 @@ module ActiveRecord
|
|
57
71
|
construct_association(parent, reflection, row, seen, model_cache)
|
58
72
|
end
|
59
73
|
end
|
60
|
-
|
74
|
+
|
61
75
|
end
|
62
76
|
end
|
63
|
-
|
77
|
+
|
64
78
|
def construct_association(parent, reflection, attributes, seen, model_cache)
|
65
79
|
return if attributes.nil?
|
66
|
-
|
80
|
+
|
67
81
|
klass = if reflection.polymorphic?
|
68
82
|
parent.send(reflection.foreign_type).constantize.base_class
|
69
83
|
else
|
@@ -71,12 +85,12 @@ module ActiveRecord
|
|
71
85
|
end
|
72
86
|
id = attributes[klass.primary_key]
|
73
87
|
model = seen[parent.class.base_class][parent.id][klass][id]
|
74
|
-
|
88
|
+
|
75
89
|
if model
|
76
90
|
construct(model, attributes.select{|k,v| !klass.column_names.include?(k.to_s) }, seen, model_cache)
|
77
91
|
|
78
92
|
other = parent.association(reflection.name)
|
79
|
-
|
93
|
+
|
80
94
|
if reflection.collection?
|
81
95
|
other.target.push(model)
|
82
96
|
else
|
@@ -90,7 +104,7 @@ module ActiveRecord
|
|
90
104
|
construct(model, attributes.select{|k,v| !klass.column_names.include?(k.to_s) }, seen, model_cache)
|
91
105
|
end
|
92
106
|
end
|
93
|
-
|
107
|
+
|
94
108
|
|
95
109
|
def construct_model(record, reflection, id, attributes, seen, model_cache)
|
96
110
|
klass = if reflection.polymorphic?
|
@@ -98,7 +112,7 @@ module ActiveRecord
|
|
98
112
|
else
|
99
113
|
reflection.klass
|
100
114
|
end
|
101
|
-
|
115
|
+
|
102
116
|
model = model_cache[klass][id] ||= klass.instantiate(attributes)
|
103
117
|
other = record.association(reflection.name)
|
104
118
|
|
@@ -111,7 +125,7 @@ module ActiveRecord
|
|
111
125
|
other.set_inverse_instance(model)
|
112
126
|
model
|
113
127
|
end
|
114
|
-
|
128
|
+
|
115
129
|
end
|
116
130
|
|
117
131
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module QueryMethods
|
3
|
+
|
4
|
+
def reverse_sql_order(order_query)
|
5
|
+
order_query = [arel_table[primary_key].asc] if order_query.empty?
|
6
|
+
|
7
|
+
order_query.flat_map do |o|
|
8
|
+
case o
|
9
|
+
when Arel::Nodes::Ordering
|
10
|
+
o.reverse
|
11
|
+
when String
|
12
|
+
o.to_s.split(',').map! do |s|
|
13
|
+
s.strip!
|
14
|
+
s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
|
15
|
+
end
|
16
|
+
else
|
17
|
+
o
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
class Relation
|
3
|
+
|
3
4
|
def to_sql
|
4
5
|
@to_sql ||= begin
|
5
6
|
relation = self
|
6
7
|
connection = klass.connection
|
7
|
-
visitor = connection.visitor
|
8
|
-
collector = connection.collector
|
8
|
+
visitor = connection.visitor.is_a?(Arel::Visitors::Sunstone) ? Arel::Visitors::ToSql.new(connection) : connection.visitor
|
9
9
|
|
10
10
|
if eager_loading?
|
11
11
|
find_with_associations { |rel| relation = rel }
|
@@ -14,13 +14,11 @@ module ActiveRecord
|
|
14
14
|
arel = relation.arel
|
15
15
|
binds = (arel.bind_values + relation.bind_values).dup
|
16
16
|
binds.map! { |bv| connection.quote(*bv.reverse) }
|
17
|
-
collect = visitor.accept(arel.ast,
|
18
|
-
|
19
|
-
collect.compile(binds)
|
20
|
-
else
|
21
|
-
collect.substitute_binds(binds).join
|
22
|
-
end
|
17
|
+
collect = visitor.accept(arel.ast, Arel::Collectors::Bind.new)
|
18
|
+
collect.substitute_binds(binds).join
|
23
19
|
end
|
24
20
|
end
|
21
|
+
|
22
|
+
|
25
23
|
end
|
26
24
|
end
|
@@ -1,23 +1,29 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
1
|
+
# module ActiveRecord
|
2
|
+
# class PredicateBuilder # :nodoc:
|
3
|
+
#
|
4
|
+
# def self.expand(klass, table, column, value)
|
5
|
+
# queries = []
|
6
|
+
#
|
7
|
+
# # In standard Rails where takes :table => { columns }, but in sunstone we
|
8
|
+
# # can can do nested tables eg: where(:properties => { :regions => {:id => 1}})
|
9
|
+
# if klass && reflection = klass._reflect_on_association(column)
|
10
|
+
# base_class = polymorphic_base_class_from_value(value)
|
11
|
+
#
|
12
|
+
# if reflection.polymorphic? && base_class
|
13
|
+
# queries << build(table[reflection.foreign_type], base_class)
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# column = reflection.foreign_key
|
17
|
+
#
|
18
|
+
# if base_class
|
19
|
+
# primary_key = reflection.association_primary_key(base_class)
|
20
|
+
# value = convert_value_to_association_ids(value, primary_key)
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# queries << build(table[column], value)
|
25
|
+
# queries
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# end
|
29
|
+
# end
|
@@ -1,23 +1,36 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
class StatementCache
|
3
3
|
class PartialQuery
|
4
|
-
|
5
|
-
def initialize
|
6
|
-
@
|
7
|
-
|
8
|
-
|
9
|
-
|
4
|
+
|
5
|
+
def initialize(values, sunstone=false)
|
6
|
+
@values = values
|
7
|
+
if sunstone
|
8
|
+
@indexes = values.value.find_all { |thing|
|
9
|
+
Arel::Nodes::BindParam === thing
|
10
|
+
}
|
11
|
+
else
|
12
|
+
@indexes = values.each_with_index.find_all { |thing,i|
|
13
|
+
Arel::Nodes::BindParam === thing
|
14
|
+
}.map(&:last)
|
15
|
+
end
|
10
16
|
end
|
11
17
|
|
12
18
|
def sql_for(binds, connection)
|
13
19
|
binds = binds.dup
|
14
|
-
|
20
|
+
|
21
|
+
if connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
|
22
|
+
@values.compile(binds)
|
23
|
+
else
|
24
|
+
val = @values.dup
|
25
|
+
@indexes.each { |i| val[i] = connection.quote(*binds.shift.reverse) }
|
26
|
+
val.join
|
27
|
+
end
|
15
28
|
end
|
16
29
|
end
|
17
30
|
|
18
31
|
def self.partial_query(visitor, ast, collector)
|
19
32
|
collected = visitor.accept(ast, collector)
|
20
|
-
PartialQuery.new collected
|
33
|
+
PartialQuery.new(visitor.is_a?(Arel::Visitors::Sunstone) ? collected : collected.value, visitor.is_a?(Arel::Visitors::Sunstone))
|
21
34
|
end
|
22
35
|
|
23
36
|
end
|