ninjudd-model_set 0.9.2

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 (95) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +39 -0
  3. data/VERSION.yml +4 -0
  4. data/lib/model_set.rb +712 -0
  5. data/lib/model_set/conditioned.rb +33 -0
  6. data/lib/model_set/conditions.rb +103 -0
  7. data/lib/model_set/query.rb +128 -0
  8. data/lib/model_set/raw_query.rb +41 -0
  9. data/lib/model_set/raw_sql_query.rb +19 -0
  10. data/lib/model_set/set_query.rb +34 -0
  11. data/lib/model_set/solr_query.rb +70 -0
  12. data/lib/model_set/sphinx_query.rb +148 -0
  13. data/lib/model_set/sql_base_query.rb +52 -0
  14. data/lib/model_set/sql_query.rb +75 -0
  15. data/lib/multi_set.rb +67 -0
  16. data/test/model_set_test.rb +283 -0
  17. data/test/multi_set_test.rb +65 -0
  18. data/test/test_helper.rb +23 -0
  19. data/vendor/sphinx_client/README.rdoc +41 -0
  20. data/vendor/sphinx_client/Rakefile +21 -0
  21. data/vendor/sphinx_client/init.rb +1 -0
  22. data/vendor/sphinx_client/install.rb +5 -0
  23. data/vendor/sphinx_client/lib/sphinx.rb +6 -0
  24. data/vendor/sphinx_client/lib/sphinx/client.rb +1093 -0
  25. data/vendor/sphinx_client/lib/sphinx/request.rb +50 -0
  26. data/vendor/sphinx_client/lib/sphinx/response.rb +69 -0
  27. data/vendor/sphinx_client/spec/client_response_spec.rb +112 -0
  28. data/vendor/sphinx_client/spec/client_spec.rb +469 -0
  29. data/vendor/sphinx_client/spec/fixtures/default_search.php +8 -0
  30. data/vendor/sphinx_client/spec/fixtures/default_search_index.php +8 -0
  31. data/vendor/sphinx_client/spec/fixtures/excerpt_custom.php +11 -0
  32. data/vendor/sphinx_client/spec/fixtures/excerpt_default.php +8 -0
  33. data/vendor/sphinx_client/spec/fixtures/excerpt_flags.php +11 -0
  34. data/vendor/sphinx_client/spec/fixtures/field_weights.php +9 -0
  35. data/vendor/sphinx_client/spec/fixtures/filter.php +9 -0
  36. data/vendor/sphinx_client/spec/fixtures/filter_exclude.php +9 -0
  37. data/vendor/sphinx_client/spec/fixtures/filter_float_range.php +9 -0
  38. data/vendor/sphinx_client/spec/fixtures/filter_float_range_exclude.php +9 -0
  39. data/vendor/sphinx_client/spec/fixtures/filter_range.php +9 -0
  40. data/vendor/sphinx_client/spec/fixtures/filter_range_exclude.php +9 -0
  41. data/vendor/sphinx_client/spec/fixtures/filter_range_int64.php +10 -0
  42. data/vendor/sphinx_client/spec/fixtures/filter_ranges.php +10 -0
  43. data/vendor/sphinx_client/spec/fixtures/filters.php +10 -0
  44. data/vendor/sphinx_client/spec/fixtures/filters_different.php +13 -0
  45. data/vendor/sphinx_client/spec/fixtures/geo_anchor.php +9 -0
  46. data/vendor/sphinx_client/spec/fixtures/group_by_attr.php +9 -0
  47. data/vendor/sphinx_client/spec/fixtures/group_by_attrpair.php +9 -0
  48. data/vendor/sphinx_client/spec/fixtures/group_by_day.php +9 -0
  49. data/vendor/sphinx_client/spec/fixtures/group_by_day_sort.php +9 -0
  50. data/vendor/sphinx_client/spec/fixtures/group_by_month.php +9 -0
  51. data/vendor/sphinx_client/spec/fixtures/group_by_week.php +9 -0
  52. data/vendor/sphinx_client/spec/fixtures/group_by_year.php +9 -0
  53. data/vendor/sphinx_client/spec/fixtures/group_distinct.php +10 -0
  54. data/vendor/sphinx_client/spec/fixtures/id_range.php +9 -0
  55. data/vendor/sphinx_client/spec/fixtures/id_range64.php +9 -0
  56. data/vendor/sphinx_client/spec/fixtures/index_weights.php +9 -0
  57. data/vendor/sphinx_client/spec/fixtures/keywords.php +8 -0
  58. data/vendor/sphinx_client/spec/fixtures/limits.php +9 -0
  59. data/vendor/sphinx_client/spec/fixtures/limits_cutoff.php +9 -0
  60. data/vendor/sphinx_client/spec/fixtures/limits_max.php +9 -0
  61. data/vendor/sphinx_client/spec/fixtures/limits_max_cutoff.php +9 -0
  62. data/vendor/sphinx_client/spec/fixtures/match_all.php +9 -0
  63. data/vendor/sphinx_client/spec/fixtures/match_any.php +9 -0
  64. data/vendor/sphinx_client/spec/fixtures/match_boolean.php +9 -0
  65. data/vendor/sphinx_client/spec/fixtures/match_extended.php +9 -0
  66. data/vendor/sphinx_client/spec/fixtures/match_extended2.php +9 -0
  67. data/vendor/sphinx_client/spec/fixtures/match_fullscan.php +9 -0
  68. data/vendor/sphinx_client/spec/fixtures/match_phrase.php +9 -0
  69. data/vendor/sphinx_client/spec/fixtures/max_query_time.php +9 -0
  70. data/vendor/sphinx_client/spec/fixtures/miltiple_queries.php +12 -0
  71. data/vendor/sphinx_client/spec/fixtures/ranking_bm25.php +9 -0
  72. data/vendor/sphinx_client/spec/fixtures/ranking_none.php +9 -0
  73. data/vendor/sphinx_client/spec/fixtures/ranking_proximity.php +9 -0
  74. data/vendor/sphinx_client/spec/fixtures/ranking_proximity_bm25.php +9 -0
  75. data/vendor/sphinx_client/spec/fixtures/ranking_wordcount.php +9 -0
  76. data/vendor/sphinx_client/spec/fixtures/retries.php +9 -0
  77. data/vendor/sphinx_client/spec/fixtures/retries_delay.php +9 -0
  78. data/vendor/sphinx_client/spec/fixtures/select.php +9 -0
  79. data/vendor/sphinx_client/spec/fixtures/set_override.php +11 -0
  80. data/vendor/sphinx_client/spec/fixtures/sort_attr_asc.php +9 -0
  81. data/vendor/sphinx_client/spec/fixtures/sort_attr_desc.php +9 -0
  82. data/vendor/sphinx_client/spec/fixtures/sort_expr.php +9 -0
  83. data/vendor/sphinx_client/spec/fixtures/sort_extended.php +9 -0
  84. data/vendor/sphinx_client/spec/fixtures/sort_relevance.php +9 -0
  85. data/vendor/sphinx_client/spec/fixtures/sort_time_segments.php +9 -0
  86. data/vendor/sphinx_client/spec/fixtures/sphinxapi.php +1269 -0
  87. data/vendor/sphinx_client/spec/fixtures/update_attributes.php +8 -0
  88. data/vendor/sphinx_client/spec/fixtures/update_attributes_mva.php +8 -0
  89. data/vendor/sphinx_client/spec/fixtures/weights.php +9 -0
  90. data/vendor/sphinx_client/spec/sphinx/sphinx-id64.conf +67 -0
  91. data/vendor/sphinx_client/spec/sphinx/sphinx.conf +67 -0
  92. data/vendor/sphinx_client/spec/sphinx/sphinx_test.sql +86 -0
  93. data/vendor/sphinx_client/sphinx.yml.tpl +3 -0
  94. data/vendor/sphinx_client/tasks/sphinx.rake +75 -0
  95. metadata +154 -0
@@ -0,0 +1,33 @@
1
+ class ModelSet
2
+ module Conditioned
3
+ # Shared methods for dealing with conditions.
4
+ attr_reader :conditions
5
+
6
+ def add_conditions!(*conditions)
7
+ operator = conditions.shift if conditions.first.kind_of?(Symbol)
8
+ operator ||= :and
9
+
10
+ # Sanitize conditions.
11
+ conditions.collect! do |condition|
12
+ condition.kind_of?(Conditions) ? condition : Conditions.new( sanitize_condition(condition) )
13
+ end
14
+
15
+ if operator == :not
16
+ # In this case, :not actually means :and :not.
17
+ conditions = ~Conditions.new(:and, *conditions)
18
+ operator = :and
19
+ end
20
+
21
+ conditions << @conditions if @conditions
22
+ @conditions = Conditions.new(operator, *conditions)
23
+
24
+ clear_cache!
25
+ end
26
+
27
+ def invert!
28
+ raise 'cannot invert without conditions' if @conditions.nil?
29
+ @conditions = ~@conditions
30
+ clear_cache!
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,103 @@
1
+ class ModelSet
2
+ class Conditions
3
+ deep_clonable
4
+
5
+ attr_reader :operator, :conditions
6
+
7
+ def self.new(*args)
8
+ if args.size == 1 and args.first.kind_of?(self)
9
+ # Just clone if the only argument is a Conditions object.
10
+ args.first.clone
11
+ elsif args.size == 2 and [:and, :or].include?(args.first)
12
+ # The operator is not necessary if there is only one subcondition.
13
+ new(args.last)
14
+ else
15
+ super
16
+ end
17
+ end
18
+
19
+ def new(*args)
20
+ self.class.new(*args)
21
+ end
22
+
23
+ def initialize(*args)
24
+ if args.size == 1 and not args.first.kind_of?(Symbol)
25
+ # Terminal.
26
+ @conditions = args
27
+ else
28
+ @operator = args.shift
29
+ raise "invalid operator :#{operator}" unless [:and, :or, :not].include?(operator)
30
+
31
+ if operator == :not
32
+ raise "unary operator :not cannot have multiple conditions" if args.size > 1
33
+ @conditions = [self.class.new(args.first)]
34
+ else
35
+ # Compact the conditions if possible.
36
+ @conditions = []
37
+ args.each do |clause|
38
+ self << clause
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ def terminal?
45
+ operator.nil?
46
+ end
47
+
48
+ def <<(clause)
49
+ raise 'cannot append conditions to a terminal' if terminal?
50
+
51
+ clause = self.class.new(clause)
52
+ if clause.operator == operator
53
+ @conditions.concat(clause.conditions)
54
+ else
55
+ @conditions << clause
56
+ end
57
+ @conditions.uniq!
58
+ end
59
+
60
+ def ~
61
+ if operator == :not
62
+ conditions.first.clone
63
+ else
64
+ new(:not, self)
65
+ end
66
+ end
67
+
68
+ def |(other)
69
+ new(:or, self, other)
70
+ end
71
+
72
+ def &(other)
73
+ new(:and, self, other)
74
+ end
75
+
76
+ def to_s
77
+ return conditions.first if terminal?
78
+
79
+ condition_strings = conditions.collect do |condition|
80
+ condition.operator == :not ? condition.to_s : "(#{condition.to_s})"
81
+ end.sort_by {|s| s.size}
82
+
83
+ case operator
84
+ when :not
85
+ "NOT #{condition_strings.first}"
86
+ when :and
87
+ "#{condition_strings.join(' AND ')}"
88
+ when :or
89
+ "#{condition_strings.join(' OR ')}"
90
+ end
91
+ end
92
+
93
+ def hash
94
+ # for uniq
95
+ [operator, conditions].hash
96
+ end
97
+
98
+ def eql?(other)
99
+ # for uniq
100
+ self.hash == other.hash
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,128 @@
1
+ class ModelSet
2
+ class Query
3
+ deep_clonable
4
+
5
+ def initialize(model_set = ModelSet)
6
+ if model_set.kind_of?(Class)
7
+ @set_class = model_set
8
+ else
9
+ @set_class = model_set.class
10
+ anchor!(model_set.query) if model_set.query
11
+ end
12
+ end
13
+
14
+ def order_by!(order)
15
+ @sort_order = order
16
+ clear_cache!
17
+ end
18
+
19
+ def unsorted!
20
+ @sort_order = nil
21
+ clear_cache!
22
+ end
23
+
24
+ def page!(page)
25
+ @page = page ? page.to_i : nil
26
+ @offset = nil
27
+ clear_limited_cache!
28
+ end
29
+
30
+ def limit!(limit, offset = nil)
31
+ @limit = limit ? limit.to_i : nil
32
+ @offset = offset ? offset.to_i : nil
33
+ @page = nil if offset
34
+ clear_limited_cache!
35
+ end
36
+
37
+ def unlimited!
38
+ @limit = nil
39
+ @offset = nil
40
+ @page = nil
41
+ clear_limited_cache!
42
+ end
43
+
44
+ def clear_limited_cache!
45
+ @ids = nil
46
+ @size = nil
47
+ self
48
+ end
49
+
50
+ def clear_cache!
51
+ @count = nil
52
+ clear_limited_cache!
53
+ end
54
+
55
+ attr_reader :set_class
56
+ delegate :id_field, :id_field_with_prefix, :to => :set_class
57
+
58
+ def model_class
59
+ set_class.query_model_class
60
+ end
61
+
62
+ def model_name
63
+ model_class.name
64
+ end
65
+
66
+ def table_name
67
+ model_class.table_name
68
+ end
69
+
70
+ attr_reader :limit, :sort_order
71
+
72
+ def offset
73
+ if limit
74
+ @offset ||= @page ? (@page - 1) * limit : 0
75
+ end
76
+ end
77
+
78
+ def page
79
+ if limit
80
+ @page ||= @offset ? (@offset / limit) : 1
81
+ end
82
+ end
83
+
84
+ def pages
85
+ limit ? (1.0 * count / limit).ceil : 1
86
+ end
87
+
88
+ def before_query(*args)
89
+ proc = self.class.before_query
90
+ proc.bind(self).call(*args) if proc
91
+ end
92
+
93
+ def self.before_query(&block)
94
+ if block
95
+ @before_query = block
96
+ else
97
+ @before_query
98
+ end
99
+ end
100
+
101
+ def on_exception(*args)
102
+ proc = self.class.on_exception
103
+ proc ? proc.bind(self).call(*args) : raise(args.first)
104
+ end
105
+
106
+ def self.on_exception(&block)
107
+ if block
108
+ @on_exception = block
109
+ else
110
+ @on_exception
111
+ end
112
+ end
113
+
114
+ def after_query(*args)
115
+ proc = self.class.after_query
116
+ proc.bind(self).call(*args) if proc
117
+ end
118
+
119
+ def self.after_query(*args, &block)
120
+ if block
121
+ @after_query = block
122
+ else
123
+ @after_query
124
+ end
125
+ end
126
+
127
+ end
128
+ end
@@ -0,0 +1,41 @@
1
+ class ModelSet
2
+ class RawQuery < Query
3
+ attr_reader :records
4
+
5
+ def anchor!(query, raw_method = 'find_raw_by_id')
6
+ @records = model_class.send(raw_method, query.ids.to_a)
7
+ end
8
+
9
+ def select!(&block)
10
+ records.select!(&block)
11
+ end
12
+
13
+ def reject!(&block)
14
+ records.reject!(&block)
15
+ end
16
+
17
+ def sort_by!(&block)
18
+ @records = records.sort_by(&block)
19
+ end
20
+
21
+ def ids
22
+ if limit
23
+ (records[offset, limit] || []).collect {|r| r['id'].to_i}
24
+ else
25
+ records.collect {|r| r['id'].to_i}
26
+ end
27
+ end
28
+
29
+ def size
30
+ if limit
31
+ [count - offset, limit].min
32
+ else
33
+ count
34
+ end
35
+ end
36
+
37
+ def count
38
+ records.size
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,19 @@
1
+ class ModelSet
2
+ class RawSQLQuery < SQLBaseQuery
3
+ def sql=(sql)
4
+ @sql = sanitize_condition(sql)
5
+ ['LIMIT', 'OFFSET'].each do |term|
6
+ raise "#{term} not permitted in raw sql" if @sql.match(/ #{term} \d+/i)
7
+ end
8
+ end
9
+
10
+ def sql
11
+ "#{@sql} #{limit_clause}"
12
+ end
13
+
14
+ def count
15
+ # The only way to get the count if there is a limit is to fetch all ids without the limit.
16
+ @count ||= limit ? fetch_id_set(@sql).size : size
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,34 @@
1
+ class ModelSet
2
+ class SetQuery < Query
3
+ delegate :add!, :unshift!, :subtract!, :intersect!, :reorder!, :to => :set
4
+
5
+ def anchor!(query)
6
+ @set = query.ids.to_ordered_set
7
+ end
8
+
9
+ def set
10
+ @set ||= [].to_ordered_set
11
+ end
12
+
13
+ def ids
14
+ if limit
15
+ set.limit(limit, offset)
16
+ else
17
+ set.clone
18
+ end
19
+ end
20
+
21
+ def size
22
+ if limit
23
+ [count - offset, limit].min
24
+ else
25
+ count
26
+ end
27
+ end
28
+
29
+ def count
30
+ set.size
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,70 @@
1
+ class ModelSet
2
+ class SolrQuery < Query
3
+ include Conditioned
4
+
5
+ MAX_SOLR_RESULTS = 1000
6
+
7
+ def anchor!(query)
8
+ add_conditions!( ids_clause(query.ids) )
9
+ end
10
+
11
+ def size
12
+ fetch_results if @size.nil?
13
+ @size
14
+ end
15
+
16
+ def count
17
+ fetch_results if @count.nil?
18
+ @count
19
+ end
20
+
21
+ def ids
22
+ fetch_results if @ids.nil?
23
+ @ids
24
+ end
25
+
26
+ private
27
+
28
+ def fetch_results
29
+ query = "#{conditions.to_s};#{@sort_order.to_s}"
30
+
31
+ solr_params = []
32
+ solr_params << "q=#{ ERB::Util::url_encode(query) }"
33
+ solr_params << "wt=ruby"
34
+ solr_params << "fl=pk_i"
35
+
36
+ if limit
37
+ solr_params << "rows=#{limit}"
38
+ solr_params << "start=#{offset}"
39
+ else
40
+ solr_params << "rows=#{MAX_SOLR_RESULTS}"
41
+ end
42
+
43
+ solr_params = solr_params.join('&')
44
+ before_query(solr_params)
45
+
46
+ # Catch any errors when calling solr so we can log the params.
47
+ begin
48
+ resp = eval ActsAsSolr::Post.execute(solr_params)
49
+ rescue Exception => e
50
+ on_exception(e, solr_params)
51
+ end
52
+
53
+ after_query(solr_params)
54
+
55
+ @count = resp['response']['numFound']
56
+ @ids = resp['response']['docs'].collect {|doc| doc['pk_i'].to_i}.to_ordered_set
57
+ @size = @ids.size
58
+ end
59
+
60
+ def ids_clause(ids, field = nil)
61
+ return 'pk_i:(false)' if ids.empty?
62
+ field ||= 'pk_i'
63
+ "#{field}:(#{ids.join(' OR ')})"
64
+ end
65
+
66
+ def sanitize_condition(condition)
67
+ condition
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,148 @@
1
+ require File.dirname(__FILE__) + '/../../vendor/sphinx_client/lib/sphinx'
2
+
3
+ class ModelSet
4
+ class SphinxQuery < Query
5
+ MAX_SPHINX_RESULTS = 1000
6
+
7
+ class SphinxError < StandardError; end
8
+
9
+ attr_reader :conditions, :filters
10
+
11
+ def anchor!(query)
12
+ add_filters!( id_field => query.ids.to_a )
13
+ end
14
+
15
+ def add_filters!(filters)
16
+ @filters ||= {}
17
+ @filters.merge!(filters)
18
+ clear_cache!
19
+ end
20
+
21
+ def add_conditions!(conditions)
22
+ if conditions.kind_of?(Hash)
23
+ conditions.each do |field, value|
24
+ next if value.nil?
25
+ field = field.join(',') if field.kind_of?(Array)
26
+ add_conditions!("@(#{field}) #{value}")
27
+ end
28
+ else
29
+ @conditions ||= []
30
+ @conditions << conditions
31
+ @conditions.uniq!
32
+ clear_cache!
33
+ end
34
+ end
35
+
36
+ def index
37
+ @index ||= '*'
38
+ end
39
+
40
+ def use_index!(index)
41
+ @index = index
42
+ end
43
+
44
+ SORT_MODES = {
45
+ :relevance => Sphinx::Client::SPH_SORT_RELEVANCE,
46
+ :descending => Sphinx::Client::SPH_SORT_ATTR_DESC,
47
+ :ascending => Sphinx::Client::SPH_SORT_ATTR_ASC,
48
+ :time => Sphinx::Client::SPH_SORT_TIME_SEGMENTS,
49
+ :extending => Sphinx::Client::SPH_SORT_EXTENDED,
50
+ :expression => Sphinx::Client::SPH_SORT_EXPR,
51
+ }
52
+
53
+ def order_by!(field, mode = :ascending)
54
+ raise "invalid mode: :#{mode}" unless SORT_MODES[mode]
55
+ @sort_order = [SORT_MODES[mode], field.to_s]
56
+ clear_cache!
57
+ end
58
+
59
+ def size
60
+ fetch_results if @size.nil?
61
+ @size
62
+ end
63
+
64
+ def count
65
+ fetch_results if @count.nil?
66
+ @count
67
+ end
68
+
69
+ def ids
70
+ fetch_results if @ids.nil?
71
+ @ids
72
+ end
73
+
74
+ private
75
+
76
+ def fetch_results
77
+ if @conditions.nil? or (@filters and @filters[id_field] and @filters[id_field].empty?)
78
+ @count = 0
79
+ @size = 0
80
+ @ids = []
81
+ else
82
+ opts = {
83
+ :filters => @filters,
84
+ :query => conditions_clause,
85
+ }
86
+ before_query(opts)
87
+
88
+ search = Sphinx::Client.new
89
+ search.SetServer(self.class.server_host, self.class.server_port)
90
+ search.SetMatchMode(Sphinx::Client::SPH_MATCH_EXTENDED2)
91
+ if limit
92
+ search.SetLimits(offset, limit, offset + limit)
93
+ else
94
+ search.SetLimits(0, MAX_SPHINX_RESULTS, MAX_SPHINX_RESULTS)
95
+ end
96
+ search.SetSortMode(*@sort_order) if @sort_order
97
+ search.SetFilter('class_id', model_class.class_id) if model_class.respond_to?(:class_id)
98
+
99
+ @filters and @filters.each do |field, value|
100
+ next if value.nil?
101
+
102
+ exclude = defined?(AntiObject) && value.kind_of?(AntiObject)
103
+ value = ~value if exclude
104
+
105
+ if value.kind_of?(Range)
106
+ min, max = filter_values([value.begin, value.end])
107
+ search.SetFilterRange(field.to_s, min, max, exclude)
108
+ else
109
+ search.SetFilter(field.to_s, filter_values(value), exclude)
110
+ end
111
+ end
112
+
113
+ begin
114
+ response = search.Query(opts[:query], index)
115
+ raise SphinxError, search.GetLastError unless response
116
+ rescue Exception => e
117
+ on_exception(e, opts)
118
+ end
119
+
120
+ @count = response['total_found']
121
+ @ids = response['matches'].collect {|r| r['id']}.to_ordered_set
122
+ @size = @ids.size
123
+
124
+ after_query(opts)
125
+ end
126
+ end
127
+
128
+ def filter_values(values)
129
+ Array(values).collect do |value|
130
+ case value
131
+ when Date : value.to_time.to_i
132
+ when TrueClass : 1
133
+ when FalseClass : 0
134
+ else
135
+ value.to_i
136
+ end
137
+ end
138
+ end
139
+
140
+ class << self
141
+ attr_accessor :server_host, :server_port
142
+ end
143
+
144
+ def conditions_clause
145
+ @conditions ? @conditions.join(' ') : ''
146
+ end
147
+ end
148
+ end