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.
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