pose 3.0.0 → 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +42 -19
  3. data/Rakefile +37 -5
  4. data/lib/pose/activerecord_base_additions.rb +7 -6
  5. data/{app/models → lib}/pose/assignment.rb +6 -1
  6. data/lib/pose/jobs/install.rb +27 -0
  7. data/lib/pose/jobs/reindex_all.rb +34 -0
  8. data/lib/pose/jobs/remove.rb +28 -0
  9. data/lib/pose/jobs/uninstall.rb +19 -0
  10. data/lib/pose/jobs/vacuum.rb +23 -0
  11. data/lib/pose/model_class_additions.rb +32 -14
  12. data/lib/pose/query.rb +49 -9
  13. data/lib/pose/search.rb +5 -5
  14. data/lib/pose/static_api.rb +21 -3
  15. data/lib/pose/version.rb +1 -1
  16. data/lib/pose/word.rb +33 -0
  17. data/lib/pose.rb +11 -5
  18. data/lib/tasks/pose_tasks.rake +16 -27
  19. data/spec/{dummy/db/development.sqlite3 → db/pose.sqlite3} +0 -0
  20. data/spec/factories/posable_one.rb +2 -0
  21. data/spec/factories/posable_three.rb +8 -0
  22. data/spec/factories/posable_two.rb +2 -0
  23. data/spec/factories/user.rb +2 -0
  24. data/spec/lib/pose/activerecord_base_additions_spec.rb +11 -0
  25. data/spec/{models → lib/pose}/assignment_spec.rb +5 -5
  26. data/spec/lib/pose/jobs/reindex_all_spec.rb +31 -0
  27. data/spec/lib/pose/jobs/remove_spec.rb +20 -0
  28. data/spec/lib/pose/model_class_additions_spec.rb +150 -0
  29. data/spec/lib/pose/query_spec.rb +106 -8
  30. data/spec/lib/pose/search_spec.rb +2 -22
  31. data/spec/lib/pose/word_spec.rb +68 -0
  32. data/spec/pose_api_spec.rb +33 -13
  33. data/spec/spec_helper.rb +4 -32
  34. data/spec/support/config/database.yml +51 -0
  35. data/spec/support/config/database.yml.example +16 -0
  36. data/spec/{dummy/db/migrate → support/migrations}/20130308054001_create_posable_one.rb +0 -0
  37. data/spec/{dummy/db/migrate → support/migrations}/20130308054142_create_posable_two.rb +0 -0
  38. data/spec/support/migrations/20130308054143_create_posable_three.rb +9 -0
  39. data/spec/{dummy/db/migrate → support/migrations}/20130708084009_create_users.rb +0 -0
  40. data/spec/support/migrations/20130808084009_setup_pose_specs.rb +5 -0
  41. data/spec/{dummy/app → support}/models/posable_one.rb +1 -1
  42. data/spec/support/models/posable_three.rb +10 -0
  43. data/spec/{dummy/app → support}/models/posable_two.rb +1 -1
  44. data/spec/{dummy/app → support}/models/user.rb +1 -1
  45. data/spec/support/spec_manager.rb +105 -0
  46. metadata +70 -101
  47. data/app/models/pose/word.rb +0 -22
  48. data/db/migrate/20130308144915_pose_install.rb +0 -18
  49. data/lib/generators/pose/install/install_generator.rb +0 -56
  50. data/lib/generators/pose/install/templates/install_migration.rb +0 -24
  51. data/lib/generators/pose/remove/remove_generator.rb +0 -56
  52. data/lib/generators/pose/remove/templates/remove_migration.rb +0 -23
  53. data/lib/generators/pose/upgrade/templates/upgrade_migration.rb +0 -7
  54. data/lib/generators/pose/upgrade/upgrade_generator.rb +0 -35
  55. data/lib/pose/engine.rb +0 -5
  56. data/lib/pose/helpers.rb +0 -89
  57. data/lib/pose/railtie.rb +0 -19
  58. data/spec/dummy/Rakefile +0 -7
  59. data/spec/dummy/config/application.rb +0 -23
  60. data/spec/dummy/config/boot.rb +0 -10
  61. data/spec/dummy/config/database.yml +0 -25
  62. data/spec/dummy/config/environment.rb +0 -5
  63. data/spec/dummy/config/environments/development.rb +0 -30
  64. data/spec/dummy/config/environments/production.rb +0 -81
  65. data/spec/dummy/config/environments/test.rb +0 -37
  66. data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -7
  67. data/spec/dummy/config/initializers/filter_parameter_logging.rb +0 -4
  68. data/spec/dummy/config/initializers/inflections.rb +0 -15
  69. data/spec/dummy/config/initializers/mime_types.rb +0 -5
  70. data/spec/dummy/config/initializers/secret_token.rb +0 -7
  71. data/spec/dummy/config/initializers/session_store.rb +0 -8
  72. data/spec/dummy/config/initializers/wrap_parameters.rb +0 -14
  73. data/spec/dummy/config/locales/en.yml +0 -5
  74. data/spec/dummy/config/routes.rb +0 -2
  75. data/spec/dummy/config.ru +0 -4
  76. data/spec/dummy/db/schema.rb +0 -47
  77. data/spec/dummy/db/test.sqlite3 +0 -0
  78. data/spec/dummy/log/development.log +0 -309
  79. data/spec/dummy/log/test.log +0 -88518
  80. data/spec/dummy/script/rails +0 -6
  81. data/spec/lib/pose/helpers_spec.rb +0 -147
  82. data/spec/models/word_spec.rb +0 -42
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b5795ce133c87ea0c6f0841d857b1e106bd527d1
4
- data.tar.gz: e7404bd3671c5c12df5a190df2ce48699d018efc
3
+ metadata.gz: 1059e7c72d577be6f7d5f3c0fc4fdc30b429bd4b
4
+ data.tar.gz: d0ed2c994af2326ec73a779b0fb61a44dca2602b
5
5
  SHA512:
6
- metadata.gz: ba4cdcc4bb8680bff841c706b96e1d252fec59e5394c69260dd831d9366c8a07f2d9c3d16f69c2ceb4bdc6e43ef8a4ec757fe214a3a3abfc80ada389cdea721a
7
- data.tar.gz: 9415d0ece9e8984aeba669647b83e7fc7be78b3ff5bf827888a0f9851e48defc66b4a13b6703f29f19b3cbe4da7e6bb1259a2baee7a9f13ebe610e96e437733d
6
+ metadata.gz: a9eb63377caee2f07d3e7c9118ee9715d9e386371ca766aeb35b98eabe8d76f2d2128a27188ce71dd5f05c61e8bc46dc9726a351278392fc478dcb38aeeb44f6
7
+ data.tar.gz: 28e599e762c76d35070e5d81c71c9b7069270dbd3f1da463db8eb1f6f546830062da027e6eb47a794d85efa475680f66052e7c8373462bbcbdb9b9b0bbd2d37f
data/README.md CHANGED
@@ -1,19 +1,28 @@
1
1
  # POlymorphic SEarch <a href="http://travis-ci.org/#!/kevgo/pose" target="_blank"><img src="https://secure.travis-ci.org/kevgo/pose.png" alt="Build status"></a> [![Code Climate](https://codeclimate.com/github/kevgo/pose.png)](https://codeclimate.com/github/kevgo/pose) [![Coverage Status](https://coveralls.io/repos/kevgo/pose/badge.png?branch=master)](https://coveralls.io/r/kevgo/pose) [![Dependency Status](https://gemnasium.com/kevgo/pose.png)](https://gemnasium.com/kevgo/pose)
2
2
 
3
- A database agnostic fulltext search engine for ActiveRecord objects in Ruby on Rails.
3
+ A database agnostic fulltext search engine for ActiveRecord objects. See also [Pose Rails Adapter](http://github.com/einzige/pose-rails).
4
4
 
5
5
  * Searches over several classes at once.
6
6
  * The searchable content of each class and document can be freely customized.
7
- * Uses the main Rails database - no separate servers, databases, or search engines required.
7
+ * Uses application database - no separate servers, databases, or search engines required.
8
8
  * Does not pollute the searchable classes nor their database tables.
9
9
  * Very fast search, doing only simple queries over fully indexed columns.
10
10
  * Allows to augment the fulltext search query with your own joins and where clauses.
11
11
 
12
- Version 3.x is for Rails 4 compatibilty. For Rails 3.x, please use versions 2.x.
13
-
14
12
 
15
13
  ## Installation
16
14
 
15
+ ### Versions
16
+
17
+ * __2.x__ for _Rails 3.x_ compatibilty
18
+ * __3.0__ for _Rails 4_ compatibilty
19
+ * __3.1__ introduces a new setup. The _pose_ gem is now a generic Ruby gem that works
20
+ with any Ruby web server that uses ActiveRecord, like [Sinatra](http://www.sinatrarb.com),
21
+ [Padrino](http://www.padrinorb.com), or [Rails](http://rubyonrails.org).
22
+ Generators for installation and uninstallation are extracted into the
23
+ [pose_rails](https://github.com/po-se/pose-rails) gem.
24
+
25
+
17
26
  ### Set up the gem.
18
27
 
19
28
  Add the gem to your Gemfile and run `bundle install`
@@ -25,8 +34,7 @@ gem 'pose'
25
34
  ### Create the database tables for pose.
26
35
 
27
36
  ```bash
28
- $ rails generate pose:install
29
- $ rake db:migrate
37
+ $ rake pose:install
30
38
  ```
31
39
 
32
40
  Pose creates two tables in your database. These tables are automatically populated and kept up to date.
@@ -37,11 +45,32 @@ Pose creates two tables in your database. These tables are automatically populat
37
45
 
38
46
  ### Make your ActiveRecord models searchable
39
47
 
48
+ Each model defines the searchable content through the `posify` method.
49
+ Valid parameters are
50
+ * names of __fields__
51
+ * names of __methods__
52
+ * a __block__
53
+
54
+ The value/result of each parameter is added to the search index for this instance.
55
+ Here are a few examples:
56
+
57
+ ```ruby
58
+ class User < ActiveRecord::Base
59
+ # first_name and last_name are attributes
60
+
61
+ # This line makes the class searchable.
62
+ posify :first_name, :last_name, :address
63
+
64
+ def address
65
+ "#{street} #{city} #{state}"
66
+ end
67
+ end
68
+ ```
69
+
40
70
  ```ruby
41
71
  class MyClass < ActiveRecord::Base
42
72
 
43
- # This line makes your class searchable.
44
- # The given block must return the searchable content as a string.
73
+ # This line makes the class searchable.
45
74
  posify do
46
75
 
47
76
  # Only active instances should show up in search results.
@@ -55,6 +84,7 @@ class MyClass < ActiveRecord::Base
55
84
  end
56
85
  ```
57
86
 
87
+ You can mix and match all parameter types for `posify` at will.
58
88
  Note that you can return whatever content you want in the `posify` block,
59
89
  not only data from this object, but also data from related objects, class names, etc.
60
90
 
@@ -241,23 +271,16 @@ Or, clone the repository, make your changes, and submit a unit-tested pull reque
241
271
 
242
272
  ### Run the unit tests for the Pose Gem
243
273
 
244
- Pose uses Sqlite3 for tests.
245
- To run tests, first create a test database.
246
-
247
- ```bash
248
- bundle
249
- rake app:db:create
250
- rake app:db:migrate
251
- rake app:db:test:prepare
252
- ```
274
+ Pose can work with Sqlite3, Postgesql and MySQL by default.
275
+ To run tests, please create database configuration file `spec/support/config/database.yml`, please refer to the template:
276
+ [spec/support/config/database.yml.example](spec/support/config/database.yml.example)
253
277
 
254
278
  Then run the tests.
255
279
 
256
280
  ```bash
257
- rake
281
+ bundle exec rake test
258
282
  ```
259
283
 
260
-
261
284
  ### Road Map
262
285
 
263
286
  * pagination of search results
data/Rakefile CHANGED
@@ -1,16 +1,48 @@
1
1
  #!/usr/bin/env rake
2
+
2
3
  begin
3
4
  require 'bundler/setup'
4
5
  rescue LoadError
5
6
  puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
7
  end
7
8
 
8
- APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
9
- load 'rails/tasks/engine.rake'
10
-
11
9
  Bundler::GemHelper.install_tasks
12
10
 
13
11
  # RSpec tasks.
14
12
  require 'rspec/core/rake_task'
15
- RSpec::Core::RakeTask.new :spec
16
- task :default => :spec
13
+
14
+ RSpec::Core::RakeTask.new :spec_mysql do
15
+ ENV['POSE_ENV'] = 'mysql'
16
+ end
17
+
18
+ RSpec::Core::RakeTask.new :spec_mysql2 do
19
+ ENV['POSE_ENV'] = 'mysql2'
20
+ end
21
+
22
+ RSpec::Core::RakeTask.new :spec_sqlite do
23
+ ENV['POSE_ENV'] = 'sqlite'
24
+ end
25
+
26
+ RSpec::Core::RakeTask.new :spec_postgres do
27
+ ENV['POSE_ENV'] = 'postgres'
28
+ end
29
+
30
+ RSpec::Core::RakeTask.new :spec_mysql_ci do
31
+ ENV['POSE_ENV'] = 'mysql_ci'
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new :spec_mysql2_ci do
35
+ ENV['POSE_ENV'] = 'mysql2_ci'
36
+ end
37
+
38
+ RSpec::Core::RakeTask.new :spec_sqlite_ci do
39
+ ENV['POSE_ENV'] = 'sqlite_ci'
40
+ end
41
+
42
+ RSpec::Core::RakeTask.new :spec_postgres_ci do
43
+ ENV['POSE_ENV'] = 'postgres_ci'
44
+ end
45
+
46
+ task :test_ci => [:spec_sqlite_ci, :spec_postgres_ci, :spec_mysql2_ci]
47
+ task :test => [:spec_sqlite, :spec_postgres, :spec_mysql2]
48
+ task :default => :test_ci
@@ -3,13 +3,14 @@ module Pose
3
3
  module ActiveRecordBaseAdditions
4
4
 
5
5
  # Defines the searchable content in ActiveRecord objects.
6
- def posify &block
7
- raise "Error while posifying class '#{name}': " \
8
- "You must provide a block that returns the searchable content to 'posify'." unless block_given?
9
-
6
+ def posify *source_methods, &block
10
7
  include ModelClassAdditions
11
- self.pose_content = block
12
- end
13
8
 
9
+ self.pose_content = proc do
10
+ text_chunks = source_methods.map { |source| send(source) }
11
+ text_chunks << instance_eval(&block) if block
12
+ text_chunks.reject(&:blank?).join(' ')
13
+ end
14
+ end
14
15
  end
15
16
  end
@@ -1,17 +1,22 @@
1
1
  # Assigns searchable objects to words in the search index.
2
2
  module Pose
3
3
  class Assignment < ActiveRecord::Base
4
+ self.table_name_prefix = 'pose_'
5
+
4
6
  belongs_to :word, class_name: 'Pose::Word'
5
7
  belongs_to :posable, polymorphic: true
6
8
 
7
9
  # Removes all Assignments for the given class.
10
+ # Returns a number for removed records.
11
+ # @param [Class] clazz
12
+ # @return [Integer]
8
13
  def self.delete_class_index clazz
9
14
  Assignment.delete_all(posable_type: clazz.name)
10
15
  end
11
16
 
12
17
  # Removes all Assignments that aren't used anymore.
13
18
  def self.cleanup_orphaned_pose_assignments progress_bar = nil
14
- Assignment.find_each(include: [:posable, :word], batch_size: 5000) do |assignment|
19
+ Assignment.includes([:posable, :word]).find_each(batch_size: 5000) do |assignment|
15
20
  progress_bar.increment if progress_bar
16
21
 
17
22
  # Delete the assignment if the posable object no longer exists.
@@ -0,0 +1,27 @@
1
+ module Pose
2
+ module Jobs
3
+ class InitialMigration < ActiveRecord::Migration
4
+ def change
5
+ create_table "pose_assignments" do |t|
6
+ t.integer "word_id", null: false
7
+ t.integer "posable_id", null: false
8
+ t.string "posable_type", limit: 60, null: false
9
+ end
10
+ add_index "pose_assignments", :word_id
11
+ add_index "pose_assignments", :posable_id
12
+
13
+ create_table "pose_words" do |t|
14
+ t.string "text", limit: 80, null: false
15
+ end
16
+ add_index "pose_words", :text
17
+ end
18
+ end
19
+
20
+ # Creates the Pose tables in the database.
21
+ class Install
22
+ def perform
23
+ InitialMigration.new.change
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,34 @@
1
+ require 'ruby-progressbar'
2
+
3
+ module Pose
4
+ module Jobs
5
+
6
+ # Recreates the Pose search index for the given class from scratch.
7
+ class ReindexAll
8
+ attr_reader :klass
9
+
10
+ # @param [String, Class] clazz
11
+ def initialize(clazz)
12
+ @klass = case clazz
13
+ when String
14
+ clazz.constantize
15
+ when Class
16
+ clazz
17
+ else
18
+ raise ArgumentError, "Class or String expected, #{clazz.class} given"
19
+ end
20
+ end
21
+
22
+ def perform
23
+ progress_bar = ProgressBar.create title: " reindexing", total: klass.count
24
+
25
+ klass.find_each do |instance|
26
+ instance.update_pose_words
27
+ progress_bar.increment
28
+ end
29
+
30
+ progress_bar.finish
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,28 @@
1
+ require 'ruby-progressbar'
2
+
3
+ module Pose
4
+ module Jobs
5
+
6
+ # Removes the Pose search index for the given class.
7
+ class Remove
8
+ attr_reader :klass
9
+
10
+ # @param [String, Class] clazz
11
+ def initialize(clazz)
12
+ @klass = case clazz
13
+ when String
14
+ clazz.constantize
15
+ when Class
16
+ clazz
17
+ else
18
+ raise ArgumentError, "Class or String expected, #{clazz.class} given"
19
+ end
20
+ end
21
+
22
+ def perform
23
+ Pose::Assignment.delete_class_index(klass)
24
+ puts "Search index for class #{klass.name} deleted.\n\n"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ require 'pose/jobs/install'
2
+
3
+ module Pose
4
+ module Jobs
5
+
6
+ # Removes the Pose database tables.
7
+ class KillMigration < ActiveRecord::Migration
8
+ def change
9
+ revert InitialMigration
10
+ end
11
+ end
12
+
13
+ class Uninstall
14
+ def perform
15
+ KillMigration.new.change
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ require 'ruby-progressbar'
2
+
3
+ module Pose
4
+ module Jobs
5
+
6
+ # Cleans up unused data in the search index.
7
+ class Vacuum
8
+ def perform
9
+ puts "Cleaning Pose search index...\n\n"
10
+
11
+ progress_bar = ProgressBar.create title: ' assignments', total: Pose::Assignment.count
12
+ Pose::Assignment.cleanup_orphaned_pose_assignments progress_bar
13
+ progress_bar.finish
14
+
15
+ progress_bar = ProgressBar.create title: ' words', total: Pose::Word.count
16
+ Pose::Word.remove_unused_words progress_bar
17
+ progress_bar.finish
18
+
19
+ puts "\nPose search index cleanup complete.\n\n"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -13,6 +13,36 @@ module Pose
13
13
  cattr_accessor :pose_content
14
14
  end
15
15
 
16
+ # Returns all words in the search index for this instance.
17
+ # @return [Array<String>]
18
+ def pose_current_words
19
+ pose_words.map(&:text)
20
+ end
21
+
22
+ # Returns the searchable text snippet for this instance.
23
+ # This data is not stored in the search engine.
24
+ # It is recomputes this from data in the database here.
25
+ # @return [String]
26
+ def pose_fetch_content
27
+ instance_eval(&(pose_content)).to_s
28
+ end
29
+
30
+ # @return [Array<String>]
31
+ def pose_fresh_words reload = false
32
+ @pose_fresh_words = nil if reload
33
+ @pose_fresh_words ||= Query.query_words pose_fetch_content
34
+ end
35
+
36
+ # @return [Array<String>]
37
+ def pose_stale_words reload = false
38
+ pose_current_words - pose_fresh_words(reload)
39
+ end
40
+
41
+ # @return [Array<String>]
42
+ def pose_words_to_add reload = false
43
+ pose_fresh_words(reload) - pose_current_words
44
+ end
45
+
16
46
  # Updates the associated words for this object in the database.
17
47
  def update_pose_index
18
48
  update_pose_words if Pose.perform_search?
@@ -26,20 +56,8 @@ module Pose
26
56
  # Helper method.
27
57
  # Updates the search words with the text returned by search_strings.
28
58
  def update_pose_words
29
-
30
- # Step 1: get an array of all words for the current object.
31
- search_text = instance_eval &(self.class.pose_content)
32
- new_words = Query.new([], search_text.to_s).query_words
33
-
34
- # Step 2: Add new words to the search index.
35
- Helpers.get_words_to_add(self.pose_words, new_words).each do |word_to_add|
36
- self.pose_words << Word.find_or_create_by(text: word_to_add)
37
- end
38
-
39
- # Step 3: Remove now obsolete words from search index.
40
- Helpers.get_words_to_remove(self.pose_words, new_words).each do |word_to_remove|
41
- self.pose_words.delete word_to_remove
42
- end
59
+ self.pose_words.delete(Word.factory(pose_stale_words true))
60
+ self.pose_words << Word.factory(pose_words_to_add)
43
61
  end
44
62
  end
45
63
  end
data/lib/pose/query.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  module Pose
2
+
2
3
  # Represents a search query.
3
4
  #
4
5
  # Provides convenient access to all elements of the search query:
@@ -12,7 +13,7 @@ module Pose
12
13
 
13
14
 
14
15
  def initialize classes, text, options = {}
15
- @classes = [classes].flatten
16
+ @classes = Array(classes).flatten
16
17
  @text = text
17
18
  @options = options
18
19
  end
@@ -27,19 +28,19 @@ module Pose
27
28
 
28
29
  # Returns whether this query contains custom JOIN expressions.
29
30
  def has_joins?
30
- !@options[:joins].blank?
31
+ @options[:joins].present?
31
32
  end
32
33
 
33
34
 
34
35
  # Returns whether the query defines a limit on the number of results.
35
36
  def has_limit?
36
- !@options[:limit].blank?
37
+ @options[:limit].present?
37
38
  end
38
39
 
39
40
 
40
41
  # Returns whether this query contains WHERE clauses.
41
42
  def has_where?
42
- !@options[:where].blank?
43
+ @options[:where].present?
43
44
  end
44
45
 
45
46
 
@@ -50,9 +51,23 @@ module Pose
50
51
  end
51
52
 
52
53
 
54
+ # Returns whether the given string is a URL.
55
+ #
56
+ # @param [String] word The string to check.
57
+ #
58
+ # @return [Boolean]
59
+ def self.is_url? word
60
+
61
+ # Handle localhost separately.
62
+ return true if /^http:\/\/localhost(:\d+)?/ =~ word
63
+
64
+ /^https?:\/\/([\w\.])+\.([\w\.])+/ =~ word
65
+ end
66
+
67
+
53
68
  # Returns the custom JOIN expressions of this query.
54
69
  def joins
55
- @joins ||= [@options[:joins]].flatten.compact
70
+ @joins ||= Array(@options[:joins]).flatten.compact
56
71
  end
57
72
 
58
73
 
@@ -69,16 +84,41 @@ module Pose
69
84
 
70
85
 
71
86
  def self.query_words query_string
72
- query_string.split(' ').map{|query_word| Helpers.root_word query_word}.flatten.uniq
87
+ query_string.split(' ').map{|query_word| Query.root_word query_word}.flatten.uniq
88
+ end
89
+
90
+
91
+ # Simplifies the given word to a generic search form.
92
+ #
93
+ # @param [String] raw_word The word to make searchable.
94
+ #
95
+ # @return [String] The stemmed version of the word.
96
+ def self.root_word raw_word
97
+ result = []
98
+ raw_word_copy = raw_word[0..-1]
99
+ raw_word_copy.gsub! '%20', ' '
100
+ raw_word_copy.gsub! /[()*<>'",;\?\-\=&%#]/, ' '
101
+ raw_word_copy.gsub! /\s+/, ' '
102
+ raw_word_copy.split(' ').each do |word|
103
+ if Query.is_url?(word)
104
+ result.concat word.split(/[\.\/\:]/).delete_if(&:blank?)
105
+ else
106
+ word.gsub! /[\-\/\._:]/, ' '
107
+ word.gsub! /\s+/, ' '
108
+ word.split(' ').each do |w|
109
+ stemmed_word = w.parameterize.singularize
110
+ result.concat stemmed_word.split ' '
111
+ end
112
+ end
113
+ end
114
+ result.uniq
73
115
  end
74
116
 
75
117
 
76
118
  # Returns the WHERE clause of this query.
77
119
  def where
78
120
  return [] unless has_where?
79
- if @options[:where].size == 2 and @options[:where][0].class == String
80
- return [ @options[:where] ]
81
- end
121
+ return [ @options[:where] ] if @options[:where].size == 2 && @options[:where][0].is_a?(String)
82
122
  @options[:where]
83
123
  end
84
124
  end
data/lib/pose/search.rb CHANGED
@@ -108,13 +108,13 @@ module Pose
108
108
  # Finds all matching ids for a single word of the search query.
109
109
  def search_word word
110
110
  empty_result.tap do |result|
111
- data = Pose::Assignment.joins(:word) \
112
- .select('pose_assignments.posable_id, pose_assignments.posable_type') \
113
- .where('pose_words.text LIKE ?', "#{word}%") \
114
- .where('pose_assignments.posable_type IN (?)', @query.class_names)
111
+ data = Assignment.joins(:word) \
112
+ .select('pose_assignments.posable_id, pose_assignments.posable_type') \
113
+ .where('pose_words.text LIKE ?', "#{word}%") \
114
+ .where('pose_assignments.posable_type IN (?)', @query.class_names)
115
115
  data = add_joins data
116
116
  data = add_wheres data
117
- Pose::Assignment.connection.select_all(data.to_sql).each do |pose_assignment|
117
+ Assignment.connection.select_all(data.to_sql).each do |pose_assignment|
118
118
  result[pose_assignment['posable_type']] << pose_assignment['posable_id'].to_i
119
119
  end
120
120
  end
@@ -17,7 +17,7 @@ module Pose
17
17
  # @return [Array<String>]
18
18
  def autocomplete_words query
19
19
  return [] if query.blank?
20
- Word.where('text LIKE ?', "#{Helpers.root_word(query)[0]}%").map(&:text)
20
+ Word.where('text LIKE ?', "#{Query.root_word(query)[0]}%").map(&:text)
21
21
  end
22
22
 
23
23
 
@@ -38,8 +38,26 @@ module Pose
38
38
  #
39
39
  # @return [Hash<Class, ActiveRecord::Relation>]
40
40
  def search query_string, classes, options = {}
41
- search = Pose::Search.new classes, query_string, options
42
- search.results
41
+ Search.new(classes, query_string, options).results
42
+ end
43
+
44
+
45
+ # Returns whether the currently used database is a relational one.
46
+ def has_sql_connection?
47
+ has_mysql_connection? || has_postgres_connection? || has_sqlite_connection?
48
+ end
49
+
50
+ def has_mysql_connection?
51
+ ['ActiveRecord::ConnectionAdapters::MysqlAdapter',
52
+ 'ActiveRecord::ConnectionAdapters::Mysql2Adapter'].include? ActiveRecord::Base.connection.class.name
53
+ end
54
+
55
+ def has_postgres_connection?
56
+ ActiveRecord::Base.connection.class.name == 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter'
57
+ end
58
+
59
+ def has_sqlite_connection?
60
+ ActiveRecord::Base.connection.class.name == 'ActiveRecord::ConnectionAdapters::SQLite3Adapter'
43
61
  end
44
62
  end
45
63
  end
data/lib/pose/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pose
2
- VERSION = "3.0.0"
2
+ VERSION = "3.1.1"
3
3
  end
data/lib/pose/word.rb ADDED
@@ -0,0 +1,33 @@
1
+ # A single word in the search index.
2
+ module Pose
3
+ class Word < ActiveRecord::Base
4
+ self.table_name_prefix = 'pose_'
5
+
6
+ has_many :assignments, class_name: 'Pose::Assignment', dependent: :destroy
7
+
8
+
9
+ # Returns the Pose::Word instances with the given texts.
10
+ # @param [Array<String>]
11
+ def self.factory texts
12
+ texts.map {|text| Word.find_or_create_by text: text}
13
+ end
14
+
15
+
16
+ def self.remove_unused_words progress_bar = nil
17
+ if Pose.has_sql_connection?
18
+ # SQL database --> use an optimized query.
19
+ Word.select("pose_words.id").
20
+ joins("LEFT OUTER JOIN pose_assignments ON pose_assignments.word_id = pose_words.id").
21
+ group("pose_words.id").
22
+ having("COUNT(pose_assignments.id) = 0").
23
+ delete_all
24
+ else
25
+ # Unknown database --> use metawhere.
26
+ Word.select(:id).includes(:assignments).find_each(batch_size: 100) do |word|
27
+ word.delete if word.assignments.size.zero?
28
+ progress_bar.try(:increment)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
data/lib/pose.rb CHANGED
@@ -1,11 +1,17 @@
1
- require "pose/engine"
1
+ require 'active_record'
2
2
  require 'pose/query'
3
3
  require 'pose/search'
4
4
  require 'pose/static_api'
5
- require 'pose/helpers'
6
5
  require 'pose/activerecord_base_additions'
7
6
  require 'pose/model_class_additions'
8
- require 'pose/railtie' if defined? Rails
7
+ require 'pose/assignment'
8
+ require 'pose/word'
9
+ require 'pose/jobs/reindex_all'
10
+ require 'pose/jobs/remove'
11
+ require 'pose/jobs/vacuum'
12
+ require 'pose/jobs/install'
13
+ require 'pose/jobs/uninstall'
9
14
 
10
- module Pose
11
- end
15
+ Dir[File.join(File.dirname(__FILE__), "tasks/**/*.rake")].each { |ext| load ext } if defined? Rake
16
+
17
+ ActiveRecord::Base.send :extend, Pose::ActiveRecordBaseAdditions