pose 0.0.2

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.
@@ -0,0 +1,150 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html>
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
6
+
7
+ <link rel="stylesheet" href="css/full_list.css" type="text/css" media="screen" charset="utf-8" />
8
+
9
+ <link rel="stylesheet" href="css/common.css" type="text/css" media="screen" charset="utf-8" />
10
+
11
+
12
+
13
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
14
+
15
+ <script type="text/javascript" charset="utf-8" src="js/full_list.js"></script>
16
+
17
+
18
+ <base id="base_target" target="_parent" />
19
+ </head>
20
+ <body>
21
+ <script type="text/javascript" charset="utf-8">
22
+ if (window.top.frames.main) {
23
+ document.getElementById('base_target').target = 'main';
24
+ document.body.className = 'frames';
25
+ }
26
+ </script>
27
+ <div id="content">
28
+ <h1 id="full_list_header">Method List</h1>
29
+ <div id="nav">
30
+
31
+ <a target="_self" href="class_list.html">Classes</a>
32
+
33
+ <a target="_self" href="method_list.html">Methods</a>
34
+
35
+ <a target="_self" href="file_list.html">Files</a>
36
+
37
+ </div>
38
+ <div id="search">Search: <input type="text" /></div>
39
+
40
+ <ul id="full_list" class="methods">
41
+
42
+
43
+ <li class="r1 ">
44
+ <span class='object_link'><a href="PoseGenerator.html#create_migration_file-instance_method" title="PoseGenerator#create_migration_file (method)">#create_migration_file</a></span>
45
+
46
+ <small>PoseGenerator</small>
47
+
48
+ </li>
49
+
50
+
51
+ <li class="r2 ">
52
+ <span class='object_link'><a href="Pose/InstanceMethods.html#delete_pose_index-instance_method" title="Pose::InstanceMethods#delete_pose_index (method)">#delete_pose_index</a></span>
53
+
54
+ <small>Pose::InstanceMethods</small>
55
+
56
+ </li>
57
+
58
+
59
+ <li class="r1 ">
60
+ <span class='object_link'><a href="PoseMigrations.html#down-class_method" title="PoseMigrations.down (method)">down</a></span>
61
+
62
+ <small>PoseMigrations</small>
63
+
64
+ </li>
65
+
66
+
67
+ <li class="r2 ">
68
+ <span class='object_link'><a href="Pose.html#get_words_to_add-class_method" title="Pose.get_words_to_add (method)">get_words_to_add</a></span>
69
+
70
+ <small>Pose</small>
71
+
72
+ </li>
73
+
74
+
75
+ <li class="r1 ">
76
+ <span class='object_link'><a href="Pose.html#get_words_to_remove-class_method" title="Pose.get_words_to_remove (method)">get_words_to_remove</a></span>
77
+
78
+ <small>Pose</small>
79
+
80
+ </li>
81
+
82
+
83
+ <li class="r2 ">
84
+ <span class='object_link'><a href="Pose.html#is_url-class_method" title="Pose.is_url (method)">is_url</a></span>
85
+
86
+ <small>Pose</small>
87
+
88
+ </li>
89
+
90
+
91
+ <li class="r1 ">
92
+ <span class='object_link'><a href="PoseGenerator.html#next_migration_number-class_method" title="PoseGenerator.next_migration_number (method)">next_migration_number</a></span>
93
+
94
+ <small>PoseGenerator</small>
95
+
96
+ </li>
97
+
98
+
99
+ <li class="r2 ">
100
+ <span class='object_link'><a href="#posify-instance_method" title="#posify (method)">#posify</a></span>
101
+
102
+ <small>Top Level Namespace</small>
103
+
104
+ </li>
105
+
106
+
107
+ <li class="r1 ">
108
+ <span class='object_link'><a href="Pose.html#root_word-class_method" title="Pose.root_word (method)">root_word</a></span>
109
+
110
+ <small>Pose</small>
111
+
112
+ </li>
113
+
114
+
115
+ <li class="r2 ">
116
+ <span class='object_link'><a href="Pose.html#search-class_method" title="Pose.search (method)">search</a></span>
117
+
118
+ <small>Pose</small>
119
+
120
+ </li>
121
+
122
+
123
+ <li class="r1 ">
124
+ <span class='object_link'><a href="PoseMigrations.html#up-class_method" title="PoseMigrations.up (method)">up</a></span>
125
+
126
+ <small>PoseMigrations</small>
127
+
128
+ </li>
129
+
130
+
131
+ <li class="r2 ">
132
+ <span class='object_link'><a href="Pose/InstanceMethods.html#update_pose_index-instance_method" title="Pose::InstanceMethods#update_pose_index (method)">#update_pose_index</a></span>
133
+
134
+ <small>Pose::InstanceMethods</small>
135
+
136
+ </li>
137
+
138
+
139
+ <li class="r1 ">
140
+ <span class='object_link'><a href="Pose/InstanceMethods.html#update_pose_words-instance_method" title="Pose::InstanceMethods#update_pose_words (method)">#update_pose_words</a></span>
141
+
142
+ <small>Pose::InstanceMethods</small>
143
+
144
+ </li>
145
+
146
+
147
+ </ul>
148
+ </div>
149
+ </body>
150
+ </html>
@@ -0,0 +1,172 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
6
+ <title>
7
+ Top Level Namespace
8
+
9
+ &mdash; Documentation by YARD 0.7.3
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" media="screen" charset="utf-8" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" media="screen" charset="utf-8" />
16
+
17
+ <script type="text/javascript" charset="utf-8">
18
+ relpath = '';
19
+ if (relpath != '') relpath += '/';
20
+ </script>
21
+
22
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
23
+
24
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
25
+
26
+
27
+ </head>
28
+ <body>
29
+ <script type="text/javascript" charset="utf-8">
30
+ if (window.top.frames.main) document.body.className = 'frames';
31
+ </script>
32
+
33
+ <div id="header">
34
+ <div id="menu">
35
+
36
+ <a href="_index.html">Index</a> &raquo;
37
+
38
+
39
+ <span class="title">Top Level Namespace</span>
40
+
41
+
42
+ <div class="noframes"><span class="title">(</span><a href="." target="_top">no frames</a><span class="title">)</span></div>
43
+ </div>
44
+
45
+ <div id="search">
46
+
47
+ <a id="class_list_link" href="#">Class List</a>
48
+
49
+ <a id="method_list_link" href="#">Method List</a>
50
+
51
+ <a id="file_list_link" href="#">File List</a>
52
+
53
+ </div>
54
+ <div class="clear"></div>
55
+ </div>
56
+
57
+ <iframe id="search_frame"></iframe>
58
+
59
+ <div id="content"><h1>Top Level Namespace
60
+
61
+
62
+
63
+ </h1>
64
+
65
+ <dl class="box">
66
+
67
+
68
+
69
+
70
+
71
+
72
+
73
+
74
+ </dl>
75
+ <div class="clear"></div>
76
+
77
+ <h2>Defined Under Namespace</h2>
78
+ <p class="children">
79
+
80
+
81
+ <strong class="modules">Modules:</strong> <span class='object_link'><a href="Pose.html" title="Pose (module)">Pose</a></span>
82
+
83
+
84
+
85
+ <strong class="classes">Classes:</strong> <span class='object_link'><a href="PoseAssignment.html" title="PoseAssignment (class)">PoseAssignment</a></span>, <span class='object_link'><a href="PoseGenerator.html" title="PoseGenerator (class)">PoseGenerator</a></span>, <span class='object_link'><a href="PoseMigrations.html" title="PoseMigrations (class)">PoseMigrations</a></span>, <span class='object_link'><a href="PoseWord.html" title="PoseWord (class)">PoseWord</a></span>
86
+
87
+
88
+ </p>
89
+
90
+
91
+
92
+
93
+
94
+
95
+ <h2>
96
+ Instance Method Summary
97
+ <small>(<a href="#" class="summary_toggle">collapse</a>)</small>
98
+ </h2>
99
+
100
+ <ul class="summary">
101
+
102
+ <li class="public ">
103
+ <span class="summary_signature">
104
+
105
+ <a href="#posify-instance_method" title="#posify (instance method)">- (Object) <strong>posify</strong> </a>
106
+
107
+
108
+
109
+ </span>
110
+
111
+
112
+
113
+
114
+
115
+
116
+
117
+
118
+ <span class="summary_desc"><div class='inline'></div></span>
119
+
120
+ </li>
121
+
122
+
123
+ </ul>
124
+
125
+
126
+
127
+
128
+ <div id="instance_method_details" class="method_details_list">
129
+ <h2>Instance Method Details</h2>
130
+
131
+
132
+ <div class="method_details first">
133
+ <p class="signature first" id="posify-instance_method">
134
+
135
+ - (<tt>Object</tt>) <strong>posify</strong>
136
+
137
+
138
+
139
+ </p><table class="source_code">
140
+ <tr>
141
+ <td>
142
+ <pre class="lines">
143
+
144
+
145
+ 159
146
+ 160
147
+ 161</pre>
148
+ </td>
149
+ <td>
150
+ <pre class="code"><span class="info file"># File 'lib/pose.rb', line 159</span>
151
+
152
+ <span class='def def kw'>def</span> <span class='posify identifier id'>posify</span>
153
+ <span class='include identifier id'>include</span> <span class='Pose constant id'>Pose</span>
154
+ <span class='end end kw'>end</span>
155
+ </pre>
156
+ </td>
157
+ </tr>
158
+ </table>
159
+ </div>
160
+
161
+ </div>
162
+
163
+ </div>
164
+
165
+ <div id="footer">
166
+ Generated on Wed Nov 16 11:48:27 2011 by
167
+ <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
168
+ 0.7.3 (ruby-1.8.7).
169
+ </div>
170
+
171
+ </body>
172
+ </html>
@@ -0,0 +1,15 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ class PoseGenerator < Rails::Generators::Base
5
+ include Rails::Generators::Migration
6
+ source_root File.expand_path('../templates', __FILE__)
7
+
8
+ def create_migration_file
9
+ migration_template 'migration.rb', 'db/migrate/pose.rb'
10
+ end
11
+
12
+ def self.next_migration_number(path)
13
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ class PoseMigrations < ActiveRecord::Migration
2
+ def self.up
3
+ create_table "pose_assignments" do |t|
4
+ t.integer "pose_word_id", :null => false
5
+ t.integer "posable_id", :null => false
6
+ t.string "posable_type", :limit => 40, :null => false
7
+ end
8
+
9
+ add_index "pose_assignments", :word_id
10
+ add_index "pose_assignments", :wordable_id
11
+
12
+ create_table "pose_words" do |t|
13
+ t.string "text", :limit => 80, :null => false
14
+ end
15
+
16
+ add_index "words", :text
17
+ end
18
+
19
+ def self.down
20
+ drop_table 'pose_assignments'
21
+ drop_table 'pose_words'
22
+ end
23
+ end
@@ -0,0 +1,13 @@
1
+ module Pose
2
+ module Posifier
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def posify &block
7
+ include Pose
8
+ self.pose_content = block_given? ? block : :pose_content.to_proc
9
+ end
10
+ end
11
+ end
12
+ end
13
+ ActiveRecord::Base.send :include, Pose::Posifier
@@ -0,0 +1,3 @@
1
+ module Pose
2
+ VERSION = "0.0.2"
3
+ end
data/lib/pose.rb ADDED
@@ -0,0 +1,167 @@
1
+ # Polymorphic search for ActiveRecord objects.
2
+ module Pose
3
+ extend ActiveSupport::Concern
4
+
5
+ # By default, doesn't run in tests.
6
+ # Set this to true to test the search functionality.
7
+ CONFIGURATION = { :search_in_tests => false }
8
+
9
+ included do
10
+ has_many :pose_assignments, :as => :posable
11
+ has_many :pose_words, :through => :pose_assignments
12
+
13
+ after_save :update_pose_index
14
+ before_destroy :delete_pose_index
15
+
16
+ cattr_accessor :pose_content
17
+ end
18
+
19
+ # Asks if model should perform search.
20
+ #
21
+ # @return [false, true]
22
+ def perform_search?
23
+ !(Rails.env == 'test' and !CONFIGURATION[:search_in_tests])
24
+ end
25
+
26
+ module InstanceMethods
27
+
28
+ # Updates the associated words for this object in the database.
29
+ def update_pose_index
30
+ update_pose_words if perform_search?
31
+ end
32
+
33
+ # Removes this objects from the search index.
34
+ def delete_pose_index
35
+ self.words.clear if perform_search?
36
+ end
37
+
38
+ # Helper method.
39
+ # Updates the search words with the text returned by search_strings.
40
+ def update_pose_words
41
+
42
+ # Step 1: get an array of all words for the current object.
43
+ search_string = instance_eval &(self.class.pose_content)
44
+ new_words = search_string.to_s.split(' ').map { |word| Pose.root_word(word) }.flatten.uniq
45
+
46
+ # Step 2: Add new words to the search index.
47
+ Pose.get_words_to_add(self.pose_words, new_words).each do |word_to_add|
48
+ self.pose_words << PoseWord.find_or_create_by_text(word_to_add)
49
+ end
50
+
51
+ # Step 3: Remove now obsolete words from search index.
52
+ Pose.get_words_to_remove(self.pose_words, new_words).each do |word_to_remove|
53
+ self.pose_words.delete word_to_remove
54
+ end
55
+ end
56
+ end
57
+
58
+ class <<self
59
+
60
+ # Returns all strings that are in new_words, but not in existing_words.
61
+ # Helper method.
62
+ #
63
+ # @param [Array<String>] existing_words The words that are already associated with the object.
64
+ # @param [Array<String>] new_words The words thet the object should have from now on.
65
+ #
66
+ # @return [Array<String>] The words that need to be added to the existing_words array.
67
+ def get_words_to_add existing_words, new_words
68
+ new_words - existing_words.map(&:text)
69
+ end
70
+
71
+ # Helper method.
72
+ # Returns the id of all word objects that are in existing_words, but not in new_words.
73
+ #
74
+ # @param [Array<String>] existing_words The words that are already associated with the object.
75
+ # @param [Array<String>] new_words The words thet the object should have from now on.
76
+ #
77
+ # @return [Array<String>] The words that need to be removed from the existing_words array.
78
+ def get_words_to_remove existing_words, new_words
79
+ existing_words.map do |existing_word|
80
+ existing_word unless new_words.include?(existing_word.text)
81
+ end.compact
82
+ end
83
+
84
+ # Returns whether the given string is a URL.
85
+ #
86
+ # @param [String] word The string to check.
87
+ #
88
+ # @return [Boolean]
89
+ def is_url? word
90
+ URI::parse(word).scheme == 'http'
91
+ rescue URI::InvalidURIError
92
+ false
93
+ end
94
+
95
+ # Simplifies the given word to a generic search form.
96
+ #
97
+ # @param [String] raw_word The word to make searchable.
98
+ #
99
+ # @return [String] The stemmed version of the word.
100
+ def root_word raw_word
101
+ result = []
102
+ raw_word_copy = raw_word[0..-1]
103
+ raw_word_copy.gsub! /[()*<>'",;]/, ' '
104
+ raw_word_copy.gsub! /\s+/, ' '
105
+ raw_word_copy.split(' ').each do |word|
106
+ if Pose.is_url?(word)
107
+ result.concat word.split(/[\.\/\:]/).delete_if(&:blank?)
108
+ else
109
+ word.gsub! /[\-\/\._:]/, ' '
110
+ word.gsub! /\s+/, ' '
111
+ word.split(' ').each do |w|
112
+ stemmed_word = w.parameterize.singularize
113
+ result.concat stemmed_word.split ' '
114
+ end
115
+ end
116
+ end
117
+ result.uniq
118
+ end
119
+
120
+ # Returns all objects matching the given query.
121
+ #
122
+ # @param [String] query
123
+ # @param [Array<Class>] classes
124
+ #
125
+ # @return [Hash<Class, ActiveRecord::Relation>]
126
+ def search query, classes
127
+
128
+ # Turn 'classes' into an array.
129
+ classes = [classes].flatten
130
+ classes_names = classes.map &:name
131
+
132
+ # Get the ids of the results.
133
+ result_classes_and_ids = {}
134
+ query.split(' ').each do |query_word|
135
+ current_word_classes_and_ids = {}
136
+ classes.each { |clazz| current_word_classes_and_ids[clazz.name] = [] }
137
+ PoseAssignment.joins(:pose_word) \
138
+ .where(:pose_words => {:text.matches => "#{query_word}%"},
139
+ :posable_type => classes_names) \
140
+ .each do |pose_assignment|
141
+ current_word_classes_and_ids[pose_assignment.posable_type] << pose_assignment.posable_id
142
+ end
143
+
144
+ current_word_classes_and_ids.each do |class_name, ids|
145
+ if result_classes_and_ids.has_key? class_name
146
+ result_classes_and_ids[class_name] = result_classes_and_ids[class_name] & ids
147
+ else
148
+ result_classes_and_ids[class_name] = ids
149
+ end
150
+ end
151
+ end
152
+
153
+ # Load the results by id.
154
+ {}.tap do |result|
155
+ result_classes_and_ids.each do |class_name, ids|
156
+ result_class = Kernel.const_get class_name
157
+
158
+ if ids.any? && classes.include?(result_class)
159
+ result[result_class] = result_class.where :id => ids
160
+ else
161
+ result[result_class] = []
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,5 @@
1
+ # Assigns searchable objects to words in the search index.
2
+ class PoseAssignment < ActiveRecord::Base
3
+ belongs_to :pose_word
4
+ belongs_to :posable, :polymorphic => true
5
+ end
data/lib/pose_word.rb ADDED
@@ -0,0 +1,4 @@
1
+ # A single word in the search index.
2
+ class PoseWord < ActiveRecord::Base
3
+ has_many :pose_assignments
4
+ end
@@ -0,0 +1,18 @@
1
+ namespace :pose do
2
+
3
+ desc "Deletes and recreates the search index for all instances of the given class."
4
+ task :reindex_all, [:class_name] => :environment do
5
+ clazz = Kernel.const_get class_name
6
+ clazz.find_each do |instance|
7
+ instance.update_pose_words
8
+ end
9
+ end
10
+
11
+ desc "Removes the search index for all instances of the given classes"
12
+ task :remove_from_index, [:class_name] => :environment do
13
+ clazz = Kernel.const_get class_name
14
+ clazz.find_each do |instance|
15
+ instance.delete_pose_words
16
+ end
17
+ end
18
+ end