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
@@ -0,0 +1,7 @@
1
+ module Ansr::Arel
2
+ module Visitors
3
+ require 'ansr/arel/visitors/context'
4
+ require 'ansr/arel/visitors/query_builder'
5
+ require 'ansr/arel/visitors/to_no_sql'
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ module Ansr::Arel::Visitors
2
+ class Context
3
+ attr_reader :attribute
4
+ def initialize(attribute)
5
+ @attribute = attribute
6
+ end
7
+ end
8
+
9
+ # create some thin subclasses in this module
10
+ %W(Facet Filter From Order).each do |name|
11
+ const_set(name, Class.new(Context))
12
+ end
13
+ end
@@ -0,0 +1,47 @@
1
+ module Ansr::Arel::Visitors
2
+ class QueryBuilder < Arel::Visitors::Visitor
3
+ attr_reader :table
4
+ def initialize(table)
5
+ @table = table
6
+ end
7
+
8
+ def visit(object, attribute=nil)
9
+ super(object, attribute)
10
+ end
11
+
12
+ def visit_Ansr_Arel_BigTable(object, attribute)
13
+ visit object.name, attribute if Ansr::Arel::Visitors::From === attribute
14
+ @table = object if Ansr::Arel::BigTable === object and Ansr::Arel::Visitors::From === attribute
15
+ end
16
+
17
+ def visit_Arel_Nodes_SelectCore(object, attribute)
18
+ visit(object.froms, From.new(attribute)) if object.froms
19
+ object.projections.each { |x| visit(x, attribute) }
20
+ object.wheres.each { |x| visit(x, attribute) }
21
+ object.groups.each {|x| visit(x, attribute) if x}
22
+ self
23
+ end
24
+
25
+ def visit_Symbol o, a
26
+ visit o.to_s, a
27
+ end
28
+
29
+ def visit_Array o, a
30
+ o.map { |x| visit x, a }
31
+ end
32
+
33
+ def visit_Arel_Nodes_And(object, attribute)
34
+ visit(object.children, attribute)
35
+ end
36
+
37
+ def field_key_from_node(node)
38
+ table.model.field_name(node)
39
+ end
40
+
41
+ # determines whether multiple values should accumulate or overwrite in merges
42
+ def multiple?(field_key)
43
+ false
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,41 @@
1
+ module Ansr::Arel::Visitors
2
+ class ToNoSql < Arel::Visitors::Visitor
3
+ attr_reader :table
4
+
5
+ def initialize(big_table)
6
+ super()
7
+ @table = big_table
8
+ end
9
+
10
+ def query_builder(opts = nil)
11
+ Ansr::Arel::Visitors::QueryBuilder.new(table, opts)
12
+ end
13
+
14
+ # the object generated by this method will be passed to the NoSqlAdapter#execute
15
+ def visit_Arel_Nodes_SelectStatement(object, attribute)
16
+ builder = query_builder
17
+
18
+ if object.with
19
+ builder.visit(object.with, attribute)
20
+ end
21
+
22
+ object.cores.each { |x| builder.visit_Arel_Nodes_SelectCore(x, attribute) }
23
+
24
+ unless object.orders.empty?
25
+
26
+ object.orders.each do |x|
27
+ oa = Ansr::Arel::Visitors::Order.new(attribute)
28
+ builder.visit x, oa
29
+ end
30
+ end
31
+
32
+ builder.visit(object.limit, attribute) if object.limit
33
+ builder.visit(object.offset, attribute) if object.offset
34
+ # not relevant
35
+
36
+ builder.query_opts
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -1,10 +1,13 @@
1
+ require 'active_record'
1
2
  module Ansr
2
3
  class Base < ActiveRecord::Base
3
- extend Ansr::Model::Methods
4
+ include Ansr::Model
4
5
  extend Ansr::Configurable
5
6
  extend Ansr::QueryMethods
6
7
  extend Ansr::ArelMethods
7
8
  include Ansr::Sanitization
9
+ #TODO remove the dummy associations
10
+ include Ansr::DummyAssociations
8
11
 
9
12
  self.abstract_class = true
10
13
 
@@ -13,6 +16,27 @@ module Ansr
13
16
  @source_doc = doc
14
17
  end
15
18
 
19
+ def core_initialize(attributes = nil, options = {})
20
+ defaults = self.class.column_defaults.dup
21
+ defaults.each { |k, v| defaults[k] = v.dup if v.duplicable? }
22
+
23
+ @attributes = self.class.initialize_attributes(defaults)
24
+ @column_types_override = nil
25
+ @column_types = self.class.column_types
26
+
27
+ init_internals
28
+ init_changed_attributes
29
+ ensure_proper_type
30
+ populate_with_current_scope_attributes
31
+
32
+ # +options+ argument is only needed to make protected_attributes gem easier to hook.
33
+ # Remove it when we drop support to this gem.
34
+ init_attributes(attributes, options) if attributes
35
+
36
+ yield self if block_given?
37
+ run_callbacks :initialize unless _initialize_callbacks.empty?
38
+ end
39
+
16
40
  def filter_source_hash(doc)
17
41
  fields = self.class.model().table().fields()
18
42
  filtered = doc.select do |k,v|
@@ -21,6 +45,10 @@ module Ansr
21
45
  filtered.with_indifferent_access
22
46
  end
23
47
 
48
+ def columns(name=self.name)
49
+ super(name)
50
+ end
51
+
24
52
  def [](key)
25
53
  @source_doc[key]
26
54
  end
@@ -1,17 +1,11 @@
1
1
  module Ansr
2
2
  module Configurable
3
- def config(yaml=nil)
4
- yaml ? @config ||= begin
5
- y = begin
6
- case yaml
7
- when String
8
- File.open(yaml) {|blob| YAML.load(blob)}
9
- else
10
- yaml
11
- end
12
- end
13
- y
14
- end : @config
3
+ def config
4
+ @config ||= {}
5
+ if block_given?
6
+ yield @config
7
+ end
8
+ @config
15
9
  end
16
10
 
17
11
  alias_method :configure, :config
@@ -0,0 +1,5 @@
1
+ module Ansr
2
+ module ConnectionAdapters
3
+ require 'ansr/connection_adapters/no_sql_adapter'
4
+ end
5
+ end
@@ -0,0 +1,80 @@
1
+ require 'active_record'
2
+ require 'arel/visitors/bind_visitor'
3
+ module Ansr
4
+ module ConnectionAdapters
5
+ class NoSqlAdapter < ActiveRecord::ConnectionAdapters::AbstractAdapter
6
+ attr_reader :table
7
+ def initialize(klass, connection, logger = nil, pool = nil)
8
+ super(connection, logger, pool)
9
+ @table = klass.table
10
+ @visitor = nil
11
+ end
12
+
13
+ # Converts an arel AST to NOSQL Query
14
+ def to_nosql(arel, binds = [])
15
+ arel = arel.ast if arel.respond_to?(:ast)
16
+ if arel.is_a? ::Arel::Nodes::Node
17
+ binds = binds.dup
18
+ visitor.accept(arel) do
19
+ quote(*binds.shift.reverse)
20
+ end
21
+ else # assume it is already serialized
22
+ arel
23
+ end
24
+ end
25
+
26
+ # attr_accessor :visitor is a self.class::BindSubstitution in unprepared contexts
27
+ class BindSubstitution < ::Arel::Visitors::MySQL # :nodoc:
28
+ include ::Arel::Visitors::BindVisitor
29
+ end
30
+
31
+ # Executes +query+ statement in the context of this connection using
32
+ # +binds+ as the bind substitutes. +name+ is logged along with
33
+ # the executed +query+ statement.
34
+ def execute(query, name = 'ANSR-NOSQL')
35
+ end
36
+
37
+ # called back from ::Arel::Table
38
+ def primary_key(table_name)
39
+ 'id' # table.primary_key || 'id'
40
+ end
41
+
42
+ def table_exists?(name)
43
+ true
44
+ end
45
+
46
+ def schema_cache
47
+ ActiveRecord::ConnectionAdapters::SchemaCache.new(self)
48
+ end
49
+
50
+ # this is called by the BigTable impl
51
+ # should it be retired in favor of the more domain-appropriate 'fields'? Not usually seen by clients anyway.
52
+ def columns(table_name, *rest)
53
+ @table.fields.map {|s| ::ActiveRecord::ConnectionAdapters::Column.new(s.to_s, nil, String)}
54
+ end
55
+
56
+ def sanitize_limit(limit_value)
57
+ if limit_value.to_s.to_i >= 0
58
+ limit_value
59
+ else
60
+ Ansr::Relation::DEFAULT_PAGE_SIZE
61
+ end
62
+ end
63
+
64
+ def sanitize_filter_name(filter_value)
65
+ if filter_value.is_a? Array
66
+ return filter_value.collect {|x| sanitize_filter_name(x)}.compact
67
+ else
68
+ if @table.facets.include? filter_value.to_sym
69
+ return filter_value
70
+ else
71
+ raise "#{filter_value} is not a filterable field"
72
+ #Rails.logger.warn "Ignoring #{filter_value} (not a filterable field)" if Rails.logger
73
+ #return nil
74
+ end
75
+ end
76
+ end
77
+
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,105 @@
1
+ module Ansr
2
+ module DummyAssociations
3
+ extend ActiveSupport::Concern
4
+ module ClassMethods
5
+ def create_reflection(macro, name, scope, options, active_record)
6
+ case macro
7
+ when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
8
+ klass = options[:through] ? DummyThroughReflection : DummyAssociationReflection
9
+ reflection = klass.new(macro, name, scope, options, active_record)
10
+ when :composed_of
11
+ reflection = DummyAggregateReflection.new(macro, name, scope, options, active_record)
12
+ end
13
+
14
+ self.reflections = self.reflections.merge(name => reflection)
15
+ reflection
16
+ end
17
+
18
+ # Returns an array of AggregateReflection objects for all the aggregations in the class.
19
+ def reflect_on_all_aggregations
20
+ reflections.values.grep(DummyAggregateReflection)
21
+ end
22
+
23
+ # Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
24
+ #
25
+ # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
26
+ #
27
+ def reflect_on_aggregation(aggregation)
28
+ reflection = reflections[aggregation]
29
+ reflection if reflection.is_a?(DummyAggregateReflection)
30
+ end
31
+
32
+ # Returns an array of DummyAssociationReflection objects for all the
33
+ # associations in the class. If you only want to reflect on a certain
34
+ # association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
35
+ # <tt>:belongs_to</tt>) as the first parameter.
36
+ #
37
+ # Example:
38
+ #
39
+ # Account.reflect_on_all_associations # returns an array of all associations
40
+ # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
41
+ #
42
+ def reflect_on_all_associations(macro = nil)
43
+ association_reflections = reflections.values.grep(DummyAssociationReflection)
44
+ macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
45
+ end
46
+
47
+ # Returns the DummyAssociationReflection object for the +association+ (use the symbol).
48
+ #
49
+ # Account.reflect_on_association(:owner) # returns the owner AssociationReflection
50
+ # Invoice.reflect_on_association(:line_items).macro # returns :has_many
51
+ #
52
+ def reflect_on_association(association)
53
+ reflection = reflections[association]
54
+ reflection if reflection.is_a?(DummyAssociationReflection)
55
+ end
56
+
57
+ # Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
58
+ def reflect_on_all_autosave_associations
59
+ reflections.values.select { |reflection| reflection.options[:autosave] }
60
+ end
61
+ end
62
+ class DummyReflection < ActiveRecord::Reflection::AggregateReflection
63
+ def initialize(macro, name, scope, options, active_record)
64
+ super(macro, name, scope, options, active_record)
65
+ @symbol = name
66
+ end
67
+
68
+ def polymorphic?
69
+ false
70
+ end
71
+
72
+ def foreign_key
73
+ @symbol # ??
74
+ end
75
+ def collection?
76
+ [:has_one, :has_many, :has_and_belongs_to_many].include? macro
77
+ end
78
+ def validate?
79
+ false
80
+ end
81
+ def association_class
82
+ DummyAssociation
83
+ end
84
+
85
+ def check_validity!
86
+ true
87
+ end
88
+ end
89
+ class DummyAssociationReflection < DummyReflection; end
90
+ class DummyAggregateReflection < DummyReflection; end
91
+ class DummyThroughReflection < DummyReflection; end
92
+ class DummyAssociation < ActiveRecord::Associations::Association
93
+ def writer(*args); end
94
+ def reader(*args)
95
+ self
96
+ end
97
+ def loaded?
98
+ true
99
+ end
100
+ def identifier
101
+ nil
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,103 @@
1
+ require 'ostruct'
2
+
3
+ module Ansr::Facets
4
+
5
+ # represents a facet value; which is a field value and its hit count
6
+ class FacetItem < OpenStruct
7
+ def initialize *args
8
+ options = args.extract_options!
9
+
10
+ # Backwards-compat method signature
11
+ value = args.shift
12
+ hits = args.shift
13
+
14
+ options[:value] = value if value
15
+ options[:hits] = hits if hits
16
+
17
+ super(options)
18
+ end
19
+
20
+ def label
21
+ super || value
22
+ end
23
+
24
+ def as_json(props = nil)
25
+ table.as_json(props)
26
+ end
27
+ end
28
+
29
+ # represents a facet; which is a field and its values
30
+ class FacetField
31
+ attr_reader :name, :items
32
+ def initialize name, items, options = {}
33
+ @name, @items = name, items
34
+ @options = options
35
+ end
36
+
37
+ def limit
38
+ @options[:limit]
39
+ end
40
+
41
+ def sort
42
+ @options[:sort] || 'index'
43
+ end
44
+
45
+ def offset
46
+ @options[:offset] || 0
47
+ end
48
+ end
49
+
50
+ # @response.facets.each do |facet|
51
+ # facet.name
52
+ # facet.items
53
+ # end
54
+ # "caches" the result in the @facets instance var
55
+ def facets
56
+ @facets ||= begin
57
+ facet_fields.map do |(facet_field_name,values_and_hits)|
58
+ items = []
59
+ options = {}
60
+ values_and_hits.each_slice(2) do |k,v|
61
+ items << FacetItem.new(:value => k, :hits => v)
62
+ end
63
+ options[:sort] = (params[:"f.#{facet_field_name}.facet.sort"] || params[:'facet.sort'])
64
+ if params[:"f.#{facet_field_name}.facet.limit"] || params[:"facet.limit"]
65
+ options[:limit] = (params[:"f.#{facet_field_name}.facet.limit"] || params[:"facet.limit"]).to_i
66
+ end
67
+
68
+ if params[:"f.#{facet_field_name}.facet.offset"] || params[:'facet.offset']
69
+ options[:offset] = (params[:"f.#{facet_field_name}.facet.offset"] || params[:'facet.offset']).to_i
70
+ end
71
+ FacetField.new(facet_field_name, items, options)
72
+ end
73
+ end
74
+ end
75
+
76
+ # pass in a facet field name and get back a Facet instance
77
+ def facet_by_field_name(name)
78
+ @facets_by_field_name ||= {}
79
+ @facets_by_field_name[name] ||= (
80
+ facets.detect{|facet|facet.name.to_s == name.to_s}
81
+ )
82
+ end
83
+
84
+ def facet_counts
85
+ @facet_counts ||= self['facet_counts'] || {}
86
+ end
87
+
88
+ # Returns the hash of all the facet_fields (ie: {'instock_b' => ['true', 123, 'false', 20]}
89
+ def facet_fields
90
+ @facet_fields ||= facet_counts['facet_fields'] || {}
91
+ end
92
+
93
+ # Returns all of the facet queries
94
+ def facet_queries
95
+ @facet_queries ||= facet_counts['facet_queries'] || {}
96
+ end
97
+
98
+ # Returns all of the facet queries
99
+ def facet_pivot
100
+ @facet_pivot ||= facet_counts['facet_pivot'] || {}
101
+ end
102
+
103
+ end # end Facets