ultrasphinx 1.8 → 1.9

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 (60) hide show
  1. data.tar.gz.sig +1 -3
  2. data/CHANGELOG +4 -0
  3. data/DEPLOYMENT_NOTES +7 -3
  4. data/Manifest +10 -0
  5. data/RAKE_TASKS +4 -2
  6. data/README +35 -18
  7. data/TODO +0 -1
  8. data/examples/default.base +24 -19
  9. data/lib/ultrasphinx.rb +1 -1
  10. data/lib/ultrasphinx/configure.rb +66 -25
  11. data/lib/ultrasphinx/core_extensions.rb +10 -1
  12. data/lib/ultrasphinx/is_indexed.rb +49 -6
  13. data/lib/ultrasphinx/search.rb +81 -69
  14. data/lib/ultrasphinx/search/internals.rb +61 -38
  15. data/lib/ultrasphinx/search/parser.rb +17 -7
  16. data/lib/ultrasphinx/ultrasphinx.rb +69 -16
  17. data/tasks/ultrasphinx.rake +47 -29
  18. data/test/config/ultrasphinx/test.base +50 -21
  19. data/test/integration/app/app/models/geo/address.rb +2 -1
  20. data/test/integration/app/app/models/person/user.rb +12 -3
  21. data/test/integration/app/app/models/seller.rb +2 -1
  22. data/test/integration/app/config/environment.rb +2 -0
  23. data/test/integration/app/config/ultrasphinx/default.base +16 -8
  24. data/test/integration/app/config/ultrasphinx/development.conf +319 -0
  25. data/test/integration/app/config/ultrasphinx/development.conf.canonical +152 -24
  26. data/test/integration/app/db/schema.rb +56 -0
  27. data/test/integration/app/test/fixtures/sellers.yml +1 -1
  28. data/test/integration/app/test/fixtures/users.yml +1 -1
  29. data/test/integration/app/test/unit/country_test.rb +8 -0
  30. data/test/integration/delta_test.rb +39 -0
  31. data/test/integration/search_test.rb +60 -1
  32. data/test/integration/spell_test.rb +6 -2
  33. data/test/profile/benchmark.rb +44 -0
  34. data/test/test_helper.rb +6 -1
  35. data/test/unit/parser_test.rb +21 -2
  36. data/ultrasphinx.gemspec +4 -4
  37. data/vendor/riddle/README +2 -2
  38. data/vendor/riddle/lib/riddle.rb +1 -1
  39. data/vendor/riddle/lib/riddle/client.rb +45 -10
  40. data/vendor/riddle/spec/fixtures/data/anchor.bin +0 -0
  41. data/vendor/riddle/spec/fixtures/data/any.bin +0 -0
  42. data/vendor/riddle/spec/fixtures/data/boolean.bin +0 -0
  43. data/vendor/riddle/spec/fixtures/data/distinct.bin +0 -0
  44. data/vendor/riddle/spec/fixtures/data/field_weights.bin +0 -0
  45. data/vendor/riddle/spec/fixtures/data/filter.bin +0 -0
  46. data/vendor/riddle/spec/fixtures/data/group.bin +0 -0
  47. data/vendor/riddle/spec/fixtures/data/index.bin +0 -0
  48. data/vendor/riddle/spec/fixtures/data/index_weights.bin +0 -0
  49. data/vendor/riddle/spec/fixtures/data/phrase.bin +0 -0
  50. data/vendor/riddle/spec/fixtures/data/rank_mode.bin +0 -0
  51. data/vendor/riddle/spec/fixtures/data/simple.bin +0 -0
  52. data/vendor/riddle/spec/fixtures/data/sort.bin +0 -0
  53. data/vendor/riddle/spec/fixtures/data/update_simple.bin +0 -0
  54. data/vendor/riddle/spec/fixtures/data/weights.bin +0 -0
  55. data/vendor/riddle/spec/fixtures/data_generator.php +130 -0
  56. data/vendor/riddle/spec/fixtures/sphinxapi.php +1066 -0
  57. data/vendor/riddle/spec/spec_helper.rb +1 -0
  58. data/vendor/riddle/spec/unit/client_spec.rb +18 -4
  59. metadata +12 -2
  60. metadata.gz.sig +0 -0
@@ -2,8 +2,7 @@
2
2
  module Ultrasphinx
3
3
  class Search
4
4
  module Parser
5
- # We could rewrite this in Treetop, but since it works well, what's the point? It's
6
- # probably faster this way. Ragel would speed it up, but be too much work.
5
+ # We could rewrite this in Treetop, but for now it works well.
7
6
 
8
7
  class Error < RuntimeError
9
8
  end
@@ -50,7 +49,15 @@ module Ultrasphinx
50
49
  query << ")" if field and contents.size > 1
51
50
  end
52
51
 
53
- # XXX swap the first pair if the order is reversed
52
+ # Collapse fieldsets early so that the swap doesn't split them
53
+ query.each_with_index do |token, index|
54
+ if token =~ /^@/
55
+ query[index] = "#{token} #{query[index + 1]}"
56
+ query[index + 1] = nil
57
+ end
58
+ end
59
+
60
+ # Swap the first pair if the order is reversed
54
61
  if [OPERATORS['NOT'], OPERATORS['OR']].include? query.first.upcase
55
62
  query[0], query[1] = query[1], query[0]
56
63
  end
@@ -83,10 +90,11 @@ module Ultrasphinx
83
90
  if subtoken =~ /\"(.*)\"/
84
91
  subtoken.sub!($1, $1.gsub(/[()]/, ''))
85
92
  end
86
-
87
- # add to the stream, converting the operator
93
+
94
+ # Add to the stream, converting the operator
88
95
  if !has_operator
89
- if OPERATORS.to_a.flatten.include? subtoken and index != (query.size - 1) # Operators at the end of the string are not parsed
96
+ if OPERATORS.to_a.flatten.include? subtoken and index != (query.size - 1)
97
+ # Note that operators at the end of the string are not parsed
90
98
  token_stream << OPERATORS[subtoken] || subtoken
91
99
  has_operator = true # flip
92
100
  else
@@ -103,7 +111,9 @@ module Ultrasphinx
103
111
  end
104
112
  end
105
113
 
106
- raise Error, "#{token_stream.inspect} is not a valid token stream" unless token_stream.size % 2 == 0
114
+ if token_stream.size.zero? or token_stream.size.odd?
115
+ raise Error, "#{token_stream.inspect} is not a valid token stream"
116
+ end
107
117
  token_stream.in_groups_of(2)
108
118
  end
109
119
 
@@ -32,12 +32,16 @@ module Ultrasphinx
32
32
 
33
33
  MAX_INT = 2**32-1
34
34
 
35
- MAX_WORDS = 2**16 # maximum number of stopwords built
35
+ MAX_WORDS = 2**16 # The maximum number of stopwords built
36
36
 
37
- UNIFIED_INDEX_NAME = "complete"
37
+ MAIN_INDEX = "main"
38
+
39
+ DELTA_INDEX = "delta"
40
+
41
+ INDEXES = [MAIN_INDEX, DELTA_INDEX]
38
42
 
39
43
  CONFIG_MAP = {
40
- # These must be symbols for key mapping against Rails itself
44
+ # These must be symbols for key mapping against Rails itself.
41
45
  :username => 'sql_user',
42
46
  :password => 'sql_pass',
43
47
  :host => 'sql_host',
@@ -59,12 +63,14 @@ module Ultrasphinx
59
63
  SQL_FUNCTIONS = {
60
64
  'mysql' => {
61
65
  'group_concat' => "CAST(GROUP_CONCAT(DISTINCT ? SEPARATOR ' ') AS CHAR)",
66
+ 'delta' => "DATE_SUB(NOW(), INTERVAL ? SECOND)",
62
67
  'hash' => "CAST(CRC32(?) AS unsigned)",
63
68
  'range_cast' => "?",
64
69
  'stored_procedures' => {}
65
70
  },
66
71
  'postgresql' => {
67
72
  'group_concat' => "GROUP_CONCAT(?)",
73
+ 'delta' => "(NOW() - '? SECOND'::interval)",
68
74
  'range_cast' => "cast(coalesce(?,1) AS integer)",
69
75
  'hash' => "CRC32(?)",
70
76
  'stored_procedures' => Hash[*(
@@ -90,7 +96,8 @@ sql_query_pre = ) + SQL_FUNCTIONS['postgresql']['stored_procedures'].values.join
90
96
 
91
97
  ADAPTER = ActiveRecord::Base.connection.instance_variable_get("@config")[:adapter] rescue 'mysql'
92
98
 
93
- # Install the stored procedures
99
+ # Install the stored procedures.
100
+ # XXX This shouldn't be done at every index, say the Postgres people.
94
101
  SQL_FUNCTIONS[ADAPTER]['stored_procedures'].each do |key, value|
95
102
  ActiveRecord::Base.connection.execute(value)
96
103
  end
@@ -106,34 +113,71 @@ sql_query_pre = ) + SQL_FUNCTIONS['postgresql']['stored_procedures'].values.join
106
113
  else
107
114
  STDERR.puts msg
108
115
  end
109
- end
116
+ end
117
+ nil # Explicitly return nil
110
118
  end
111
119
 
112
120
  # Configuration file parser.
113
121
  def self.options_for(heading, path)
114
- section = open(path).read[/^#{heading.gsub('/', '__')}\s*?\{(.*?)\}/m, 1]
122
+ # Evaluate ERB
123
+ template = ERB.new(File.open(path) {|f| f.read})
124
+ contents = template.result(binding)
115
125
 
116
- unless section
117
- Ultrasphinx.say "warning; heading #{heading} not found in #{path}; it may be corrupted. "
118
- {}
119
- else
126
+ # Find the correct heading.
127
+ section = contents[/^#{heading.gsub('/', '__')}\s*?\{(.*?)\}/m, 1]
128
+
129
+ if section
130
+ # Convert to a hash
120
131
  options = section.split("\n").map do |line|
121
132
  line =~ /\s*(.*?)\s*=\s*([^\#]*)/
122
133
  $1 ? [$1, $2.strip] : []
123
134
  end
124
135
  Hash[*options.flatten]
125
- end
126
-
136
+ else
137
+ # XXX Is it safe to raise here?
138
+ Ultrasphinx.say "warning; heading #{heading} not found in #{path}; it may be corrupted. "
139
+ {}
140
+ end
127
141
  end
142
+
143
+ def self.get_models_to_class_ids #:nodoc:
144
+ # Reading the conf file makes sure that we are in sync with the actual Sphinx index, not
145
+ # whatever you happened to change your models to most recently.
146
+ if File.exist? CONF_PATH
147
+ lines, hash = open(CONF_PATH).readlines, {}
148
+ msg = "#{CONF_PATH} file is corrupted. Please run 'rake ultrasphinx:configure'."
149
+
150
+ lines.each_with_index do |line, index|
151
+ # Find the main sources
152
+ if line =~ /^source ([\w\d_-]*)_#{MAIN_INDEX}/
153
+ # Derive the model name
154
+ model = $1.gsub('__', '/').classify
128
155
 
129
- # Introspect on the existing generated conf files
156
+ # Get the id modulus out of the adjacent sql_query
157
+ query = lines[index..-1].detect do |query_line|
158
+ query_line =~ /^sql_query /
159
+ end
160
+ raise ConfigurationError, msg unless query
161
+ hash[model] = query[/(\d*) AS class_id/, 1].to_i
162
+ end
163
+ end
164
+ raise ConfigurationError, msg unless hash.values.size == hash.values.uniq.size
165
+ hash
166
+ else
167
+ # We can't raise here because you may be generating the configuration for the first time
168
+ Ultrasphinx.say "configuration file not found for #{RAILS_ENV.inspect} environment"
169
+ Ultrasphinx.say "please run 'rake ultrasphinx:configure'"
170
+ end
171
+ end
172
+
173
+ # Introspect on the existing generated conf files.
130
174
  INDEXER_SETTINGS = options_for('indexer', BASE_PATH)
131
175
  CLIENT_SETTINGS = options_for('client', BASE_PATH)
132
176
  DAEMON_SETTINGS = options_for('searchd', BASE_PATH)
133
177
  SOURCE_SETTINGS = options_for('source', BASE_PATH)
134
178
  INDEX_SETTINGS = options_for('index', BASE_PATH)
135
179
 
136
- # Make sure there's a trailing slash
180
+ # Make sure there's a trailing slash.
137
181
  INDEX_SETTINGS['path'] = INDEX_SETTINGS['path'].chomp("/") + "/"
138
182
 
139
183
  DICTIONARY = CLIENT_SETTINGS['dictionary_name'] || 'ap'
@@ -148,7 +192,7 @@ sql_query_pre = ) + SQL_FUNCTIONS['postgresql']['stored_procedures'].values.join
148
192
  if File.exist? CONF_PATH
149
193
  begin
150
194
  if options_for(
151
- "source #{MODEL_CONFIGURATION.keys.first.tableize}",
195
+ "source #{MODEL_CONFIGURATION.keys.first.tableize}_#{MAIN_INDEX}",
152
196
  CONF_PATH
153
197
  )['sql_db'] != ActiveRecord::Base.connection.instance_variable_get("@config")[:database]
154
198
  say "warning; configured database name is out-of-date"
@@ -158,5 +202,14 @@ sql_query_pre = ) + SQL_FUNCTIONS['postgresql']['stored_procedures'].values.join
158
202
  end
159
203
  end
160
204
  end
161
-
205
+
206
+ # See if a delta index was defined.
207
+ def self.delta_index_present?
208
+ if File.exist?(CONF_PATH)
209
+ File.open(CONF_PATH).readlines.detect do |line|
210
+ line =~ /^index delta/
211
+ end
212
+ end
213
+ end
214
+
162
215
  end
@@ -12,7 +12,7 @@ namespace :ultrasphinx do
12
12
  desc "Bootstrap a full Sphinx environment"
13
13
  task :bootstrap => [:_environment, :configure, :index, :"daemon:restart"] do
14
14
  say "done"
15
- say "please restart Mongrel"
15
+ say "please restart your application containers"
16
16
  end
17
17
 
18
18
  desc "Rebuild the configuration file for this particular environment."
@@ -20,31 +20,23 @@ namespace :ultrasphinx do
20
20
  Ultrasphinx::Configure.run
21
21
  end
22
22
 
23
- desc "Reindex the database and send an update signal to the search daemon."
24
- task :index => [:_environment] do
25
- rotate = ultrasphinx_daemon_running?
26
- mkdir_p Ultrasphinx::INDEX_SETTINGS['path']
27
-
28
- cmd = "indexer --config '#{Ultrasphinx::CONF_PATH}'"
29
- cmd << " #{ENV['OPTS']} " if ENV['OPTS']
30
- cmd << " --rotate" if rotate
31
- cmd << " #{Ultrasphinx::UNIFIED_INDEX_NAME}"
32
-
33
- say cmd
34
- system cmd
35
-
36
- if rotate
37
- sleep(4)
38
- failed = Dir[Ultrasphinx::INDEX_SETTINGS['path'] + "/*.new.*"]
39
- if failed.any?
40
- say "warning; index failed to rotate! Deleting new indexes"
41
- failed.each {|f| File.delete f }
42
- else
43
- say "index rotated ok"
44
- end
23
+ namespace :index do
24
+ desc "Reindex and rotate the main index."
25
+ task :main => [:_environment] do
26
+ ultrasphinx_index(Ultrasphinx::MAIN_INDEX)
45
27
  end
28
+
29
+ desc "Reindex and rotate the delta index."
30
+ task :delta => [:_environment] do
31
+ ultrasphinx_index(Ultrasphinx::DELTA_INDEX)
32
+ end
33
+
34
+ end
35
+
36
+ desc "Reindex and rotate all indexes."
37
+ task :index => [:_environment] do
38
+ ultrasphinx_index("--all")
46
39
  end
47
-
48
40
 
49
41
  namespace :daemon do
50
42
  desc "Start the search daemon"
@@ -125,30 +117,56 @@ namespace :us do
125
117
  task :restart => ["ultrasphinx:daemon:restart"]
126
118
  task :stop => ["ultrasphinx:daemon:stop"]
127
119
  task :stat => ["ultrasphinx:daemon:status"]
120
+ task :index => ["ultrasphinx:index"]
128
121
  task :in => ["ultrasphinx:index"]
122
+ task :main => ["ultrasphinx:index:main"]
123
+ task :delta => ["ultrasphinx:index:delta"]
129
124
  task :spell => ["ultrasphinx:spelling:build"]
130
125
  task :conf => ["ultrasphinx:configure"]
131
126
  task :boot => ["ultrasphinx:bootstrap"]
132
127
  end
133
128
 
134
- # support methods
129
+ # Support methods
135
130
 
136
131
  def ultrasphinx_daemon_pid
137
- open(open(Ultrasphinx::BASE_PATH).readlines.map do |line|
138
- line[/^\s*pid_file\s*=\s*([^\s\#]*)/, 1]
139
- end.compact.first).readline.chomp rescue nil # XXX ridiculous
132
+ open(Ultrasphinx::DAEMON_SETTINGS['pid_file']).readline.chomp rescue nil
140
133
  end
141
134
 
142
135
  def ultrasphinx_daemon_running?
143
136
  if ultrasphinx_daemon_pid and `ps #{ultrasphinx_daemon_pid} | wc`.to_i > 1
144
137
  true
145
138
  else
146
- # remove bogus lockfiles
139
+ # Remove bogus lockfiles.
147
140
  Dir[Ultrasphinx::INDEX_SETTINGS["path"] + "*spl"].each {|file| File.delete(file)}
148
141
  false
149
142
  end
150
143
  end
151
144
 
145
+ def ultrasphinx_index(index)
146
+ rotate = ultrasphinx_daemon_running?
147
+ index_path = Ultrasphinx::INDEX_SETTINGS['path']
148
+ mkdir_p index_path unless File.directory? index_path
149
+
150
+ cmd = "indexer --config '#{Ultrasphinx::CONF_PATH}'"
151
+ cmd << " #{ENV['OPTS']} " if ENV['OPTS']
152
+ cmd << " --rotate" if rotate
153
+ cmd << " #{index}"
154
+
155
+ say "$ #{cmd}"
156
+ system cmd
157
+
158
+ if rotate
159
+ sleep(4)
160
+ failed = Dir[index_path + "/*.new.*"]
161
+ if failed.any?
162
+ say "warning; index failed to rotate! Deleting new indexes"
163
+ failed.each {|f| File.delete f }
164
+ else
165
+ say "index rotated ok"
166
+ end
167
+ end
168
+ end
169
+
152
170
  def say msg
153
171
  Ultrasphinx.say msg
154
172
  end
@@ -2,55 +2,84 @@
2
2
  #
3
3
  # Sphinx/Ultrasphinx user-configurable options.
4
4
  #
5
- # Copy this file to RAILS_ROOT/config/ultrasphinx.
6
- # You can use individual namespaces if you want (e.g. "development.base").
5
+ # Copy this file to RAILS_ROOT/config/ultrasphinx. You can use individual
6
+ # namespaces if you want (e.g. development.base, production.base,
7
+ # test.base). Note that ERb is also allowed.
8
+ #
9
+ # This file should not be handed directly to Sphinx. Use the rake task
10
+ #
11
+ # rake ultrasphinx::configure
12
+ #
13
+ # to generate a parallel default.conf file. This is the file that Sphinx itself will
14
+ # use. The Ultrasphinx rake tasks automatically pass the correct file to
15
+ # to Sphinx.
16
+ #
17
+ # It is safe to edit .base files by hand. It is not safe to edit the generated
18
+ # .conf files. Do not symlink the .conf file to the .base file; it's wrong.
7
19
  #
8
20
 
21
+ <% path = "/opt/local/var/db/sphinx/" %>
22
+
23
+ # Indexing options
9
24
  indexer
10
- {
11
- # Indexer running options
25
+ {
12
26
  mem_limit = 256M
27
+
28
+ # Ultrasphinx-specific key
29
+ delta = <%= 1.day + 30.minutes %>
13
30
  }
14
31
 
32
+ # Daemon options
15
33
  searchd
16
- {
17
- # Daemon options
34
+ {
18
35
  # What interface the search daemon should listen on and where to store its logs
19
36
  address = 0.0.0.0
20
37
  port = 3312
21
- log = /opt/local/var/db/sphinx/log/searchd.log
22
- query_log = /opt/local/var/db/sphinx/log/query.log
38
+ seamless_rotate = 1
39
+ log = <%= path %>log/searchd.log
40
+ query_log = <%= path %>log/query.log
23
41
  read_timeout = 5
24
42
  max_children = 300
25
- pid_file = /opt/local/var/db/sphinx/log/searchd.pid
43
+ pid_file = <%= path %>log/searchd.pid
26
44
  max_matches = 100000
27
45
  }
28
46
 
47
+ # Client options
29
48
  client
30
49
  {
31
- # Client options
50
+ # Name of the Aspell dictionary (two letters max)
51
+ dictionary_name = ap
52
+
32
53
  # How your application connects to the search daemon (not necessarily the same as above)
33
54
  server_host = localhost
34
55
  server_port = 3312
35
56
  }
36
57
 
58
+ # Individual SQL source options
37
59
  source
38
- {
39
- # Individual SQL source options
40
- sql_range_step = 20000
41
- strip_html = 0
42
- index_html_attrs =
60
+ {
61
+ sql_ranged_throttle = 0
62
+ sql_range_step = 5000
43
63
  sql_query_post =
44
64
  }
45
65
 
66
+ # Index building options
46
67
  index
47
- {
48
- # Index building options
49
- path = /opt/local/var/db/sphinx/
50
- docinfo = extern # just leave this alone
68
+ {
69
+ path = <%= path %>
70
+ docinfo = extern # Just leave this alone
51
71
  morphology = stem_en
52
- stopwords = # /path/to/stopwords.txt
72
+ stopwords = # <%= path %>/ap-stopwords.txt
53
73
  min_word_len = 1
74
+
75
+ # HTML-specific options
76
+ html_strip = 0
77
+ html_index_attrs =
78
+
79
+ # Enable these if you need wildcard searching. They will slow down indexing significantly.
80
+ # min_infix_len = 1
81
+ # enable_star = 1
82
+
54
83
  charset_type = utf-8 # or sbcs (Single Byte Character Set)
55
- charset_table = 0..9, A..Z->a..z, -, _, ., &, a..z,
84
+ charset_table = 0..9, A..Z->a..z, -, _, ., &, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F,U+C5->U+E5, U+E5, U+C4->U+E4, U+E4, U+D6->U+F6, U+F6, U+16B, U+0c1->a, U+0c4->a, U+0c9->e, U+0cd->i, U+0d3->o, U+0d4->o, U+0da->u, U+0dd->y, U+0e1->a, U+0e4->a, U+0e9->e, U+0ed->i, U+0f3->o, U+0f4->o, U+0fa->u, U+0fd->y, U+104->U+105, U+105, U+106->U+107, U+10c->c, U+10d->c, U+10e->d, U+10f->d, U+116->U+117, U+117, U+118->U+119, U+11a->e, U+11b->e, U+12E->U+12F, U+12F, U+139->l, U+13a->l, U+13d->l, U+13e->l, U+141->U+142, U+142, U+143->U+144, U+144,U+147->n, U+148->n, U+154->r, U+155->r, U+158->r, U+159->r, U+15A->U+15B, U+15B, U+160->s, U+160->U+161, U+161->s, U+164->t, U+165->t, U+16A->U+16B, U+16B, U+16e->u, U+16f->u, U+172->U+173, U+173, U+179->U+17A, U+17A, U+17B->U+17C, U+17C, U+17d->z, U+17e->z,
56
85
  }
@@ -4,5 +4,6 @@ class Geo::Address < ActiveRecord::Base
4
4
 
5
5
  is_indexed 'fields' => ['name'],
6
6
  'concatenate' => [{'fields' => ['line_1', 'line_2', 'city', 'province_region', 'zip_postal_code'], 'as' => 'content'}],
7
- 'include' => [{'association_name' => 'state', 'field' => 'name', 'as' => 'state'}]
7
+ 'include' => [{'association_name' => 'state', 'field' => 'name', 'as' => 'state'}],
8
+ 'delta' => true
8
9
  end
@@ -3,8 +3,17 @@ class User < ActiveRecord::Base
3
3
  has_one :address, :class_name => "Geo::Address"
4
4
 
5
5
  is_indexed :fields => ['login', 'email', 'deleted'],
6
- :include => [{:association_name => 'specific_seller', :field => 'company_name', :as => 'company'},
7
- {:class_name => 'Seller', :field => 'sellers_two.company_name', :as => 'company_two', 'association_sql' => 'LEFT OUTER JOIN sellers AS sellers_two ON users.id = sellers_two.user_id', 'function_sql' => "REPLACE(?, '6', ' replacement ')"}],
8
- :conditions => "deleted = '0'"
6
+ :include => [{:association_name => 'specific_seller', :field => 'company_name', :as => 'company', :facet => true},
7
+ {:class_name => 'Seller', :field => 'sellers_two.company_name', :as => 'company_two', :facet => true, 'association_sql' => 'LEFT OUTER JOIN sellers AS sellers_two ON users.id = sellers_two.user_id', 'function_sql' => "REPLACE(?, '6', ' replacement ')"}],
8
+ :conditions => "deleted = '0'",
9
+ :delta => {:field => 'created_at'}
10
+
11
+ def self.find_all_by_id(*args)
12
+ raise "Wrong finder"
13
+ end
14
+
15
+ def self.custom_find(*args)
16
+ method_missing(:find_all_by_id, *args)
17
+ end
9
18
 
10
19
  end