pose 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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