ansr 0.0.1 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|