ohm-contrib 0.0.42 → 0.1.0

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.
@@ -1,6 +1,6 @@
1
1
  module Ohm
2
2
  module Contrib
3
- VERSION = "0.0.42"
3
+ VERSION = "0.1.0"
4
4
  end
5
5
 
6
6
  autoload :ActiveModelExtension, "ohm/contrib/active_model_extension"
@@ -14,8 +14,8 @@ module Ohm
14
14
  autoload :Typecast, "ohm/contrib/typecast"
15
15
  autoload :Locking, "ohm/contrib/locking"
16
16
  autoload :Callbacks, "ohm/contrib/callbacks"
17
- autoload :LunarMacros, "ohm/contrib/lunar_macros"
18
17
  autoload :Slug, "ohm/contrib/slug"
19
18
  autoload :Scope, "ohm/contrib/scope"
20
19
  autoload :SoftDelete, "ohm/contrib/soft_delete"
21
- end
20
+ autoload :FulltextSearching, "ohm/contrib/fulltext_searching"
21
+ end
@@ -0,0 +1,80 @@
1
+ begin
2
+ require "text"
3
+ rescue LoadError
4
+ raise LoadError,
5
+ "Type `[sudo] gem install text` to use Ohm::FulltextSearching."
6
+ end
7
+
8
+ module Ohm
9
+ module FulltextSearching
10
+ def self.included(model)
11
+ model.extend ClassMethods
12
+ end
13
+
14
+ module ClassMethods
15
+ def search(hash)
16
+ find(hash_to_metaphones(hash))
17
+ end
18
+
19
+ def hash_to_metaphones(hash)
20
+ ret = Hash.new { |h, k| h[k] = [] }
21
+
22
+ hash.each do |att, string|
23
+ metaphones(string).each { |m| ret[:"fulltext_#{att}"] << m }
24
+ end
25
+
26
+ return ret
27
+ end
28
+
29
+ def double_metaphone(str)
30
+ return [] if STOPWORDS.include?(str.to_s.downcase)
31
+
32
+ Text::Metaphone.double_metaphone(str).compact
33
+ end
34
+
35
+ def metaphones(str)
36
+ str.to_s.strip.split(/\s+/).map { |s| double_metaphone(s) }.flatten
37
+ end
38
+
39
+ def fulltext(att)
40
+ field = :"fulltext_#{att}"
41
+
42
+ define_method(field) { self.class.metaphones(send(att)) }
43
+ index(field)
44
+ end
45
+ end
46
+
47
+ STOPWORDS = %w{a about above according across actually adj after
48
+ afterwards again against all almost alone along already also although
49
+ always among amongst an and another any anyhow anyone anything anywhere
50
+ are aren't around as at b be became because become becomes becoming
51
+ been before beforehand begin behind being below beside besides between
52
+ beyond both but by c can can't cannot caption co co. could couldn't d
53
+ did didn't do does doesn't don't down during e each eg eight eighty
54
+ either else elsewhere end ending enough etc even ever every everyone
55
+ everything everywhere except f few first for found from further g h
56
+ had has hasn't have haven't he he'd he'll he's hence her here here's
57
+ hereafter hereby herein hereupon hers herself him himself his how
58
+ however hundred i i'd i'll i'm i've ie if in inc. indeed instead into is
59
+ isn't it it's its itself j k l last later latter latterly least less let
60
+ let's like likely ltd m made make makes many maybe me meantime meanwhile
61
+ might miss more moreover most mostly mr mrs much must my myself n namely
62
+ neither never nevertheless next nine ninety no nobody none nonetheless
63
+ noone nor not nothing now nowhere o of off often on once one one's only
64
+ onto or other others otherwise our ours ourselves out over overall own
65
+ p per perhaps q r rather recent recently s same seem seemed seeming
66
+ seems seven several she she'd she'll she's should shouldn't since so
67
+ some somehow someone something sometime sometimes somewhere still such
68
+ t taking than that that'll that's that've the their them themselves then
69
+ thence there there'd there'll there're there's there've thereafter thereby
70
+ therefore therein thereupon these they they'd they'll they're they've
71
+ thirty this those though three through throughout thru thus to together
72
+ too toward towards u under unless unlike unlikely until up upon us used
73
+ using v very via w was wasn't we we'd we'll we're we've well were weren't
74
+ what what'll what's what've whatever when whence whenever where where's
75
+ whereafter whereas whereby wherein whereupon wherever whether which while
76
+ whither who who'd who'll who's whoever whole whom whomever whose why will
77
+ with within without won't would wouldn't x y yes yet you you'd you'll
78
+ you're you've your yours yourself yourselves z}
79
+ end
80
+ end
@@ -1,9 +1,20 @@
1
1
  require 'bigdecimal'
2
2
  require 'time'
3
3
  require 'date'
4
- require 'json'
5
4
  require 'forwardable'
6
5
 
6
+ begin
7
+ require "yajl/json_gem"
8
+ rescue LoadError
9
+ $stderr.puts(
10
+ "WARNING: Currently using the `json` gem. You might encounter encoding " +
11
+ "problems with `Ohm::Types::Hash` and `Ohm::Types::Array`. Best to use " +
12
+ "the `yajl-ruby` gem to avoid problems and also for performance reasons."
13
+ )
14
+
15
+ require "json"
16
+ end
17
+
7
18
  module Ohm
8
19
  # Provides all the primitive types. The following are included:
9
20
  #
@@ -342,5 +353,4 @@ module Ohm
342
353
  end
343
354
  end
344
355
  end
345
- end
346
-
356
+ end
@@ -4,6 +4,7 @@ require File.expand_path("./helper", File.dirname(__FILE__))
4
4
 
5
5
  test "autoloading of all libraries" do
6
6
  assert_nothing_raised NameError, LoadError do
7
+ Ohm::ActiveModelExtension
7
8
  Ohm::Boundaries
8
9
  Ohm::Timestamping
9
10
  Ohm::WebValidations
@@ -12,8 +13,7 @@ test "autoloading of all libraries" do
12
13
  Ohm::Locking
13
14
  Ohm::ExtraValidations
14
15
  Ohm::DateValidations
15
- Ohm::LunarMacros
16
+ Ohm::FulltextSearching
16
17
  Ohm::Slug
17
18
  end
18
- end
19
-
19
+ end
@@ -0,0 +1 @@
1
+ café
@@ -0,0 +1,63 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("./helper", File.dirname(__FILE__))
4
+
5
+ class Video < Ohm::Model
6
+ include Ohm::FulltextSearching
7
+
8
+ attribute :title
9
+ attribute :description
10
+
11
+ fulltext :title
12
+ fulltext :description
13
+ end
14
+
15
+ test "finding basic words using metaphones" do
16
+ v = Video.create(:title => "The quick brown fox jumps over the lazy dog")
17
+
18
+ assert Video.find(:fulltext_title => "KK").include?(v)
19
+ assert Video.find(:fulltext_title => "PRN").include?(v)
20
+ assert Video.find(:fulltext_title => "FKS").include?(v)
21
+ assert Video.find(:fulltext_title => "JMPS").include?(v)
22
+ assert Video.find(:fulltext_title => "AMPS").include?(v)
23
+ assert Video.find(:fulltext_title => ["JMPS", "AMPS"]).include?(v)
24
+ assert Video.find(:fulltext_title => "LS").include?(v)
25
+ assert Video.find(:fulltext_title => "TK").include?(v)
26
+ end
27
+
28
+ test "finding using the actual words" do
29
+ v = Video.create(:title => "The quick brown fox jumps over the lazy dog")
30
+
31
+ assert Video.search(:title => "quick").include?(v)
32
+ assert Video.search(:title => "brown").include?(v)
33
+ assert Video.search(:title => "fox").include?(v)
34
+ assert Video.search(:title => "jumps").include?(v)
35
+ assert Video.search(:title => "lazy").include?(v)
36
+ assert Video.search(:title => "dog").include?(v)
37
+ end
38
+
39
+ test "finding using slightly mispelled words" do
40
+ v = Video.create(:title => "The quick brown fox jumps over the lazy dog")
41
+
42
+ assert Video.search(:title => "quik").include?(v)
43
+ assert Video.search(:title => "brwn").include?(v)
44
+ assert Video.search(:title => "fx").include?(v)
45
+ assert Video.search(:title => "fax").include?(v)
46
+ assert Video.search(:title => "jumps").include?(v)
47
+ assert Video.search(:title => "lazi").include?(v)
48
+ assert Video.search(:title => "dag").include?(v)
49
+ end
50
+
51
+ test "stopword stripping" do
52
+ # This is the actual code that strips out stopwords.
53
+
54
+ assert_equal [], Video.double_metaphone("about")
55
+
56
+ # Here we just verify that actually on a long string level,
57
+ # stop words are in fact stripped.
58
+ assert Video.metaphones(Video::STOPWORDS.join(" ")).empty?
59
+
60
+ # Finally we need to make sure that finding stop words
61
+ # should not even proceed.
62
+ assert Video.hash_to_metaphones(:title => "about").empty?
63
+ end
@@ -139,22 +139,8 @@ test "raises when trying to assign a non-array" do
139
139
  end
140
140
  end
141
141
 
142
- test "inspecting" do
143
- post = Post.create(:addresses => [{ "address1" => "#456",
144
- "city" => "Singapore",
145
- "country" => "SG" }])
146
-
147
- expected = %q{[{"address1":"#456","city":"Singapore","country":"SG"}]}
148
-
149
- assert expected == post.addresses.inspect
150
-
151
- post.addresses = 'FooBar'
152
- assert %{"\\\"FooBar\\\""} == post.addresses.inspect
153
- end
154
-
155
142
  test "type is array" do
156
143
  post = Post.create(:addresses => ["address1"])
157
144
 
158
145
  assert post.addresses.type == Array
159
- end
160
-
146
+ end
@@ -103,20 +103,18 @@ test "raises when trying to assign a non-hash" do
103
103
  end
104
104
  end
105
105
 
106
- test "inspecting" do
107
- post = Post.create(:address => { "address1" => "#456",
108
- "city" => "Singapore",
109
- "country" => "SG" })
110
-
111
- expected = %q{{"address1":"#456","city":"Singapore","country":"SG"}}
112
- assert expected == post.address.inspect
113
-
114
- post.address = 'FooBar'
115
- assert %{"\\\"FooBar\\\""} == post.address.inspect
116
- end
117
-
118
106
  test "type is Hash" do
119
107
  post = Post.new(:address => { "address1" => "#456" })
120
108
  assert Hash == post.address.type
121
109
  end
122
110
 
111
+ test "ascii 8bit encoding" do
112
+ txt = File.expand_path("fixtures/ascii8bit.txt", File.dirname(__FILE__))
113
+
114
+ data = File.read(txt, :encoding => "ascii-8bit")
115
+
116
+ post = Post.create(:address => { "address1" => data })
117
+ post = Post[post.id]
118
+
119
+ assert_equal post.address["address1"], data.force_encoding("UTF-8")
120
+ end if defined?(Encoding)
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
+ - 1
7
8
  - 0
8
- - 42
9
- version: 0.0.42
9
+ version: 0.1.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Cyril David
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-11-23 00:00:00 +08:00
17
+ date: 2011-01-08 00:00:00 +08:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -96,9 +96,9 @@ files:
96
96
  - lib/ohm/contrib/callbacks.rb
97
97
  - lib/ohm/contrib/date_validations.rb
98
98
  - lib/ohm/contrib/extra_validations.rb
99
+ - lib/ohm/contrib/fulltext_searching.rb
99
100
  - lib/ohm/contrib/length_validations.rb
100
101
  - lib/ohm/contrib/locking.rb
101
- - lib/ohm/contrib/lunar_macros.rb
102
102
  - lib/ohm/contrib/number_validations.rb
103
103
  - lib/ohm/contrib/scope.rb
104
104
  - lib/ohm/contrib/slug.rb
@@ -115,10 +115,11 @@ files:
115
115
  - test/boundaries_test.rb
116
116
  - test/callbacks_lint.rb
117
117
  - test/date_validations_test.rb
118
+ - test/fixtures/ascii8bit.txt
119
+ - test/fulltext_searching_test.rb
118
120
  - test/helper.rb
119
121
  - test/instance_callbacks_test.rb
120
122
  - test/length_validations_test.rb
121
- - test/lunar_macros_test.rb
122
123
  - test/macro_callbacks_test.rb
123
124
  - test/membership_validation_test.rb
124
125
  - test/number_validations_test.rb
@@ -1,58 +0,0 @@
1
- begin
2
- require 'lunar'
3
- rescue LoadError
4
- raise "You have to install Lunar in order to use Ohm::LunarMacros."
5
- end
6
-
7
- module Ohm
8
- module LunarMacros
9
- def self.included(base)
10
- base.send :include, Ohm::Callbacks
11
- base.after :save, :update_lunar_index
12
- base.after :delete, :delete_lunar_index
13
-
14
- base.extend ClassMethods
15
- end
16
-
17
- module ClassMethods
18
- def fuzzy(*atts) lunar_fields(:fuzzy, *atts) end
19
- def text(*atts) lunar_fields(:text, *atts) end
20
- def number(*atts) lunar_fields(:number, *atts) end
21
- def sortable(*atts) lunar_fields(:sortable, *atts) end
22
-
23
- def lunar_fields(type, *atts)
24
- @lunar_fields ||= Hash.new { |h, k| h[k] = [] }
25
-
26
- atts.each { |att|
27
- @lunar_fields[type] << att unless @lunar_fields[type].include?(att)
28
- }
29
-
30
- @lunar_fields[type]
31
- end
32
- end
33
-
34
- def update_lunar_index
35
- Lunar.index self.class do |i|
36
- i.id id
37
-
38
- [:fuzzy, :text, :number, :sortable].each do |type|
39
- self.class.lunar_fields(type).each do |field|
40
- value = send(field)
41
-
42
- if type == :text and value.kind_of?(Enumerable)
43
- i.text field, value.join(' ') unless value.empty?
44
- else
45
- i.send type, field, value unless value.to_s.empty?
46
- end
47
- end
48
- end
49
- end
50
- end
51
-
52
- protected
53
- def delete_lunar_index
54
- Lunar.delete self.class, id
55
- end
56
- end
57
- end
58
-
@@ -1,146 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- require File.expand_path("./helper", File.dirname(__FILE__))
4
-
5
- class Post < Ohm::Model
6
- include Ohm::LunarMacros
7
- end
8
-
9
- test "fuzzy / text / number / sortable" do
10
- Post.fuzzy :name
11
- assert [:name] == Post.fuzzy
12
-
13
- Post.text :name
14
- assert [:name] == Post.text
15
-
16
- Post.number :name
17
- assert [:name] == Post.number
18
-
19
- Post.sortable :name
20
- assert [:name] == Post.sortable
21
- end
22
-
23
- test "fuzzy / text / number / sortable done twice" do
24
- Post.fuzzy :name
25
- Post.fuzzy :name
26
- assert [:name] == Post.fuzzy
27
-
28
- Post.text :name
29
- Post.text :name
30
- assert [:name] == Post.text
31
-
32
- Post.number :name
33
- Post.number :name
34
- assert [:name] == Post.number
35
-
36
- Post.sortable :name
37
- Post.sortable :name
38
- assert [:name] == Post.sortable
39
- end
40
-
41
- class Document < Ohm::Model
42
- include Ohm::LunarMacros
43
-
44
- fuzzy :filename
45
- text :content
46
- number :author_id
47
- sortable :views
48
-
49
- attribute :filename
50
- attribute :content
51
- attribute :author_id
52
- attribute :views
53
- end
54
-
55
- test "fuzzy indexing" do
56
- doc = Document.create(:filename => "amazing.mp3")
57
-
58
- strs = %w{a am ama amaz amazi amazin amazing amazing. amazing.m
59
- amazing.mp amazing.mp3}
60
-
61
- strs.each do |str|
62
- r = Lunar.search(Document, :fuzzy => { :filename => str })
63
- assert r.include?(doc)
64
- end
65
-
66
- doc.update(:filename => "crazy.mp3")
67
-
68
- strs = %w{c cr cra craz crazy crazy. crazy.m crazy.mp crazy.mp3}
69
-
70
- strs.each do |str|
71
- r = Lunar.search(Document, :fuzzy => { :filename => str })
72
- assert r.include?(doc)
73
- end
74
- end
75
-
76
- test "text indexing" do
77
- doc = Document.create(:content => "The quick brown fox")
78
-
79
- queries = ['quick', 'brown', 'fox', 'quick brown' 'quick fox', 'fox brown',
80
- 'the quick brown fox']
81
-
82
- queries.each do |q|
83
- r = Lunar.search(Document, :q => q)
84
- assert r.include?(doc)
85
- end
86
-
87
- doc.update(:content => "lazy dog jumps over")
88
-
89
- queries = ['lazy', 'dog', 'jumps', 'over', 'lazy dog', 'jumps over']
90
-
91
- queries.each do |q|
92
- r = Lunar.search(Document, :q => q)
93
- assert r.include?(doc)
94
- end
95
- end
96
-
97
- test "number indexing" do
98
- doc = Document.create(:author_id => 99)
99
-
100
- [99..100, 98..99, 98..100].each do |range|
101
- r = Lunar.search(Document, :author_id => range)
102
- assert r.include?(doc)
103
- end
104
-
105
- [97..98, 100..101].each do |range|
106
- r = Lunar.search(Document, :author_id => range)
107
- assert ! r.include?(doc)
108
- end
109
-
110
- doc.update(:author_id => 49)
111
-
112
- [49..50, 48..49, 48..50].each do |range|
113
- r = Lunar.search(Document, :author_id => range)
114
- assert r.include?(doc)
115
- end
116
-
117
- [47..48, 50..51].each do |range|
118
- r = Lunar.search(Document, :author_id => range)
119
- assert ! r.include?(doc)
120
- end
121
- end
122
-
123
- test "sortable indexing" do
124
- osx = Document.create(:content => "apple mac osx", :views => 500)
125
- iphone = Document.create(:content => "apple iphone", :views => 10_000)
126
-
127
- assert [iphone, osx] == Lunar.search(Document, :q => "apple").sort_by(:views, :order => "DESC")
128
- assert [osx, iphone] == Lunar.search(Document, :q => "apple").sort_by(:views, :order => "ASC")
129
-
130
- osx.update(:content => "ios mac osx", :views => 1000)
131
- iphone.update(:content => "ios iphone", :views => 500)
132
-
133
- assert [osx, iphone] == Lunar.search(Document, :q => "ios").sort_by(:views, :order => "DESC")
134
- assert [iphone, osx] == Lunar.search(Document, :q => "ios").sort_by(:views, :order => "ASC")
135
- end
136
-
137
- test "on delete" do
138
- doc = Document.create(:filename => "amazing.mp3", :content => "apple mac osx",
139
- :author_id => 99, :views => 500)
140
-
141
- doc.delete
142
-
143
- assert Lunar.search(Document, :q => "apple").size.zero?
144
- assert Lunar.search(Document, :fuzzy => { :filename => "amazing" }).size.zero?
145
- end
146
-