searchlogic 1.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. data/CHANGELOG.rdoc +228 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Manifest +123 -0
  4. data/README.rdoc +383 -0
  5. data/Rakefile +15 -0
  6. data/TODO.rdoc +6 -0
  7. data/examples/README.rdoc +4 -0
  8. data/init.rb +1 -0
  9. data/lib/searchlogic.rb +89 -0
  10. data/lib/searchlogic/active_record/associations.rb +52 -0
  11. data/lib/searchlogic/active_record/base.rb +218 -0
  12. data/lib/searchlogic/active_record/connection_adapters/mysql_adapter.rb +172 -0
  13. data/lib/searchlogic/active_record/connection_adapters/postgresql_adapter.rb +168 -0
  14. data/lib/searchlogic/active_record/connection_adapters/sqlite_adapter.rb +75 -0
  15. data/lib/searchlogic/condition/base.rb +159 -0
  16. data/lib/searchlogic/condition/begins_with.rb +17 -0
  17. data/lib/searchlogic/condition/blank.rb +21 -0
  18. data/lib/searchlogic/condition/child_of.rb +11 -0
  19. data/lib/searchlogic/condition/descendant_of.rb +24 -0
  20. data/lib/searchlogic/condition/ends_with.rb +17 -0
  21. data/lib/searchlogic/condition/equals.rb +27 -0
  22. data/lib/searchlogic/condition/greater_than.rb +15 -0
  23. data/lib/searchlogic/condition/greater_than_or_equal_to.rb +15 -0
  24. data/lib/searchlogic/condition/inclusive_descendant_of.rb +11 -0
  25. data/lib/searchlogic/condition/keywords.rb +47 -0
  26. data/lib/searchlogic/condition/less_than.rb +15 -0
  27. data/lib/searchlogic/condition/less_than_or_equal_to.rb +15 -0
  28. data/lib/searchlogic/condition/like.rb +15 -0
  29. data/lib/searchlogic/condition/nil.rb +21 -0
  30. data/lib/searchlogic/condition/not_begin_with.rb +20 -0
  31. data/lib/searchlogic/condition/not_blank.rb +19 -0
  32. data/lib/searchlogic/condition/not_end_with.rb +20 -0
  33. data/lib/searchlogic/condition/not_equal.rb +26 -0
  34. data/lib/searchlogic/condition/not_have_keywords.rb +20 -0
  35. data/lib/searchlogic/condition/not_like.rb +20 -0
  36. data/lib/searchlogic/condition/not_nil.rb +19 -0
  37. data/lib/searchlogic/condition/sibling_of.rb +14 -0
  38. data/lib/searchlogic/condition/tree.rb +17 -0
  39. data/lib/searchlogic/conditions/base.rb +484 -0
  40. data/lib/searchlogic/conditions/protection.rb +36 -0
  41. data/lib/searchlogic/config.rb +31 -0
  42. data/lib/searchlogic/config/helpers.rb +289 -0
  43. data/lib/searchlogic/config/search.rb +53 -0
  44. data/lib/searchlogic/core_ext/hash.rb +75 -0
  45. data/lib/searchlogic/helpers/control_types/link.rb +310 -0
  46. data/lib/searchlogic/helpers/control_types/links.rb +241 -0
  47. data/lib/searchlogic/helpers/control_types/remote_link.rb +87 -0
  48. data/lib/searchlogic/helpers/control_types/remote_links.rb +72 -0
  49. data/lib/searchlogic/helpers/control_types/remote_select.rb +36 -0
  50. data/lib/searchlogic/helpers/control_types/select.rb +82 -0
  51. data/lib/searchlogic/helpers/form.rb +208 -0
  52. data/lib/searchlogic/helpers/utilities.rb +197 -0
  53. data/lib/searchlogic/modifiers/absolute.rb +15 -0
  54. data/lib/searchlogic/modifiers/acos.rb +11 -0
  55. data/lib/searchlogic/modifiers/asin.rb +11 -0
  56. data/lib/searchlogic/modifiers/atan.rb +11 -0
  57. data/lib/searchlogic/modifiers/base.rb +27 -0
  58. data/lib/searchlogic/modifiers/ceil.rb +15 -0
  59. data/lib/searchlogic/modifiers/char_length.rb +15 -0
  60. data/lib/searchlogic/modifiers/cos.rb +15 -0
  61. data/lib/searchlogic/modifiers/cot.rb +15 -0
  62. data/lib/searchlogic/modifiers/day_of_month.rb +15 -0
  63. data/lib/searchlogic/modifiers/day_of_week.rb +15 -0
  64. data/lib/searchlogic/modifiers/day_of_year.rb +15 -0
  65. data/lib/searchlogic/modifiers/degrees.rb +11 -0
  66. data/lib/searchlogic/modifiers/exp.rb +15 -0
  67. data/lib/searchlogic/modifiers/floor.rb +15 -0
  68. data/lib/searchlogic/modifiers/hex.rb +11 -0
  69. data/lib/searchlogic/modifiers/hour.rb +11 -0
  70. data/lib/searchlogic/modifiers/log.rb +15 -0
  71. data/lib/searchlogic/modifiers/log10.rb +11 -0
  72. data/lib/searchlogic/modifiers/log2.rb +11 -0
  73. data/lib/searchlogic/modifiers/lower.rb +15 -0
  74. data/lib/searchlogic/modifiers/ltrim.rb +15 -0
  75. data/lib/searchlogic/modifiers/md5.rb +11 -0
  76. data/lib/searchlogic/modifiers/microseconds.rb +11 -0
  77. data/lib/searchlogic/modifiers/milliseconds.rb +11 -0
  78. data/lib/searchlogic/modifiers/minute.rb +15 -0
  79. data/lib/searchlogic/modifiers/month.rb +15 -0
  80. data/lib/searchlogic/modifiers/octal.rb +15 -0
  81. data/lib/searchlogic/modifiers/radians.rb +11 -0
  82. data/lib/searchlogic/modifiers/round.rb +11 -0
  83. data/lib/searchlogic/modifiers/rtrim.rb +15 -0
  84. data/lib/searchlogic/modifiers/second.rb +15 -0
  85. data/lib/searchlogic/modifiers/sign.rb +11 -0
  86. data/lib/searchlogic/modifiers/sin.rb +11 -0
  87. data/lib/searchlogic/modifiers/square_root.rb +15 -0
  88. data/lib/searchlogic/modifiers/tan.rb +15 -0
  89. data/lib/searchlogic/modifiers/trim.rb +15 -0
  90. data/lib/searchlogic/modifiers/upper.rb +15 -0
  91. data/lib/searchlogic/modifiers/week.rb +11 -0
  92. data/lib/searchlogic/modifiers/year.rb +11 -0
  93. data/lib/searchlogic/search/base.rb +148 -0
  94. data/lib/searchlogic/search/conditions.rb +53 -0
  95. data/lib/searchlogic/search/ordering.rb +244 -0
  96. data/lib/searchlogic/search/pagination.rb +121 -0
  97. data/lib/searchlogic/search/protection.rb +89 -0
  98. data/lib/searchlogic/search/searching.rb +31 -0
  99. data/lib/searchlogic/shared/utilities.rb +50 -0
  100. data/lib/searchlogic/shared/virtual_classes.rb +39 -0
  101. data/lib/searchlogic/version.rb +79 -0
  102. data/searchlogic.gemspec +39 -0
  103. data/test/fixtures/accounts.yml +15 -0
  104. data/test/fixtures/cats.yml +3 -0
  105. data/test/fixtures/dogs.yml +3 -0
  106. data/test/fixtures/orders.yml +14 -0
  107. data/test/fixtures/user_groups.yml +13 -0
  108. data/test/fixtures/users.yml +36 -0
  109. data/test/test_active_record_associations.rb +81 -0
  110. data/test/test_active_record_base.rb +93 -0
  111. data/test/test_condition_base.rb +52 -0
  112. data/test/test_condition_types.rb +143 -0
  113. data/test/test_conditions_base.rb +242 -0
  114. data/test/test_conditions_protection.rb +16 -0
  115. data/test/test_config.rb +23 -0
  116. data/test/test_helper.rb +134 -0
  117. data/test/test_search_base.rb +227 -0
  118. data/test/test_search_conditions.rb +19 -0
  119. data/test/test_search_ordering.rb +165 -0
  120. data/test/test_search_pagination.rb +72 -0
  121. data/test/test_search_protection.rb +24 -0
  122. data/test_libs/acts_as_tree.rb +98 -0
  123. data/test_libs/ordered_hash.rb +9 -0
  124. data/test_libs/rexml_fix.rb +14 -0
  125. metadata +317 -0
@@ -0,0 +1,168 @@
1
+ module Searchlogic
2
+ module ActiveRecord
3
+ module ConnectionAdapters
4
+ module PostgreSQLAdapter
5
+ # Datetime functions
6
+ def microseconds_sql(column_name)
7
+ "date_part('microseconds', #{column_name})"
8
+ end
9
+
10
+ def milliseconds_sql(column_name)
11
+ "date_part('milliseconds', #{column_name})"
12
+ end
13
+
14
+ def second_sql(column_name)
15
+ "date_part('second', #{column_name})"
16
+ end
17
+
18
+ def minute_sql(column_name)
19
+ "date_part('minute', #{column_name})"
20
+ end
21
+
22
+ def hour_sql(column_name)
23
+ "date_part('hour', #{column_name})"
24
+ end
25
+
26
+ def day_of_week_sql(column_name)
27
+ "(date_part('dow', #{column_name}) + 1)"
28
+ end
29
+
30
+ def day_of_month_sql(column_name)
31
+ "date_part('day', #{column_name})"
32
+ end
33
+
34
+ def day_of_year_sql(column_name)
35
+ "date_part('doy', #{column_name})"
36
+ end
37
+
38
+ def week_sql(column_name)
39
+ "date_part('week', #{column_name})"
40
+ end
41
+
42
+ def month_sql(column_name)
43
+ "date_part('month', #{column_name})"
44
+ end
45
+
46
+ def year_sql(column_name)
47
+ "date_part('year', #{column_name})"
48
+ end
49
+
50
+ # String functions
51
+ def char_length_sql(column_name)
52
+ "length(#{column_name})"
53
+ end
54
+
55
+ def lower_sql(column_name)
56
+ "lower(#{column_name})"
57
+ end
58
+
59
+ def ltrim_sql(column_name)
60
+ "ltrim(#{column_name})"
61
+ end
62
+
63
+ def md5_sql(column_name)
64
+ "md5(#{column_name})"
65
+ end
66
+
67
+ def rtrim_sql(column_name)
68
+ "rtrim(#{column_name})"
69
+ end
70
+
71
+ def trim_sql(column_name)
72
+ "trim(#{column_name})"
73
+ end
74
+
75
+ def upper_sql(column_name)
76
+ "upper(#{column_name})"
77
+ end
78
+
79
+ # Number functions
80
+ def absolute_sql(column_name)
81
+ "abs(#{column_name})"
82
+ end
83
+
84
+ def acos_sql(column_name)
85
+ "acos(#{column_name})"
86
+ end
87
+
88
+ def asin_sql(column_name)
89
+ "asin(#{column_name})"
90
+ end
91
+
92
+ def atan_sql(column_name)
93
+ "atan(#{column_name})"
94
+ end
95
+
96
+ def ceil_sql(column_name)
97
+ "ceil(#{column_name})"
98
+ end
99
+
100
+ def cos_sql(column_name)
101
+ "cos(#{column_name})"
102
+ end
103
+
104
+ def cot_sql(column_name)
105
+ "cot(#{column_name})"
106
+ end
107
+
108
+ def degrees_sql(column_name)
109
+ "degrees(#{column_name})"
110
+ end
111
+
112
+ def exp_sql(column_name)
113
+ "exp(#{column_name})"
114
+ end
115
+
116
+ def floor_sql(column_name)
117
+ "floor(#{column_name})"
118
+ end
119
+
120
+ def hex_sql(column_name)
121
+ "to_hex(#{column_name})"
122
+ end
123
+
124
+ def ln_sql(column_name)
125
+ "ln(#{column_name})"
126
+ end
127
+
128
+ def log_sql(column_name)
129
+ "log(#{column_name})"
130
+ end
131
+
132
+ def log2_sql(column_name)
133
+ "log(2.0, #{column_name})"
134
+ end
135
+
136
+ def log10_sql(column_name)
137
+ "log(10.0, #{column_name})"
138
+ end
139
+
140
+ def radians_sql(column_name)
141
+ "radians(#{column_name})"
142
+ end
143
+
144
+ def round_sql(column_name)
145
+ "round(#{column_name})"
146
+ end
147
+
148
+ def sign_sql(column_name)
149
+ "sign(#{column_name})"
150
+ end
151
+
152
+ def sin_sql(column_name)
153
+ "sin(#{column_name})"
154
+ end
155
+
156
+ def square_root_sql(column_name)
157
+ "sqrt(#{column_name})"
158
+ end
159
+
160
+ def tan_sql(column_name)
161
+ "tan(#{column_name})"
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
167
+
168
+ ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:include, Searchlogic::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
@@ -0,0 +1,75 @@
1
+ module Searchlogic
2
+ module ActiveRecord
3
+ module ConnectionAdapters
4
+ module SQLiteAdapter
5
+ # Date functions
6
+ def microseconds_sql(column_name)
7
+ "((strftime('%f', #{column_name}) % 1) * 1000000)"
8
+ end
9
+
10
+ def milliseconds_sql(column_name)
11
+ "((strftime('%f', #{column_name}) % 1) * 1000)"
12
+ end
13
+
14
+ def second_sql(column_name)
15
+ "strftime('%S', #{column_name})"
16
+ end
17
+
18
+ def minute_sql(column_name)
19
+ "strftime('%M', #{column_name})"
20
+ end
21
+
22
+ def hour_sql(column_name)
23
+ "strftime('%H', #{column_name})"
24
+ end
25
+
26
+ def day_of_week_sql(column_name)
27
+ "strftime('%w', #{column_name})"
28
+ end
29
+
30
+ def day_of_month_sql(column_name)
31
+ "strftime('%d', #{column_name})"
32
+ end
33
+
34
+ def day_of_year_sql(column_name)
35
+ "strftime('%j', #{column_name})"
36
+ end
37
+
38
+ def week_sql(column_name)
39
+ "strftime('%W', #{column_name})"
40
+ end
41
+
42
+ def month_sql(column_name)
43
+ "strftime('%m', #{column_name})"
44
+ end
45
+
46
+ def year_sql(column_name)
47
+ "strftime('%Y', #{column_name})"
48
+ end
49
+
50
+ # String functions
51
+ def lower_sql(column_name)
52
+ "lower(#{column_name})"
53
+ end
54
+
55
+ def ltrim_sql(column_name)
56
+ "ltrim(#{column_name})"
57
+ end
58
+
59
+ def rtrim_sql(column_name)
60
+ "rtrim(#{column_name})"
61
+ end
62
+
63
+ def trim_sql(column_name)
64
+ "trim(#{column_name})"
65
+ end
66
+
67
+ def upper_sql(column_name)
68
+ "upper(#{column_name})"
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ ::ActiveRecord::ConnectionAdapters::SQLiteAdapter.send(:include, Searchlogic::ActiveRecord::ConnectionAdapters::SQLiteAdapter)
@@ -0,0 +1,159 @@
1
+ module Searchlogic
2
+ module Condition # :nodoc:
3
+ # = Conditions condition
4
+ #
5
+ # The base class for creating a condition. Your custom conditions should extend this class.
6
+ # See Searchlogic::Conditions::Base.register_condition on how to write your own condition.
7
+ class Base
8
+ include Shared::Utilities
9
+
10
+ attr_accessor :column, :column_for_type_cast, :column_sql, :column_sql_format, :klass, :table_name
11
+ class_inheritable_accessor :handle_array_value, :ignore_meaningless_value, :join_arrays_with_or, :value_type
12
+ self.ignore_meaningless_value = true
13
+
14
+ class << self
15
+ # Name of the condition type inferred from the class name
16
+ def condition_type_name
17
+ name.split("::").last.underscore
18
+ end
19
+
20
+ def handle_array_value?
21
+ handle_array_value == true
22
+ end
23
+
24
+ def ignore_meaningless_value? # :nodoc:
25
+ ignore_meaningless_value == true
26
+ end
27
+
28
+ def join_arrays_with_or?
29
+ join_arrays_with_or == true
30
+ end
31
+
32
+ # Determines what to call the condition for the model
33
+ #
34
+ # Searchlogic tries to create conditions on each model. Before it does this it passes the model to this method to see what to call the condition. If the condition type doesnt want to create a condition on
35
+ # a model it will just return nil and Searchlogic will skip over it.
36
+ def condition_names_for_model
37
+ []
38
+ end
39
+
40
+ # Same as condition_name_for_model, but for a model's column obj
41
+ def condition_names_for_column
42
+ [condition_type_name]
43
+ end
44
+ end
45
+
46
+ def initialize(klass, options = {})
47
+ self.klass = klass
48
+ self.table_name = options[:table_name] || klass.table_name
49
+
50
+ if options[:column]
51
+ self.column = options[:column].class < ::ActiveRecord::ConnectionAdapters::Column ? options[:column] : klass.columns_hash[options[:column].to_s]
52
+
53
+ if options[:column_for_type_cast]
54
+ self.column_for_type_cast = options[:column_for_type_cast]
55
+ else
56
+ type = (!self.class.value_type.blank? && self.class.value_type.to_s) || (!options[:column_type].blank? && options[:column_type].to_s) || column.sql_type
57
+ self.column_for_type_cast = column.class.new(column.name, column.default.to_s, type, column.null)
58
+ end
59
+
60
+ self.column_sql_format = options[:column_sql_format] || "{table}.{column}"
61
+ end
62
+ end
63
+
64
+ # Substitutes string vars with table and column name. Allows us to switch the column and table on the fly and have the condition update appropriately.
65
+ # The table name could be variable depending on the condition. Take STI and more than one child model is used in the condition, the first gets the parent table name, the rest get aliases.
66
+ def column_sql
67
+ column_sql_format.gsub("{table}", quoted_table_name).gsub("{column}", quoted_column_name)
68
+ end
69
+
70
+ # Allows nils to be meaninful values
71
+ def explicitly_set_value=(value)
72
+ @explicitly_set_value = value
73
+ end
74
+
75
+ # Need this if someone wants to actually use nil in a meaningful way
76
+ def explicitly_set_value?
77
+ @explicitly_set_value == true
78
+ end
79
+
80
+ def options
81
+ {:table_name => table_name, :column => column, :column_for_type_cast => column_for_type_cast, :column_sql_format => column_sql_format}
82
+ end
83
+
84
+ # You should refrain from overwriting this method, it performs various tasks before callign your to_conditions method, allowing you to keep to_conditions simple.
85
+ def sanitize(alt_value = nil) # :nodoc:
86
+ return if value_is_meaningless?
87
+ v = alt_value || value
88
+ if v.is_a?(Array) && !self.class.handle_array_value?
89
+ merge_conditions(*v.collect { |i| sanitize(i) } << {:any => self.class.join_arrays_with_or?})
90
+ else
91
+ v = v.utc if column && v.respond_to?(:utc) && [:time, :timestamp, :datetime].include?(column.type) && klass.time_zone_aware_attributes && !klass.skip_time_zone_conversion_for_attributes.include?(column.name.to_sym)
92
+ to_conditions(v)
93
+ end
94
+ end
95
+
96
+ # The value for the condition
97
+ def value
98
+ @casted_value ||= type_cast_value(@value)
99
+ end
100
+
101
+ # Sets the value for the condition
102
+ def value=(v)
103
+ self.explicitly_set_value = true
104
+ @casted_value = nil
105
+ @value = v
106
+ end
107
+
108
+ def value_is_meaningless? # :nodoc:
109
+ meaningless?(@value)
110
+ end
111
+
112
+ private
113
+ def like_condition_name
114
+ @like_condition_name ||= klass.connection.adapter_name == "PostgreSQL" ? "ILIKE" : "LIKE"
115
+ end
116
+
117
+ def meaningless?(v)
118
+ case v
119
+ when Array
120
+ v.each { |i| return false unless meaningless?(i) }
121
+ true
122
+ else
123
+ !explicitly_set_value? || (self.class.ignore_meaningless_value? && v != false && v.blank?)
124
+ end
125
+ end
126
+
127
+ def meaningful?(v)
128
+ !meaningless?(v)
129
+ end
130
+
131
+ def quote_column_name(column_name)
132
+ klass.connection.quote_column_name(column_name)
133
+ end
134
+
135
+ def quoted_column_name
136
+ quote_column_name(column.name)
137
+ end
138
+
139
+ def quote_table_name(table_name)
140
+ klass.connection.quote_table_name(table_name)
141
+ end
142
+
143
+ def quoted_table_name
144
+ quote_table_name(table_name)
145
+ end
146
+
147
+ def type_cast_value(v)
148
+ case v
149
+ when Array
150
+ v.collect { |i| type_cast_value(i) }.compact
151
+ else
152
+ return if meaningless?(v)
153
+ return v if !column_for_type_cast || !v.is_a?(String)
154
+ column_for_type_cast.type_cast(v)
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,17 @@
1
+ module Searchlogic
2
+ module Condition
3
+ class BeginsWith < Base
4
+ self.join_arrays_with_or = true
5
+
6
+ class << self
7
+ def condition_names_for_column
8
+ super + ["bw", "sw", "starts_with", "start"]
9
+ end
10
+ end
11
+
12
+ def to_conditions(value)
13
+ ["#{column_sql} LIKE ?", "#{value}%"]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ module Searchlogic
2
+ module Condition
3
+ class Blank < Base
4
+ self.value_type = :boolean
5
+
6
+ class << self
7
+ def condition_names_for_column
8
+ super + ["is_blank"]
9
+ end
10
+ end
11
+
12
+ def to_conditions(value)
13
+ if value == true
14
+ "#{column_sql} IS NULL or #{column_sql} = '' or #{column_sql} = false"
15
+ elsif value == false
16
+ "#{column_sql} IS NOT NULL and #{column_sql} != '' and #{column_sql} != false"
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ module Searchlogic
2
+ module Condition
3
+ class ChildOf < Tree
4
+ def to_conditions(value)
5
+ parent_association = klass.reflect_on_association(:parent)
6
+ foreign_key_name = (parent_association && parent_association.options[:foreign_key]) || "parent_id"
7
+ ["#{quoted_table_name}.#{quote_column_name(foreign_key_name)} = ?", (value.is_a?(klass) ? value.send(klass.primary_key) : value)]
8
+ end
9
+ end
10
+ end
11
+ end