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