litestack 0.4.2 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.standard.yml +3 -1
  3. data/BENCHMARKS.md +3 -3
  4. data/CAVEATS.md +20 -0
  5. data/CHANGELOG.md +40 -1
  6. data/FILESYSTEMS.md +55 -0
  7. data/Gemfile +2 -0
  8. data/README.md +8 -4
  9. data/ROADMAP.md +6 -6
  10. data/assets/litestack_advantage.png +0 -0
  11. data/bench/bench.rb +2 -0
  12. data/bench/bench_cache_rails.rb +33 -2
  13. data/bench/bench_cache_raw.rb +36 -12
  14. data/bench/bench_jobs_rails.rb +3 -3
  15. data/bench/bench_jobs_raw.rb +3 -3
  16. data/bin/liteboard +16 -13
  17. data/gemfiles/rails70.gemfile +5 -0
  18. data/gemfiles/rails71.gemfile +5 -0
  19. data/gemfiles/rails71.gemfile.lock +264 -0
  20. data/lib/active_job/queue_adapters/litejob_adapter.rb +11 -3
  21. data/lib/active_record/connection_adapters/litedb_adapter.rb +8 -0
  22. data/lib/active_support/cache/litecache.rb +40 -7
  23. data/lib/generators/litestack/install/install_generator.rb +2 -2
  24. data/lib/generators/litestack/install/templates/cable.yml +0 -3
  25. data/lib/litestack/liteboard/liteboard.rb +15 -19
  26. data/lib/litestack/liteboard/views/litecable.erb +1 -1
  27. data/lib/litestack/litecable.rb +1 -1
  28. data/lib/litestack/litecache.rb +51 -19
  29. data/lib/litestack/litecache.sql.yml +7 -5
  30. data/lib/litestack/litedb.rb +5 -1
  31. data/lib/litestack/litejob.rb +1 -1
  32. data/lib/litestack/litejobqueue.rb +24 -14
  33. data/lib/litestack/litemetric.rb +7 -6
  34. data/lib/litestack/litemetric.sql.yml +1 -1
  35. data/lib/litestack/litemetric_collector.sql.yml +1 -1
  36. data/lib/litestack/litequeue.rb +17 -2
  37. data/lib/litestack/litequeue.sql.yml +38 -5
  38. data/lib/litestack/litescheduler.rb +9 -4
  39. data/lib/litestack/litesearch/index.rb +11 -10
  40. data/lib/litestack/litesearch/model.rb +61 -3
  41. data/lib/litestack/litesearch/schema.rb +7 -2
  42. data/lib/litestack/litesearch/schema_adapters/backed_adapter.rb +69 -25
  43. data/lib/litestack/litesearch/schema_adapters.rb +4 -4
  44. data/lib/litestack/litesearch.rb +2 -2
  45. data/lib/litestack/litesupport.rb +9 -7
  46. data/lib/litestack/railtie.rb +4 -2
  47. data/lib/litestack/version.rb +1 -1
  48. data/lib/litestack.rb +15 -15
  49. data/lib/railties/rails/commands/dbconsole.rb +5 -5
  50. data/lib/sequel/adapters/litedb.rb +9 -1
  51. data/lib/sequel/adapters/shared/litedb.rb +2 -2
  52. data/scripts/build_metrics.rb +2 -2
  53. data/scripts/test_cable.rb +1 -1
  54. metadata +105 -56
  55. data/Gemfile.lock +0 -92
@@ -3,10 +3,10 @@ schema:
3
3
  create_table_data: >
4
4
  CREATE TABLE IF NOT EXISTS data(
5
5
  id TEXT PRIMARY KEY,
6
- value TEXT,
6
+ value ANY,
7
7
  expires_in INTEGER,
8
8
  last_used INTEGER
9
- );
9
+ ) STRICT;
10
10
  create_expiry_index: >
11
11
  CREATE INDEX IF NOT EXISTS expiry_index ON data (expires_in);
12
12
  create_last_used_index: >
@@ -14,7 +14,7 @@ schema:
14
14
 
15
15
  stmts:
16
16
  pruner: >
17
- DELETE FROM data WHERE expires_in <= $1;
17
+ DELETE FROM data WHERE expires_in <= unixepoch('now');
18
18
 
19
19
  extra_pruner: >
20
20
  DELETE FROM data WHERE id IN (
@@ -49,7 +49,7 @@ stmts:
49
49
  value = EXCLUDED.value,
50
50
  last_used = EXCLUDED.last_used,
51
51
  expires_in = EXCLUDED.expires_in;
52
-
52
+
53
53
  inserter: >
54
54
  INSERT INTO data (id, value, expires_in, last_used)
55
55
  VALUES ($1, $2, unixepoch('now') + $3, unixepoch('now'))
@@ -65,7 +65,7 @@ stmts:
65
65
  SELECT id FROM data WHERE id = $1;
66
66
 
67
67
  getter: >
68
- SELECT id, value, expires_in FROM data WHERE id = $1;
68
+ SELECT id, value, expires_in FROM data WHERE id = $1 AND expires_in >= unixepoch('now');
69
69
 
70
70
  deleter: >
71
71
  delete FROM data WHERE id = $1 RETURNING value;
@@ -79,6 +79,8 @@ stmts:
79
79
  last_used = EXCLUDED.last_used,
80
80
  expires_in = EXCLUDED.expires_in;
81
81
 
82
+
83
+
82
84
  counter: >
83
85
  SELECT count(*) FROM data;
84
86
 
@@ -15,7 +15,7 @@ class Litedb < ::SQLite3::Database
15
15
  # add litesearch support
16
16
  include Litesearch
17
17
 
18
- # overrride the original initilaizer to allow for connection configuration
18
+ # override the original initilaizer to allow for connection configuration
19
19
  def initialize(file, options = {}, zfs = nil)
20
20
  if block_given?
21
21
  super(file, options, zfs) do |db|
@@ -31,6 +31,10 @@ class Litedb < ::SQLite3::Database
31
31
  collect_metrics if @collecting_metrics
32
32
  end
33
33
 
34
+ def sqlite_version
35
+ SQLite3::SQLITE_VERSION_NUMBER
36
+ end
37
+
34
38
  def collecting_metrics?
35
39
  @collecting_metrics
36
40
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_stringe_literal: true
2
2
 
3
- require_relative "./litejobqueue"
3
+ require_relative "litejobqueue"
4
4
 
5
5
  ##
6
6
  # Litejob is a Ruby module that enables seamless integration of the Litejobqueue job queueing system into Ruby applications. By including the Litejob module in a class and implementing the #perform method, developers can easily enqueue and process jobs asynchronously.
@@ -1,7 +1,7 @@
1
1
  # frozen_stringe_literal: true
2
2
 
3
- require_relative "./litequeue"
4
- require_relative "./litemetric"
3
+ require_relative "litequeue"
4
+ require_relative "litemetric"
5
5
 
6
6
  ##
7
7
  # Litejobqueue is a job queueing and processing system designed for Ruby applications. It is built on top of SQLite, which is an embedded relational database management system that is #lightweight and fast.
@@ -15,7 +15,7 @@ class Litejobqueue < Litequeue
15
15
  include Litemetric::Measurable
16
16
 
17
17
  # the default options for the job queue
18
- # can be overriden by passing new options in a hash
18
+ # can be overridden by passing new options in a hash
19
19
  # to Litejobqueue.new, it will also be then passed to the underlying Litequeue object
20
20
  # config_path: "./litejob.yml" -> were to find the configuration file (if any)
21
21
  # path: "./db/queue.db"
@@ -136,20 +136,28 @@ class Litejobqueue < Litequeue
136
136
  # @@queue = nil
137
137
  close
138
138
  end
139
-
139
+
140
140
  private
141
+
142
+ def prepare_search_options(opts)
143
+ sql_opts = super(opts)
144
+ sql_opts[:klass] = opts[:klass]
145
+ sql_opts[:params] = opts[:params]
146
+ sql_opts
147
+ end
141
148
 
142
149
  def exit_callback
143
150
  @running = false # stop all workers
144
- return unless @jobs_in_flight > 0
145
- puts "--- Litejob detected an exit, cleaning up"
146
- index = 0
147
- while @jobs_in_flight > 0 && index < 30 # 3 seconds grace period for jobs to finish
148
- puts "--- Waiting for #{@jobs_in_flight} jobs to finish"
149
- sleep 0.1
150
- index += 1
151
+ if @jobs_in_flight > 0
152
+ puts "--- Litejob detected an exit, cleaning up"
153
+ index = 0
154
+ while @jobs_in_flight > 0 && index < 30 # 3 seconds grace period for jobs to finish
155
+ puts "--- Waiting for #{@jobs_in_flight} jobs to finish"
156
+ sleep 0.1
157
+ index += 1
158
+ end
159
+ puts " --- Exiting with #{@jobs_in_flight} jobs in flight"
151
160
  end
152
- puts " --- Exiting with #{@jobs_in_flight} jobs in flight"
153
161
  end
154
162
 
155
163
  def setup
@@ -179,6 +187,8 @@ class Litejobqueue < Litequeue
179
187
 
180
188
  # create a worker according to environment
181
189
  def create_worker
190
+ # temporarily stop this feature until a better solution is implemented
191
+ #return if defined?(Rails) && !defined?(Rails::Server)
182
192
  Litescheduler.spawn do
183
193
  worker_sleep_index = 0
184
194
  while @running
@@ -186,7 +196,7 @@ class Litejobqueue < Litequeue
186
196
  @queues.each do |priority, queues| # iterate through the levels
187
197
  queues.each do |queue, spawns| # iterate through the queues in the level
188
198
  batched = 0
189
-
199
+
190
200
  while (batched < priority) && (payload = pop(queue, 1)) # fearlessly use the same queue object
191
201
  capture(:dequeue, queue)
192
202
  processed += 1
@@ -201,7 +211,7 @@ class Litejobqueue < Litequeue
201
211
  end
202
212
  if processed == 0
203
213
  sleep @options[:sleep_intervals][worker_sleep_index]
204
- worker_sleep_index += 1 if worker_sleep_index < @options[:sleep_intervals].length - 1
214
+ worker_sleep_index += 1 if worker_sleep_index < (@options[:sleep_intervals].length - 1)
205
215
  else
206
216
  worker_sleep_index = 0 # reset the index
207
217
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "singleton"
4
4
 
5
- require_relative "./litesupport"
5
+ require_relative "litesupport"
6
6
 
7
7
  # this class is a singleton
8
8
  # and should remain so
@@ -148,10 +148,11 @@ class Litemetric
148
148
  end
149
149
 
150
150
  def exit_callback
151
- return unless @collector.count > 0
152
- warn "--- Litemetric detected an exit, flushing metrics"
153
151
  @running = false
154
- @collector.flush
152
+ if @collector.count > 0
153
+ warn "--- Litemetric detected an exit, flushing metrics"
154
+ @collector.flush
155
+ end
155
156
  end
156
157
 
157
158
  def setup
@@ -203,8 +204,8 @@ class Litemetric
203
204
  def create_snapshotter
204
205
  Litescheduler.spawn do
205
206
  while @running
206
- sleep @litemetric.options[:snapshot_interval]
207
207
  capture_snapshot
208
+ sleep @litemetric.options[:snapshot_interval]
208
209
  end
209
210
  end
210
211
  end
@@ -275,7 +276,7 @@ class Litemetric
275
276
  def capture_single_key(topic, event, key, value, time = nil)
276
277
  run_stmt(:capture_event, topic.to_s, event.to_s, key.to_s, time, 1, value)
277
278
  end
278
-
279
+
279
280
  def count
280
281
  run_stmt(:event_count)[0][0]
281
282
  end
@@ -19,7 +19,7 @@ schema:
19
19
  created_at INTEGER DEFAULT((unixepoch()/300*300)) NOT NULL,
20
20
  resolution TEXT DEFAULT('minute') NOT NULL,
21
21
  PRIMARY KEY(resolution, created_at, topic, name, key)
22
- ) STRICT;
22
+ );
23
23
 
24
24
  create_topic_index_on_events: >
25
25
  CREATE INDEX events_topic_index ON events (resolution, created_at, topic) WHERE name = '___';
@@ -12,7 +12,7 @@ schema:
12
12
  created_at INTEGER DEFAULT((unixepoch()/300*300)) NOT NULL ON CONFLICT REPLACE,
13
13
  resolution TEXT DEFAULT('minute') NOT NULL,
14
14
  PRIMARY KEY(resolution, created_at, topic, name, key)
15
- ) STRICT;
15
+ );
16
16
 
17
17
  stmts:
18
18
  capture_event: >
@@ -13,7 +13,7 @@ require_relative "litesupport"
13
13
 
14
14
  class Litequeue
15
15
  # the default options for the queue
16
- # can be overriden by passing new options in a hash
16
+ # can be overridden by passing new options in a hash
17
17
  # to Litequeue.new
18
18
  # path: "./queue.db"
19
19
  # mmap_size: 128 * 1024 * 1024 -> 128MB to be held in memory
@@ -80,7 +80,7 @@ class Litequeue
80
80
  run_sql("DELETE FROM queue WHERE iif(?1 IS NOT NULL, name = ?1, TRUE)", queue)
81
81
  end
82
82
 
83
- # returns a count of entries in all queues, or if a queue name is given, reutrns the count of entries in that queue
83
+ # returns a count of entries in all queues, or if a queue name is given, returns the count of entries in that queue
84
84
  def count(queue = nil)
85
85
  run_sql("SELECT count(*) FROM queue WHERE iif(?1 IS NOT NULL, name = ?1, TRUE)", queue)[0][0]
86
86
  end
@@ -111,9 +111,24 @@ class Litequeue
111
111
  queues: queues
112
112
  }
113
113
  end
114
+
115
+ def find(opts = {})
116
+ run_stmt(:search, prepare_search_options(opts))
117
+ end
114
118
 
115
119
  private
116
120
 
121
+ def prepare_search_options(opts)
122
+ sql_opts = {}
123
+ sql_opts[:fire_at_from] = opts[:fire_at][0] rescue nil
124
+ sql_opts[:fire_at_to] = opts[:fire_at][1] rescue nil
125
+ sql_opts[:created_at_from] = opts[:created_at][0] rescue nil
126
+ sql_opts[:created_at_to] = opts[:created_at][1] rescue nil
127
+ sql_opts[:name] = opts[:queue]
128
+ sql_opts[:dir] = opts[:dir] == :desc ? -1 : 1
129
+ sql_opts
130
+ end
131
+
117
132
  def create_connection
118
133
  super("#{__dir__}/litequeue.sql.yml") do |conn|
119
134
  conn.wal_autocheckpoint = 10000
@@ -4,7 +4,7 @@ schema:
4
4
  CREATE TABLE IF NOT EXISTS queue(
5
5
  id TEXT PRIMARY KEY DEFAULT(hex(randomblob(32))) NOT NULL ON CONFLICT REPLACE,
6
6
  name TEXT DEFAULT('default') NOT NULL ON CONFLICT REPLACE,
7
- fire_at INTEGER DEFAULT(unixepoch()) NOT NULL ON CONFLICT REPLACE,
7
+ fire_at REAL DEFAULT(unixepoch('subsec')) NOT NULL ON CONFLICT REPLACE,
8
8
  value TEXT,
9
9
  created_at INTEGER DEFAULT(unixepoch()) NOT NULL ON CONFLICT REPLACE
10
10
  ) WITHOUT ROWID;
@@ -16,21 +16,36 @@ stmts:
16
16
 
17
17
  push: >
18
18
  INSERT INTO queue(id, name, fire_at, value)
19
- VALUES (hex(randomblob(32)), $1, (unixepoch() + $2), $3)
19
+ VALUES (hex(randomblob(32)), $1, (unixepoch('subsec') + $2), $3)
20
20
  RETURNING id, name;
21
21
 
22
22
  repush: >
23
23
  INSERT INTO queue(id, name, fire_at, value)
24
- VALUES (?, ?, (unixepoch() + ?), ?)
24
+ VALUES (?, ?, (unixepoch('subsec') + ?), ?)
25
25
  RETURNING name;
26
26
 
27
+ pop_in_place: >
28
+ UPDATE queue
29
+ SET
30
+ name = '->:' || name,
31
+ fire_at = unixepoch('subsec')
32
+ WHERE (name, fire_at, id)
33
+ IN (
34
+ SELECT name, fire_at, id FROM queue
35
+ WHERE name = ifnull($1, 'default')
36
+ AND fire_at <= (unixepoch('subsec'))
37
+ ORDER BY fire_at ASC
38
+ LIMIT ifnull($2, 1)
39
+ )
40
+ RETURNING id, value;
41
+
27
42
  pop: >
28
43
  DELETE FROM queue
29
44
  WHERE (name, fire_at, id)
30
45
  IN (
31
46
  SELECT name, fire_at, id FROM queue
32
47
  WHERE name = ifnull($1, 'default')
33
- AND fire_at <= (unixepoch())
48
+ AND fire_at <= (unixepoch('subsec'))
34
49
  ORDER BY fire_at ASC
35
50
  LIMIT ifnull($2, 1)
36
51
  )
@@ -40,7 +55,25 @@ stmts:
40
55
  DELETE FROM queue
41
56
  WHERE id = $1
42
57
  RETURNING value;
43
-
58
+
59
+ search: >
60
+ SELECT *, :params, value ->> '$.params', value ->> '$.params' LIKE '%'||'three_second'||'%' FROM queue
61
+ WHERE
62
+ name = ifnull(:name, name)
63
+ AND
64
+ iif(:fire_at_from, fire_at >= :fire_at_from, true)
65
+ AND
66
+ iif(:fire_at_to, fire_at <= :fire_at_to, true)
67
+ AND
68
+ iif(:created_at_from, created_at >= :created_at_from, true)
69
+ AND
70
+ iif(:created_at_to, created_at <= :created_at_to, true)
71
+ AND
72
+ iif(:klass IS NOT NULL, value ->> '$.klass' LIKE '%'||:klass||'%', true)
73
+ AND
74
+ iif(:params IS NOT NULL, value ->> '$.params' LIKE '%'||:params||'%', true)
75
+ ORDER BY created_at * :dir;
76
+
44
77
  info: >
45
78
  SELECT
46
79
  name,
@@ -29,7 +29,7 @@ module Litescheduler
29
29
  end
30
30
 
31
31
  def self.storage
32
- if backend == :fiber || backend == :poylphony
32
+ if fiber_backed?
33
33
  Fiber.current.storage
34
34
  else
35
35
  Thread.current
@@ -37,7 +37,7 @@ module Litescheduler
37
37
  end
38
38
 
39
39
  def self.current
40
- if backend == :fiber || backend == :poylphony
40
+ if fiber_backed?
41
41
  Fiber.current
42
42
  else
43
43
  Thread.current
@@ -64,7 +64,7 @@ module Litescheduler
64
64
  # they must send (true) as a parameter to this method
65
65
  # else it is a no-op for fibers
66
66
  def self.synchronize(fiber_sync = false, &block)
67
- if (backend == :fiber) || (backend == :polyphony)
67
+ if fiber_backed?
68
68
  yield # do nothing, just run the block as is
69
69
  else
70
70
  mutex.synchronize(&block)
@@ -72,7 +72,7 @@ module Litescheduler
72
72
  end
73
73
 
74
74
  def self.max_contexts
75
- return 50 if backend == :fiber || backend == :polyphony
75
+ return 50 if fiber_backed?
76
76
  5
77
77
  end
78
78
 
@@ -81,4 +81,9 @@ module Litescheduler
81
81
  # a single mutex per process (is that ok?)
82
82
  @@mutex ||= Mutex.new
83
83
  end
84
+
85
+ def self.fiber_backed?
86
+ backend == :fiber || backend == :polyphony
87
+ end
88
+ private_class_method :fiber_backed?
84
89
  end
@@ -1,5 +1,5 @@
1
1
  require "oj"
2
- require_relative "./schema"
2
+ require_relative "schema"
3
3
 
4
4
  class Litesearch::Index
5
5
  DEFAULT_SEARCH_OPTIONS = {limit: 25, offset: 0}
@@ -71,7 +71,7 @@ class Litesearch::Index
71
71
 
72
72
  def rebuild!
73
73
  if @db.transaction_active?
74
- do_rebuild
74
+ do_rebuild
75
75
  else
76
76
  @db.transaction(:immediate) { do_rebuild }
77
77
  end
@@ -102,19 +102,20 @@ class Litesearch::Index
102
102
  rs = @stmts[:search].execute(term, options[:limit], options[:offset])
103
103
  generate_results(rs)
104
104
  end
105
-
106
- def similar(id, limit=10)
105
+
106
+ def similar(id, limit = 10)
107
107
  # pp term = @db.execute(@schema.sql_for(:similarity_query), id)
108
- if @schema.schema[:tokenizer] == :trigram
108
+ rs = if @schema.schema[:tokenizer] == :trigram
109
109
  # just use the normal similarity approach for now
110
110
  # need to recondisder that for trigram indexes later
111
- rs = @stmts[:similar].execute(id, limit)
111
+ @stmts[:similar].execute(id, limit) # standard:disable Style/IdenticalConditionalBranches
112
112
  else
113
- rs = @stmts[:similar].execute(id, limit)
113
+ @stmts[:similar].execute(id, limit) # standard:disable Style/IdenticalConditionalBranches
114
114
  end
115
+
115
116
  generate_results(rs)
116
117
  end
117
-
118
+
118
119
  def clear!
119
120
  @stmts[:delete_all].execute!(id)
120
121
  end
@@ -140,7 +141,7 @@ class Litesearch::Index
140
141
  else
141
142
  result = rs.to_a
142
143
  end
143
- result
144
+ result
144
145
  end
145
146
 
146
147
  def exists?(name)
@@ -175,7 +176,7 @@ class Litesearch::Index
175
176
 
176
177
  def do_modify(new_schema)
177
178
  changes = @schema.compare(new_schema)
178
- # ensure the new schema maintains feild order
179
+ # ensure the new schema maintains field order
179
180
  new_schema.order_fields(@schema)
180
181
  # with the changes object decide what needs to be done to the schema
181
182
  requires_schema_change = false
@@ -11,6 +11,7 @@ module Litesearch::Model
11
11
  klass.include Litesearch::Model::ActiveRecordInstanceMethods
12
12
  klass.extend Litesearch::Model::ActiveRecordClassMethods
13
13
  ActiveRecord::Base.extend Litesearch::Model::BaseClassMethods
14
+ Litesearch::Schema.prepend Litesearch::Model::ActiveRecordSchemaMethods
14
15
  end
15
16
  end
16
17
 
@@ -21,7 +22,7 @@ module Litesearch::Model
21
22
  end
22
23
 
23
24
  module InstanceMethods
24
- def similar(limit=10)
25
+ def similar(limit = 10)
25
26
  conn = self.class.get_connection
26
27
  idx = conn.search_index(self.class.send(:index_name))
27
28
  r_a_h = conn.results_as_hash
@@ -36,14 +37,38 @@ module Litesearch::Model
36
37
  end
37
38
  result
38
39
  end
39
-
40
40
  end
41
41
 
42
42
  module ClassMethods
43
+
43
44
  def litesearch
45
+ # it is possible that this code is running when there is no table created yet
46
+ if !defined?(ActiveRecord::Base).nil? && ancestors.include?(ActiveRecord::Base)
47
+ unless table_exists?
48
+ # capture the schema block
49
+ @schema = ::Litesearch::Schema.new
50
+ @schema.model_class = self if @schema.respond_to? :model_class
51
+ @schema.type :backed
52
+ @schema.table table_name.to_sym
53
+ yield @schema
54
+ @schema.post_init
55
+ @schema_not_created = true
56
+ after_initialize do
57
+ if self.class.instance_variable_get(:@schema_not_created)
58
+ self.class.get_connection.search_index(self.class.index_name) do |schema|
59
+ @schema.model_class = self.class if @schema.respond_to? :model_class
60
+ schema.merge(self.class.instance_variable_get(:@schema))
61
+ end
62
+ self.class.instance_variable_set(:@schema_not_created, false)
63
+ end
64
+ end
65
+ return nil
66
+ end
67
+ end
44
68
  idx = get_connection.search_index(index_name) do |schema|
45
69
  schema.type :backed
46
70
  schema.table table_name.to_sym
71
+ schema.model_class = self if schema.respond_to? :model_class
47
72
  yield schema
48
73
  schema.post_init
49
74
  @schema = schema # save the schema
@@ -77,7 +102,7 @@ module Litesearch::Model
77
102
  else
78
103
  models_hash = search_models
79
104
  end
80
- # remove the models from the options hash before passing it ot the query
105
+ # remove the models from the options hash before passing it to the query
81
106
  options.delete(:models)
82
107
  models_hash.each do |name, klass|
83
108
  selects << "SELECT '#{name}' AS model, rowid, -rank AS search_rank FROM #{index_name_for_table(klass.table_name)}(:term)"
@@ -109,6 +134,32 @@ module Litesearch::Model
109
134
  end
110
135
  end
111
136
 
137
+ module ActiveRecordSchemaMethods
138
+
139
+ attr_accessor :model_class
140
+
141
+ def field(name, attributes = {})
142
+ keys = attributes.keys
143
+ if keys.include?(:action_text) || keys.include?(:rich_text)
144
+ attributes[:source] = "#{ActionText::RichText.table_name}.body" rescue "action_text_rich_texts.body"
145
+ attributes[:reference] = :record_id
146
+ attributes[:conditions] = { record_type: model_class.name }
147
+ attributes[:target] = nil
148
+ elsif keys.include? :as
149
+ attributes[:source] = attributes[:target] unless attributes[:source]
150
+ attributes[:reference] = "#{attributes[:as]}_id"
151
+ attributes[:conditions] = {"#{attributes[:as]}_type".to_sym => model_class.name }
152
+ attributes[:target] = nil
153
+ end
154
+ super(name, attributes)
155
+ end
156
+
157
+ def allowed_attributes
158
+ super + [:polymorphic, :as, :action_text]
159
+ end
160
+
161
+ end
162
+
112
163
  module ActiveRecordInstanceMethods; end
113
164
 
114
165
  module ActiveRecordClassMethods
@@ -121,6 +172,13 @@ module Litesearch::Model
121
172
  end
122
173
 
123
174
  def search(term)
175
+ if @schema_not_created
176
+ get_connection.search_index(index_name) do |schema|
177
+ schema.merge(@schema)
178
+ schema.model_class = self if schema.respond_to? :model_class
179
+ end
180
+ @schema_not_created = false
181
+ end
124
182
  self.select(
125
183
  "#{table_name}.*"
126
184
  ).joins(
@@ -1,6 +1,7 @@
1
- require_relative "./schema_adapters"
1
+ require_relative "schema_adapters"
2
2
 
3
3
  class Litesearch::Schema
4
+
4
5
  TOKENIZERS = {
5
6
  porter: "porter unicode61 remove_diacritics 2",
6
7
  unicode: "unicode61 remove_diacritics 2",
@@ -34,6 +35,10 @@ class Litesearch::Schema
34
35
  @schema[:fields] = {} unless @schema[:fields]
35
36
  end
36
37
 
38
+ def merge(other_schema)
39
+ @schema.merge!(other_schema.schema)
40
+ end
41
+
37
42
  # schema definition API
38
43
  def name(new_name)
39
44
  @schema[:name] = new_name
@@ -181,7 +186,7 @@ class Litesearch::Schema
181
186
  end
182
187
 
183
188
  def allowed_attributes
184
- [:weight, :col, :target]
189
+ [:weight, :col, :target, :source, :conditions, :reference]
185
190
  end
186
191
  end
187
192