ansr 0.0.1 → 0.0.3
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 +15 -0
- data/README.md +35 -0
- data/ansr.gemspec +2 -1
- data/ansr_blacklight/Gemfile +3 -0
- data/ansr_blacklight/README.md +37 -0
- data/ansr_blacklight/ansr_blacklight.gemspec +28 -0
- data/ansr_blacklight/lib/ansr_blacklight.rb +57 -0
- data/ansr_blacklight/lib/ansr_blacklight/arel.rb +6 -0
- data/ansr_blacklight/lib/ansr_blacklight/arel/big_table.rb +57 -0
- data/ansr_blacklight/lib/ansr_blacklight/arel/visitors.rb +6 -0
- data/ansr_blacklight/lib/ansr_blacklight/arel/visitors/query_builder.rb +217 -0
- data/ansr_blacklight/lib/ansr_blacklight/arel/visitors/to_no_sql.rb +14 -0
- data/ansr_blacklight/lib/ansr_blacklight/base.rb +21 -0
- data/ansr_blacklight/lib/ansr_blacklight/connection_adapters/no_sql_adapter.rb +38 -0
- data/ansr_blacklight/lib/ansr_blacklight/model/querying.rb +29 -0
- data/ansr_blacklight/lib/ansr_blacklight/relation.rb +50 -0
- data/ansr_blacklight/lib/ansr_blacklight/relation/solr_projection_methods.rb +55 -0
- data/ansr_blacklight/lib/ansr_blacklight/request_builders.rb +141 -0
- data/ansr_blacklight/lib/ansr_blacklight/solr.rb +4 -0
- data/ansr_blacklight/lib/ansr_blacklight/solr/request.rb +46 -0
- data/ansr_blacklight/lib/ansr_blacklight/solr/response.rb +94 -0
- data/ansr_blacklight/lib/ansr_blacklight/solr/response/group.rb +32 -0
- data/ansr_blacklight/lib/ansr_blacklight/solr/response/group_response.rb +50 -0
- data/ansr_blacklight/lib/ansr_blacklight/solr/response/more_like_this.rb +14 -0
- data/ansr_blacklight/lib/ansr_blacklight/solr/response/pagination_methods.rb +35 -0
- data/ansr_blacklight/lib/ansr_blacklight/solr/response/spelling.rb +92 -0
- data/ansr_blacklight/spec/fixtures/config.yml +0 -0
- data/ansr_blacklight/spec/lib/loaded_relation_spec.rb +223 -0
- data/ansr_blacklight/spec/lib/queryable_relation_spec.rb +133 -0
- data/ansr_blacklight/spec/lib/relation/faceting_spec.rb +475 -0
- data/ansr_blacklight/spec/lib/relation/grouping_spec.rb +159 -0
- data/ansr_blacklight/spec/spec_helper.rb +72 -0
- data/ansr_dpla/Gemfile +3 -0
- data/ansr_dpla/Gemfile.lock +138 -0
- data/ansr_dpla/README.md +2 -2
- data/ansr_dpla/ansr_dpla.gemspec +2 -2
- data/ansr_dpla/lib/ansr_dpla.rb +3 -0
- data/ansr_dpla/lib/ansr_dpla/api.rb +3 -3
- data/ansr_dpla/lib/ansr_dpla/arel.rb +1 -2
- data/ansr_dpla/lib/ansr_dpla/arel/big_table.rb +4 -0
- data/ansr_dpla/lib/ansr_dpla/arel/visitors.rb +6 -0
- data/ansr_dpla/lib/ansr_dpla/arel/visitors/query_builder.rb +188 -0
- data/ansr_dpla/lib/ansr_dpla/arel/visitors/to_no_sql.rb +9 -0
- data/ansr_dpla/lib/ansr_dpla/connection_adapters/no_sql_adapter.rb +58 -0
- data/ansr_dpla/lib/ansr_dpla/model/base.rb +7 -0
- data/ansr_dpla/lib/ansr_dpla/model/querying.rb +6 -10
- data/ansr_dpla/lib/ansr_dpla/relation.rb +61 -0
- data/ansr_dpla/lib/ansr_dpla/request.rb +5 -0
- data/ansr_dpla/spec/lib/api_spec.rb +8 -5
- data/ansr_dpla/spec/lib/item_spec.rb +2 -2
- data/ansr_dpla/spec/lib/relation/facet_spec.rb +27 -19
- data/ansr_dpla/spec/lib/relation/select_spec.rb +10 -8
- data/ansr_dpla/spec/lib/relation/where_spec.rb +1 -1
- data/ansr_dpla/spec/lib/relation_spec.rb +31 -29
- data/ansr_dpla/test/system.rb +4 -2
- data/lib/ansr.rb +7 -0
- data/lib/ansr/arel.rb +3 -0
- data/lib/ansr/arel/big_table.rb +43 -3
- data/lib/ansr/arel/configured_field.rb +19 -0
- data/lib/ansr/arel/nodes.rb +41 -0
- data/lib/ansr/arel/visitors.rb +7 -0
- data/lib/ansr/arel/visitors/context.rb +13 -0
- data/lib/ansr/arel/visitors/query_builder.rb +47 -0
- data/lib/ansr/arel/visitors/to_no_sql.rb +41 -0
- data/lib/ansr/base.rb +29 -1
- data/lib/ansr/configurable.rb +6 -12
- data/lib/ansr/connection_adapters.rb +5 -0
- data/lib/ansr/connection_adapters/no_sql_adapter.rb +80 -0
- data/lib/ansr/dummy_associations.rb +105 -0
- data/lib/ansr/facets.rb +103 -0
- data/lib/ansr/model.rb +17 -107
- data/lib/ansr/model/connection_handler.rb +6 -0
- data/lib/ansr/relation.rb +40 -23
- data/lib/ansr/relation/group.rb +31 -0
- data/lib/ansr/relation/predicate_builder.rb +106 -0
- data/lib/ansr/relation/query_methods.rb +192 -45
- data/lib/ansr/sanitization.rb +5 -18
- data/lib/ansr/utils.rb +89 -0
- data/lib/ansr/version.rb +1 -1
- metadata +73 -25
- data/ansr_dpla/lib/ansr_dpla/arel/connection.rb +0 -81
- data/ansr_dpla/lib/ansr_dpla/arel/query_builder.rb +0 -131
- data/lib/ansr/model/connection.rb +0 -103
data/lib/ansr/model.rb
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
module Ansr
|
|
2
2
|
module Model
|
|
3
|
-
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
module ClassMethods
|
|
4
5
|
def spawn
|
|
5
|
-
s =
|
|
6
|
+
s = build_default_scope
|
|
6
7
|
s.references!(references())
|
|
7
8
|
end
|
|
8
9
|
|
|
@@ -27,11 +28,12 @@ module Ansr
|
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
def table
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
type = (config[:table_class] || Ansr::Arel::BigTable)
|
|
32
|
+
if @table
|
|
33
|
+
# allow the table class to be reconfigured
|
|
34
|
+
@table = nil unless @table.class == type
|
|
35
|
+
end
|
|
36
|
+
@table ||= type.new(self)
|
|
35
37
|
end
|
|
36
38
|
|
|
37
39
|
def engine
|
|
@@ -46,114 +48,22 @@ module Ansr
|
|
|
46
48
|
Ansr::Relation.new(model(), table())
|
|
47
49
|
end
|
|
48
50
|
|
|
49
|
-
def
|
|
50
|
-
|
|
51
|
+
def column_types
|
|
52
|
+
TypeProxy.new(table())
|
|
51
53
|
end
|
|
52
|
-
end
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if ViewProxy === model
|
|
58
|
-
@model = model.model
|
|
59
|
-
self.constraints = Array(model.constraints)
|
|
60
|
-
self.projections = model.projections.dup
|
|
61
|
-
else
|
|
62
|
-
@model = model
|
|
63
|
-
end
|
|
64
|
-
if wheres[0]
|
|
65
|
-
self.constraints = self.constraints + wheres
|
|
55
|
+
class TypeProxy
|
|
56
|
+
def initialize(table)
|
|
57
|
+
@table = table
|
|
66
58
|
end
|
|
67
|
-
end
|
|
68
59
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
arel.project projections()
|
|
73
|
-
arel
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def find_by_nosql(arel, bind_values)
|
|
77
|
-
filter_context = nil
|
|
78
|
-
if self.view?
|
|
79
|
-
filter_context = view_arel(arel.engine, arel.source.left)
|
|
80
|
-
arel = arel.intersect filter_context
|
|
60
|
+
def [](name)
|
|
61
|
+
# this should delegate to the NoSqlAdapter
|
|
62
|
+
::ActiveRecord::ConnectionAdapters::Column.new(name.to_s, nil, String)
|
|
81
63
|
end
|
|
82
|
-
model.find_by_nosql(arel, bind_values)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def expand_hash_conditions_for_aggregates(attrs)
|
|
86
|
-
# this is a protected method in the AR sanitization module
|
|
87
|
-
model.send(:expand_hash_conditions_for_aggregates, attrs)
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def constraints
|
|
91
|
-
@constraints ||= []
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def constraints=(values)
|
|
95
|
-
@constraints = Array === (values) ? values : Array(values)
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def projections
|
|
99
|
-
@projections ||= []
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def projections=(values)
|
|
103
|
-
@projections = Array === (values) ? values : Array(values)
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def view?
|
|
107
|
-
(@constraints and @constraints.length > 0) or (@projections and @projections.length > 0)
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def view(*wheres)
|
|
111
|
-
ViewProxy.new(self, wheres)
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
def self.===(other)
|
|
115
|
-
other.is_a? ViewProxy
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def connection_handler=(handler)
|
|
119
|
-
@connection_handler = handler
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def build_default_scope
|
|
123
|
-
model().all
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
# model delegations
|
|
127
|
-
def connection
|
|
128
|
-
model().connection
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def name
|
|
132
|
-
model().name
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def table
|
|
136
|
-
model().table
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
def arel_table
|
|
140
|
-
model().arel_table
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def current_scope=(scope)
|
|
144
|
-
model().current_scope=(scope)
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
def current_scope
|
|
148
|
-
model().current_scope
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
def method_missing(method, *args, &block)
|
|
152
|
-
model().send(method, *args, &block)
|
|
153
64
|
end
|
|
154
65
|
end
|
|
155
66
|
|
|
156
|
-
require 'ansr/model/connection'
|
|
157
67
|
require 'ansr/model/connection_handler'
|
|
158
68
|
end
|
|
159
69
|
end
|
data/lib/ansr/relation.rb
CHANGED
|
@@ -2,12 +2,15 @@ require 'yaml'
|
|
|
2
2
|
require 'blacklight'
|
|
3
3
|
module Ansr
|
|
4
4
|
class Relation < ::ActiveRecord::Relation
|
|
5
|
+
attr_reader :response
|
|
5
6
|
attr_accessor :filters, :count, :context, :resource
|
|
6
7
|
|
|
7
8
|
DEFAULT_PAGE_SIZE = 10
|
|
8
9
|
|
|
9
10
|
include Sanitization::ClassMethods
|
|
10
11
|
include QueryMethods
|
|
12
|
+
include Kaminari::PageScopeMethods
|
|
13
|
+
alias :total_count :count
|
|
11
14
|
|
|
12
15
|
def initialize(klass, table, values = {})
|
|
13
16
|
raise "Cannot search nil model" if klass.nil?
|
|
@@ -20,6 +23,10 @@ module Ansr
|
|
|
20
23
|
rsrc.to_sym
|
|
21
24
|
end
|
|
22
25
|
|
|
26
|
+
def default_limit_value
|
|
27
|
+
DEFAULT_PAGE_SIZE
|
|
28
|
+
end
|
|
29
|
+
|
|
23
30
|
def load
|
|
24
31
|
exec_queries unless loaded?
|
|
25
32
|
self
|
|
@@ -46,35 +53,36 @@ module Ansr
|
|
|
46
53
|
end
|
|
47
54
|
|
|
48
55
|
def offset!(value)
|
|
49
|
-
page_size = self.limit_value || DEFAULT_PAGE_SIZE
|
|
50
|
-
if (value.to_i % page_size.to_i) != 0
|
|
51
|
-
raise "Bad offset #{value} for page size #{page_size}"
|
|
52
|
-
end
|
|
53
56
|
self.offset_value=value
|
|
54
57
|
self
|
|
55
58
|
end
|
|
56
59
|
|
|
57
|
-
def count
|
|
60
|
+
def count()
|
|
58
61
|
self.load
|
|
59
|
-
@response
|
|
62
|
+
@response.count
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def total_count
|
|
66
|
+
count
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def max_pages
|
|
70
|
+
(total_count.to_f / limit_value).ceil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# override to parse filters from response
|
|
74
|
+
def filters_from(response)
|
|
75
|
+
{} and raise "this is a dead method!"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# override to parse docs from response
|
|
79
|
+
def docs_from(response)
|
|
80
|
+
[]
|
|
60
81
|
end
|
|
61
82
|
|
|
62
83
|
def filters
|
|
63
84
|
if loaded?
|
|
64
|
-
@filter_cache
|
|
65
|
-
f = {}
|
|
66
|
-
(@response['facets'] || {}).inject(f) do |h,(k,v)|
|
|
67
|
-
if v['total'] = 0
|
|
68
|
-
items = v['terms'].collect do |term|
|
|
69
|
-
Blacklight::SolrResponse::Facets::FacetItem.new(:value => term['term'], :hits => term['count'])
|
|
70
|
-
end
|
|
71
|
-
options = {:sort => 'asc', :offset => 0}
|
|
72
|
-
h[k] = Blacklight::SolrResponse::Facets::FacetField.new k, items, options
|
|
73
|
-
end
|
|
74
|
-
h
|
|
75
|
-
end
|
|
76
|
-
f
|
|
77
|
-
end
|
|
85
|
+
@filter_cache = filters_from(response)
|
|
78
86
|
else
|
|
79
87
|
@filter_cache ||= begin
|
|
80
88
|
query = self.limit(0)
|
|
@@ -92,13 +100,23 @@ module Ansr
|
|
|
92
100
|
|
|
93
101
|
private
|
|
94
102
|
|
|
103
|
+
# override to prevent default selection of all fields
|
|
104
|
+
def build_select(arel, selects)
|
|
105
|
+
unless selects.empty?
|
|
106
|
+
@implicit_readonly = false
|
|
107
|
+
arel.project(*selects)
|
|
108
|
+
#else
|
|
109
|
+
# arel.project(@klass.arel_table[Arel.star])
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
95
113
|
|
|
96
114
|
def exec_queries
|
|
97
115
|
default_scoped = with_default_scope
|
|
98
116
|
|
|
99
117
|
if default_scoped.equal?(self)
|
|
100
118
|
@response = model.find_by_nosql(arel, bind_values)
|
|
101
|
-
@records = (@response
|
|
119
|
+
@records = docs_from(@response).collect do |d|
|
|
102
120
|
model.new(d)
|
|
103
121
|
end
|
|
104
122
|
|
|
@@ -109,7 +127,7 @@ module Ansr
|
|
|
109
127
|
@records = default_scoped.to_a
|
|
110
128
|
end
|
|
111
129
|
|
|
112
|
-
self.limit_value =
|
|
130
|
+
self.limit_value = default_limit_value unless self.limit_value
|
|
113
131
|
self.offset_value = 0 unless self.offset_value
|
|
114
132
|
@filter_cache = nil # unload any cached filters
|
|
115
133
|
@loaded = true
|
|
@@ -117,5 +135,4 @@ module Ansr
|
|
|
117
135
|
end
|
|
118
136
|
end
|
|
119
137
|
|
|
120
|
-
|
|
121
138
|
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# encapsulate a set of a response documents grouped on a field
|
|
2
|
+
module Ansr
|
|
3
|
+
class Group
|
|
4
|
+
attr_reader :field, :key, :group, :model
|
|
5
|
+
def initialize(group_key, model, group)
|
|
6
|
+
@field, @key = group_key.first
|
|
7
|
+
@model = model
|
|
8
|
+
@group = group
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# size of the group
|
|
12
|
+
def total
|
|
13
|
+
raise "Group#total must be implemented by subclass"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# offset in the response
|
|
17
|
+
def start
|
|
18
|
+
raise "Group#start must be implemented by subclass"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# model instances belonging to this group
|
|
22
|
+
def records
|
|
23
|
+
raise "Group#records must be implemented by subclass"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# the field from which the key value was selected
|
|
27
|
+
def field
|
|
28
|
+
raise "Group#field must be implemented by subclass"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
module Ansr
|
|
2
|
+
class PredicateBuilder # :nodoc:
|
|
3
|
+
def self.build_from_hash(klass, attributes, default_table)
|
|
4
|
+
queries = []
|
|
5
|
+
|
|
6
|
+
attributes.each do |column, value|
|
|
7
|
+
table = default_table
|
|
8
|
+
|
|
9
|
+
if value.is_a?(Hash)
|
|
10
|
+
if value.empty?
|
|
11
|
+
queries << '1=0'
|
|
12
|
+
else
|
|
13
|
+
table = default_table.class.new(column, default_table.engine)
|
|
14
|
+
association = klass.reflect_on_association(column.to_sym)
|
|
15
|
+
|
|
16
|
+
value.each do |k, v|
|
|
17
|
+
queries.concat expand(association && association.klass, table, k, v)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
else
|
|
21
|
+
column = column.to_s
|
|
22
|
+
# we remove the below since big table fields don't really have associations
|
|
23
|
+
# if column.include?('.')
|
|
24
|
+
# table_name, column = column.split('.', 2)
|
|
25
|
+
# table = Ansr::Arel::BigTable.new(table_name, default_table.engine)
|
|
26
|
+
# end
|
|
27
|
+
|
|
28
|
+
queries.concat expand(klass, table, column, value)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
queries
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.expand(klass, table, column, value)
|
|
36
|
+
queries = []
|
|
37
|
+
|
|
38
|
+
# Find the foreign key when using queries such as:
|
|
39
|
+
# Post.where(author: author)
|
|
40
|
+
#
|
|
41
|
+
# For polymorphic relationships, find the foreign key and type:
|
|
42
|
+
# PriceEstimate.where(estimate_of: treasure)
|
|
43
|
+
if klass && value.class < Base && reflection = klass.reflect_on_association(column.to_sym)
|
|
44
|
+
if reflection.polymorphic?
|
|
45
|
+
queries << build(table[reflection.foreign_type], value.class.base_class)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
column = reflection.foreign_key
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
queries << build(table[column], value)
|
|
52
|
+
queries
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.references(attributes)
|
|
56
|
+
attributes.map do |key, value|
|
|
57
|
+
if value.is_a?(Hash)
|
|
58
|
+
key
|
|
59
|
+
else
|
|
60
|
+
key = key.to_s
|
|
61
|
+
key.split('.').first if key.include?('.')
|
|
62
|
+
end
|
|
63
|
+
end.compact
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
def self.build(attribute, value)
|
|
68
|
+
case value
|
|
69
|
+
when Array
|
|
70
|
+
values = value.to_a.map {|x| x.is_a?(Base) ? x.id : x}
|
|
71
|
+
ranges, values = values.partition {|v| v.is_a?(Range)}
|
|
72
|
+
|
|
73
|
+
values_predicate = if values.include?(nil)
|
|
74
|
+
values = values.compact
|
|
75
|
+
|
|
76
|
+
case values.length
|
|
77
|
+
when 0
|
|
78
|
+
attribute.eq(nil)
|
|
79
|
+
when 1
|
|
80
|
+
attribute.eq(values.first).or(attribute.eq(nil))
|
|
81
|
+
else
|
|
82
|
+
attribute.in(values).or(attribute.eq(nil))
|
|
83
|
+
end
|
|
84
|
+
else
|
|
85
|
+
attribute.in(values)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
array_predicates = ranges.map { |range| attribute.in(range) }
|
|
89
|
+
array_predicates << values_predicate
|
|
90
|
+
array_predicates.inject { |composite, predicate| composite.or(predicate) }
|
|
91
|
+
when ActiveRecord::Relation
|
|
92
|
+
value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
|
|
93
|
+
attribute.in(value.arel.ast)
|
|
94
|
+
when Range
|
|
95
|
+
attribute.in(value)
|
|
96
|
+
when ActiveRecord::Base
|
|
97
|
+
attribute.eq(value.id)
|
|
98
|
+
when Class
|
|
99
|
+
# FIXME: I think we need to deprecate this behavior
|
|
100
|
+
attribute.eq(value.name)
|
|
101
|
+
else
|
|
102
|
+
attribute.eq(value)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -9,9 +9,8 @@ module Ansr
|
|
|
9
9
|
where_value = @scope.send(:build_where, opts, rest).map do |rel|
|
|
10
10
|
case rel
|
|
11
11
|
when ::Arel::Nodes::In
|
|
12
|
-
|
|
12
|
+
::Arel::Nodes::Or.new(rel.left, rel.right)
|
|
13
13
|
when ::Arel::Nodes::Equality
|
|
14
|
-
# ::Arel::Nodes::OrEqual.new(rel.left, rel.right)
|
|
15
14
|
::Arel::Nodes::Or.new(rel.left, rel.right)
|
|
16
15
|
when String
|
|
17
16
|
::Arel::Nodes::Or.new(::Arel::Nodes::SqlLiteral.new(rel))
|
|
@@ -34,52 +33,164 @@ module Ansr
|
|
|
34
33
|
@values[:filter] = values
|
|
35
34
|
end
|
|
36
35
|
|
|
37
|
-
def filter(
|
|
38
|
-
check_if_method_has_arguments!("filter",
|
|
39
|
-
spawn.filter!(
|
|
36
|
+
def filter(expr)
|
|
37
|
+
check_if_method_has_arguments!("filter", expr)
|
|
38
|
+
spawn.filter!(expr)
|
|
40
39
|
end
|
|
41
40
|
|
|
42
|
-
def filter!(
|
|
43
|
-
return self if
|
|
41
|
+
def filter!(expr)
|
|
42
|
+
return self if expr.empty?
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
return self unless
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
filter_nodes = build_where(expr)
|
|
45
|
+
return self unless filter_nodes
|
|
46
|
+
filters = []
|
|
47
|
+
filter_nodes.each do |filter_node|
|
|
48
|
+
case filter_node
|
|
49
|
+
when ::Arel::Nodes::In, ::Arel::Nodes::Equality
|
|
50
|
+
filter_node.left = Ansr::Arel::Nodes::Filter.new(filter_node.left)
|
|
51
|
+
when ::Arel::SqlLiteral
|
|
52
|
+
filter_node = Ansr::Arel::Nodes::Filter.new(filter_node)
|
|
53
|
+
when String, Symbol
|
|
54
|
+
filter_node = Ansr::Arel::Nodes::Filter.new(::Arel::SqlLiteral.new(filter_node.to_s))
|
|
55
|
+
else
|
|
56
|
+
raise "unexpected filter node type #{filter_node.class}"
|
|
57
|
+
end
|
|
58
|
+
filters << filter_node
|
|
51
59
|
end
|
|
60
|
+
self.filter_values+= filters
|
|
52
61
|
|
|
53
62
|
self
|
|
54
63
|
end
|
|
55
64
|
|
|
65
|
+
def filter_unscoping(target_value)
|
|
66
|
+
target_value_sym = target_value.to_sym
|
|
67
|
+
|
|
68
|
+
filter_values.reject! do |rel|
|
|
69
|
+
case rel
|
|
70
|
+
when ::Arel::Nodes::In, ::Arel::Nodes::Equality
|
|
71
|
+
subrelation = (rel.left.kind_of?(::Arel::Attributes::Attribute) ? rel.left : rel.right)
|
|
72
|
+
subrelation.name.to_sym == target_value_sym
|
|
73
|
+
else
|
|
74
|
+
raise "unscope(filter: #{target_value.inspect}) failed: unscoping #{rel.class} is unimplemented."
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def facet(expr, opts = {})
|
|
80
|
+
spawn.facet!(expr, opts)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def facet!(expr, opts={})
|
|
84
|
+
self.facet_values+= build_facets(expr, opts)
|
|
85
|
+
self
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def facet_values
|
|
89
|
+
@values[:facets] || []
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def facet_values=(values)
|
|
93
|
+
raise ActiveRecord::ImmutableRelation if @loaded
|
|
94
|
+
@values[:facets]=values
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def facet_unscoping(target_value)
|
|
98
|
+
end
|
|
99
|
+
|
|
56
100
|
def filter_name(expr)
|
|
57
101
|
connection.sanitize_filter_name(field_name(expr))
|
|
58
102
|
end
|
|
59
103
|
|
|
104
|
+
def as(args)
|
|
105
|
+
spawn.as!(args)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def as!(args)
|
|
109
|
+
self.as_value= args
|
|
110
|
+
self
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def as_value
|
|
114
|
+
@values[:as]
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def as_value=(args)
|
|
118
|
+
raise ActiveRecord::ImmutableRelation if @loaded
|
|
119
|
+
@values[:as] = args
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def as_unscoping(*args)
|
|
123
|
+
@values.delete(:as)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def highlight(expr, opts={})
|
|
127
|
+
spawn.highlight!(expr, opts)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def highlight!(expr, opts = {})
|
|
131
|
+
self.highlight_value= Ansr::Arel::Nodes::Highlight.new(expr, opts)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def highlight_value
|
|
135
|
+
@values[:highlight]
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def highlight_value=(val)
|
|
139
|
+
raise ActiveRecord::ImmutableRelation if @loaded
|
|
140
|
+
@values[:highlight] = val
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def highlight_unscoping(*args)
|
|
144
|
+
@values.delete(:highlight)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def spellcheck(expr, opts={})
|
|
148
|
+
spawn.spellcheck!(expr, opts)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def spellcheck!(expr, opts = {})
|
|
152
|
+
self.spellcheck_value= Ansr::Arel::Nodes::Spellcheck.new(expr, opts)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def spellcheck_value
|
|
156
|
+
@values[:spellcheck]
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def spellcheck_value=(val)
|
|
160
|
+
raise ActiveRecord::ImmutableRelation if @loaded
|
|
161
|
+
@values[:spellcheck] = val
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def spellcheck_unscoping(*args)
|
|
165
|
+
@values.delete(:spellcheck)
|
|
166
|
+
end
|
|
167
|
+
|
|
60
168
|
def field_name(expr)
|
|
61
169
|
if expr.is_a? Array
|
|
62
170
|
return expr.collect{|x| field_name(x)}.compact
|
|
63
171
|
else
|
|
64
172
|
case expr
|
|
65
173
|
when ::Arel::Nodes::Binary
|
|
66
|
-
if expr.left.relation.name != model().name
|
|
174
|
+
if expr.left.relation.name != model().table.name
|
|
67
175
|
# oof, this is really hacky
|
|
68
176
|
field_name = "#{expr.left.relation.name}.#{expr.left.name}".to_sym
|
|
177
|
+
field_name = expr.left.name.to_sym
|
|
69
178
|
else
|
|
70
179
|
field_name = expr.left.name.to_sym
|
|
71
180
|
end
|
|
72
|
-
when ::Arel::Attributes::Attribute
|
|
73
|
-
if expr.relation.name != model().name
|
|
181
|
+
when ::Arel::Attributes::Attribute, Ansr::Arel::ConfiguredField
|
|
182
|
+
if expr.relation.name != model().table.name
|
|
74
183
|
# oof, this is really hacky
|
|
75
184
|
field_name = "#{expr.relation.name}.#{expr.name}".to_sym
|
|
185
|
+
field_name = expr.name.to_sym
|
|
76
186
|
else
|
|
77
187
|
field_name = expr.name.to_sym
|
|
78
188
|
end
|
|
79
|
-
when ::Arel::Nodes::Unary
|
|
80
|
-
if expr.expr.relation.name != model().name
|
|
189
|
+
when ::Arel::Nodes::Unary, Ansr::Arel::Nodes::Filter
|
|
190
|
+
if expr.expr.relation.name != model().table.name
|
|
81
191
|
# oof, this is really hacky
|
|
82
192
|
field_name = "#{expr.expr.relation.name}.#{expr.expr.name}".to_sym
|
|
193
|
+
field_name = expr.expr.name.to_sym
|
|
83
194
|
else
|
|
84
195
|
field_name = expr.expr.name.to_sym
|
|
85
196
|
end
|
|
@@ -100,7 +211,7 @@ module Ansr
|
|
|
100
211
|
|
|
101
212
|
def find(id)
|
|
102
213
|
klass = model()
|
|
103
|
-
rel =
|
|
214
|
+
rel = where(klass.table.primary_key.name => id).limit(1)
|
|
104
215
|
rel.to_a
|
|
105
216
|
unless rel.to_a[0]
|
|
106
217
|
raise 'Bad ID'
|
|
@@ -111,46 +222,82 @@ module Ansr
|
|
|
111
222
|
def collapse_wheres(arel, wheres)
|
|
112
223
|
predicates = wheres.map do |where|
|
|
113
224
|
next where if ::Arel::Nodes::Equality === where
|
|
114
|
-
where if String === where
|
|
225
|
+
where = Arel.sql(where) if String === where # SqlLiteral-ize
|
|
115
226
|
::Arel::Nodes::Grouping.new(where)
|
|
116
227
|
end
|
|
117
228
|
|
|
118
229
|
arel.where(::Arel::Nodes::And.new(predicates)) if predicates.present?
|
|
119
230
|
end
|
|
120
231
|
|
|
232
|
+
def collapse_filters(arel, filters)
|
|
233
|
+
predicates = filters.map do |filter|
|
|
234
|
+
next filter if ::Arel::Nodes::Equality === filter
|
|
235
|
+
filter = Arel.sql(filter) if String === filter # SqlLiteral-ize
|
|
236
|
+
::Arel::Nodes::Grouping.new(filter)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
arel.where(::Arel::Nodes::And.new(predicates)) if predicates.present?
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Could filtering be moved out of intersection into one arel tree?
|
|
243
|
+
def build_arel
|
|
244
|
+
arel = super
|
|
245
|
+
collapse_filters(arel, (filter_values).uniq)
|
|
246
|
+
arel.projections << @values[:spellcheck] if @values[:spellcheck]
|
|
247
|
+
arel.projections << @values[:highlight] if @values[:highlight]
|
|
248
|
+
arel.projections += facet_values
|
|
249
|
+
arel.from arel.create_table_alias(arel.source.left, as_value) if as_value
|
|
250
|
+
arel
|
|
251
|
+
end
|
|
252
|
+
|
|
121
253
|
# cloning from ActiveRecord::QueryMethods.build_where as a first pass
|
|
122
|
-
def
|
|
254
|
+
def build_facets(expr, opts={})
|
|
255
|
+
case expr
|
|
256
|
+
when Hash
|
|
257
|
+
build_facets(::Arel.star,expr)
|
|
258
|
+
when Array
|
|
259
|
+
r = expr.inject([]) {|m,e| m.concat build_facets(e,opts)}
|
|
260
|
+
when String, Symbol, Arel::SqlLiteral
|
|
261
|
+
[Ansr::Arel::Nodes::Facet.new(::Arel::Attributes::Attribute.new(table, expr.to_s), opts)]
|
|
262
|
+
when ::Arel::Attributes::Attribute
|
|
263
|
+
[Ansr::Arel::Nodes::Facet.new(expr, opts)]
|
|
264
|
+
else
|
|
265
|
+
[expr]
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# cloning from ActiveRecord::QueryMethods.build_where to use our PredicateBuilder
|
|
270
|
+
def build_where(opts, other = [])
|
|
123
271
|
case opts
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
272
|
+
when String, Array
|
|
273
|
+
#TODO: Remove duplication with: /activerecord/lib/active_record/sanitization.rb:113
|
|
274
|
+
values = Hash === other.first ? other.first.values : other
|
|
127
275
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
opts = (other.empty? ? opts : ([opts] + other))
|
|
132
|
-
[model().send(:sanitize_sql, opts, model().table_name)]
|
|
133
|
-
when Hash
|
|
134
|
-
attributes = model().send(:expand_hash_conditions_for_sql_aggregates, opts)
|
|
135
|
-
|
|
136
|
-
attributes.keys.each do |k|
|
|
137
|
-
sk = filter_name(k)
|
|
138
|
-
attributes[sk] = attributes.delete(k) unless sk.eql? k.to_s
|
|
139
|
-
end
|
|
140
|
-
attributes.values.grep(ActiveRecord::Relation) do |rel|
|
|
141
|
-
self.bind_values += rel.bind_values
|
|
142
|
-
end
|
|
276
|
+
values.grep(ActiveRecord::Relation) do |rel|
|
|
277
|
+
self.bind_values += rel.bind_values
|
|
278
|
+
end
|
|
143
279
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
280
|
+
[@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
|
|
281
|
+
when Hash
|
|
282
|
+
attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
|
|
283
|
+
|
|
284
|
+
attributes.values.grep(ActiveRecord::Relation) do |rel|
|
|
285
|
+
self.bind_values += rel.bind_values
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
PredicateBuilder.build_from_hash(klass, attributes, table)
|
|
289
|
+
else
|
|
290
|
+
[opts]
|
|
147
291
|
end
|
|
148
292
|
end
|
|
149
293
|
|
|
150
294
|
def find_by_nosql(arel, bind_values)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
295
|
+
@ansr_query = model.connection.to_nosql(arel, bind_values)
|
|
296
|
+
model.connection.execute(@ansr_query)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def ansr_qeury
|
|
300
|
+
@ansr_query
|
|
154
301
|
end
|
|
155
302
|
end
|
|
156
303
|
end
|