pose 3.0.0 → 3.1.1

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 (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