ansr 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +15 -0
  2. data/README.md +35 -0
  3. data/ansr.gemspec +2 -1
  4. data/ansr_blacklight/Gemfile +3 -0
  5. data/ansr_blacklight/README.md +37 -0
  6. data/ansr_blacklight/ansr_blacklight.gemspec +28 -0
  7. data/ansr_blacklight/lib/ansr_blacklight.rb +57 -0
  8. data/ansr_blacklight/lib/ansr_blacklight/arel.rb +6 -0
  9. data/ansr_blacklight/lib/ansr_blacklight/arel/big_table.rb +57 -0
  10. data/ansr_blacklight/lib/ansr_blacklight/arel/visitors.rb +6 -0
  11. data/ansr_blacklight/lib/ansr_blacklight/arel/visitors/query_builder.rb +217 -0
  12. data/ansr_blacklight/lib/ansr_blacklight/arel/visitors/to_no_sql.rb +14 -0
  13. data/ansr_blacklight/lib/ansr_blacklight/base.rb +21 -0
  14. data/ansr_blacklight/lib/ansr_blacklight/connection_adapters/no_sql_adapter.rb +38 -0
  15. data/ansr_blacklight/lib/ansr_blacklight/model/querying.rb +29 -0
  16. data/ansr_blacklight/lib/ansr_blacklight/relation.rb +50 -0
  17. data/ansr_blacklight/lib/ansr_blacklight/relation/solr_projection_methods.rb +55 -0
  18. data/ansr_blacklight/lib/ansr_blacklight/request_builders.rb +141 -0
  19. data/ansr_blacklight/lib/ansr_blacklight/solr.rb +4 -0
  20. data/ansr_blacklight/lib/ansr_blacklight/solr/request.rb +46 -0
  21. data/ansr_blacklight/lib/ansr_blacklight/solr/response.rb +94 -0
  22. data/ansr_blacklight/lib/ansr_blacklight/solr/response/group.rb +32 -0
  23. data/ansr_blacklight/lib/ansr_blacklight/solr/response/group_response.rb +50 -0
  24. data/ansr_blacklight/lib/ansr_blacklight/solr/response/more_like_this.rb +14 -0
  25. data/ansr_blacklight/lib/ansr_blacklight/solr/response/pagination_methods.rb +35 -0
  26. data/ansr_blacklight/lib/ansr_blacklight/solr/response/spelling.rb +92 -0
  27. data/ansr_blacklight/spec/fixtures/config.yml +0 -0
  28. data/ansr_blacklight/spec/lib/loaded_relation_spec.rb +223 -0
  29. data/ansr_blacklight/spec/lib/queryable_relation_spec.rb +133 -0
  30. data/ansr_blacklight/spec/lib/relation/faceting_spec.rb +475 -0
  31. data/ansr_blacklight/spec/lib/relation/grouping_spec.rb +159 -0
  32. data/ansr_blacklight/spec/spec_helper.rb +72 -0
  33. data/ansr_dpla/Gemfile +3 -0
  34. data/ansr_dpla/Gemfile.lock +138 -0
  35. data/ansr_dpla/README.md +2 -2
  36. data/ansr_dpla/ansr_dpla.gemspec +2 -2
  37. data/ansr_dpla/lib/ansr_dpla.rb +3 -0
  38. data/ansr_dpla/lib/ansr_dpla/api.rb +3 -3
  39. data/ansr_dpla/lib/ansr_dpla/arel.rb +1 -2
  40. data/ansr_dpla/lib/ansr_dpla/arel/big_table.rb +4 -0
  41. data/ansr_dpla/lib/ansr_dpla/arel/visitors.rb +6 -0
  42. data/ansr_dpla/lib/ansr_dpla/arel/visitors/query_builder.rb +188 -0
  43. data/ansr_dpla/lib/ansr_dpla/arel/visitors/to_no_sql.rb +9 -0
  44. data/ansr_dpla/lib/ansr_dpla/connection_adapters/no_sql_adapter.rb +58 -0
  45. data/ansr_dpla/lib/ansr_dpla/model/base.rb +7 -0
  46. data/ansr_dpla/lib/ansr_dpla/model/querying.rb +6 -10
  47. data/ansr_dpla/lib/ansr_dpla/relation.rb +61 -0
  48. data/ansr_dpla/lib/ansr_dpla/request.rb +5 -0
  49. data/ansr_dpla/spec/lib/api_spec.rb +8 -5
  50. data/ansr_dpla/spec/lib/item_spec.rb +2 -2
  51. data/ansr_dpla/spec/lib/relation/facet_spec.rb +27 -19
  52. data/ansr_dpla/spec/lib/relation/select_spec.rb +10 -8
  53. data/ansr_dpla/spec/lib/relation/where_spec.rb +1 -1
  54. data/ansr_dpla/spec/lib/relation_spec.rb +31 -29
  55. data/ansr_dpla/test/system.rb +4 -2
  56. data/lib/ansr.rb +7 -0
  57. data/lib/ansr/arel.rb +3 -0
  58. data/lib/ansr/arel/big_table.rb +43 -3
  59. data/lib/ansr/arel/configured_field.rb +19 -0
  60. data/lib/ansr/arel/nodes.rb +41 -0
  61. data/lib/ansr/arel/visitors.rb +7 -0
  62. data/lib/ansr/arel/visitors/context.rb +13 -0
  63. data/lib/ansr/arel/visitors/query_builder.rb +47 -0
  64. data/lib/ansr/arel/visitors/to_no_sql.rb +41 -0
  65. data/lib/ansr/base.rb +29 -1
  66. data/lib/ansr/configurable.rb +6 -12
  67. data/lib/ansr/connection_adapters.rb +5 -0
  68. data/lib/ansr/connection_adapters/no_sql_adapter.rb +80 -0
  69. data/lib/ansr/dummy_associations.rb +105 -0
  70. data/lib/ansr/facets.rb +103 -0
  71. data/lib/ansr/model.rb +17 -107
  72. data/lib/ansr/model/connection_handler.rb +6 -0
  73. data/lib/ansr/relation.rb +40 -23
  74. data/lib/ansr/relation/group.rb +31 -0
  75. data/lib/ansr/relation/predicate_builder.rb +106 -0
  76. data/lib/ansr/relation/query_methods.rb +192 -45
  77. data/lib/ansr/sanitization.rb +5 -18
  78. data/lib/ansr/utils.rb +89 -0
  79. data/lib/ansr/version.rb +1 -1
  80. metadata +73 -25
  81. data/ansr_dpla/lib/ansr_dpla/arel/connection.rb +0 -81
  82. data/ansr_dpla/lib/ansr_dpla/arel/query_builder.rb +0 -131
  83. data/lib/ansr/model/connection.rb +0 -103
@@ -1,8 +1,9 @@
1
1
  module Ansr
2
2
  module Model
3
- module Methods
3
+ extend ActiveSupport::Concern
4
+ module ClassMethods
4
5
  def spawn
5
- s = Ansr::Relation.new(model(), table())
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
- raise 'Implementing classes must provide a BigTable reader'
31
- end
32
-
33
- def table=(table)
34
- raise 'Implementing classes must provide a BigTable writer'
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 view(*wheres)
50
- return ViewProxy.new(model(), *wheres)
51
+ def column_types
52
+ TypeProxy.new(table())
51
53
  end
52
- end
53
54
 
54
- class ViewProxy
55
- attr_accessor :model
56
- def initialize(model, *wheres)
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
- def view_arel(engine, table)
70
- arel = ::Arel::SelectManager.new(engine, table)
71
- constraints.each {|c| arel.where(c) }
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
@@ -4,6 +4,12 @@ module Ansr
4
4
  def initialize(connection_class)
5
5
  @connection_class = connection_class
6
6
  end
7
+
8
+ def adapter
9
+ @connection_class
10
+ end
11
+
12
+ # retrieve a datasource adapter
7
13
  def retrieve_connection(klass)
8
14
  @connection_class.new(klass)
9
15
  end
@@ -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['count']
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 ||= begin
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['docs'] || []).collect do |d|
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 = DEFAULT_PAGE_SIZE unless 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
- next rel
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(args)
38
- check_if_method_has_arguments!("filter", args)
39
- spawn.filter!(args)
36
+ def filter(expr)
37
+ check_if_method_has_arguments!("filter", expr)
38
+ spawn.filter!(expr)
40
39
  end
41
40
 
42
- def filter!(args)
43
- return self if args.empty?
41
+ def filter!(expr)
42
+ return self if expr.empty?
44
43
 
45
- filter_where = build_filter(args)
46
- return self unless filter_where
47
- filter_name = filter_name(filter_where)
48
- if (filter_name)
49
- @klass = @klass.view(*filter_where)
50
- model().projections += Array(filter_name)
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 = klass.build_default_scope.where(klass.table.primary_key.name => id).limit(1)
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 build_filter(opts, other=[])
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
- when String, Array
125
- #TODO: Remove duplication with: /activerecord/lib/active_record/sanitization.rb:113
126
- values = Hash === other.first ? other.first.values : other
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
- values.grep(ActiveRecord::Relation) do |rel|
129
- self.bind_values += rel.bind_values
130
- end
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
- ActiveRecord::PredicateBuilder.build_from_hash(model(), attributes, model().table)
145
- else
146
- [opts]
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
- query = model.connection.to_nosql(arel, bind_values)
152
- aliases = model.connection.to_aliases(arel, bind_values)
153
- model.connection.execute(query, aliases)
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