search_magic 0.0.2 → 0.0.3
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.
data/Gemfile.lock
CHANGED
@@ -9,20 +9,11 @@ module SearchMagic
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def search_on(field_name, options = {})
|
12
|
-
|
13
|
-
send(:searchable_fields)[field_name] = Metadata.new(:field_name => field_name, :field => fields[field_name.to_s], :association => metadata, :options => options)
|
12
|
+
searchable_fields[field_name] = options
|
14
13
|
end
|
15
14
|
|
16
15
|
def searchables
|
17
|
-
@searchables ||=
|
18
|
-
hash.keys.each do |name|
|
19
|
-
if self.method_defined?(name) && reflect_on_association(name).nil?
|
20
|
-
alias_method(:"_#{name}", name)
|
21
|
-
else
|
22
|
-
define_method(:"_#{name}") {find_searchable_value(name)}
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
16
|
+
@searchables ||= create_searchables
|
26
17
|
end
|
27
18
|
|
28
19
|
def search(pattern)
|
@@ -39,13 +30,36 @@ module SearchMagic
|
|
39
30
|
criteria
|
40
31
|
end
|
41
32
|
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def create_searchables
|
37
|
+
fields = searchable_fields.map do |field_name, options|
|
38
|
+
if association = reflect_on_association(field_name)
|
39
|
+
options[:as] ||= nil
|
40
|
+
only = [options[:only]].flatten.compact
|
41
|
+
except = [options[:except]].flatten.compact
|
42
|
+
associated = association.class_name.constantize.searchables.reject {|key, value| except.include?(key) }.select {|key, value| only.blank? ? true : only.include?(key) }
|
43
|
+
associated.map do |name, metadata|
|
44
|
+
Metadata.new(:type => self, :through => lambda do |obj|
|
45
|
+
value = obj.send(field_name)
|
46
|
+
value.is_a?(Array) ? value.map {|item| metadata.through.call(item)} : metadata.through.call(value)
|
47
|
+
end, :prefix => field_name.to_s.singularize.to_sym, :field_name => name, :options => metadata.options.merge(options))
|
48
|
+
end
|
49
|
+
else
|
50
|
+
Metadata.new(:type => self, :through => lambda {|obj| obj.send(field_name) }, :field_name => field_name.to_s.pluralize.singularize.to_sym, :options => options)
|
51
|
+
end
|
52
|
+
end.flatten
|
53
|
+
|
54
|
+
Hash[*fields.map {|metadata| [metadata.name, metadata]}.flatten]
|
55
|
+
end
|
42
56
|
end
|
43
57
|
|
44
58
|
module InstanceMethods
|
45
59
|
private
|
46
60
|
|
47
61
|
def update_searchable_values
|
48
|
-
|
62
|
+
self.searchable_values = self.class.searchables.values.map {|metadata| metadata.searchable_value_for(self)}.flatten
|
49
63
|
end
|
50
64
|
|
51
65
|
def find_searchable_value(name)
|
@@ -55,44 +69,28 @@ module SearchMagic
|
|
55
69
|
end
|
56
70
|
|
57
71
|
class Metadata
|
58
|
-
attr_accessor :
|
72
|
+
attr_accessor :type, :through, :prefix, :field_name, :options
|
59
73
|
|
60
74
|
def initialize(attributes = {})
|
61
75
|
attributes.each do |key, value|
|
62
76
|
send(:"#{key}=", value)
|
63
77
|
end
|
64
|
-
options[:only] = [options[:only]].flatten.compact
|
65
|
-
options[:except] = [options[:except]].flatten.compact
|
66
78
|
end
|
67
79
|
|
68
|
-
def
|
69
|
-
|
80
|
+
def name
|
81
|
+
@name ||= [options[:skip_prefix].presence ? nil : (prefix.present? ? options[:as] || prefix : nil),
|
82
|
+
prefix.present? ? field_name : (options[:as] || field_name)].compact.join("_").to_sym
|
70
83
|
end
|
71
84
|
|
72
|
-
def
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
case self.association.try(:macro)
|
78
|
-
when nil
|
79
|
-
[[self.field.type == Array ? name.to_s.singularize.to_sym : name, value, nil, self.options]]
|
80
|
-
when :embedded_in, :embeds_one, :referenced_in, :references_one
|
81
|
-
fields.map {|sub_name| [create_nested_name(name, sub_name), value, sub_name, self.options.merge(sub_fields[sub_name])]}
|
82
|
-
else
|
83
|
-
fields.map {|sub_name| [create_nested_name(name.to_s.singularize, sub_name.to_s.pluralize), value, sub_name, self.options.merge(sub_fields[sub_name])]}
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
def create_nested_name(owning_name, sub_name)
|
88
|
-
[owning_name, sub_name].compact.join("_").to_sym
|
85
|
+
def value_for(obj)
|
86
|
+
v = self.through.call(obj)
|
87
|
+
v = v.is_a?(Array) ? v.join(" ") : v.to_s
|
88
|
+
v = v.gsub(/[[:punct:]]/, ' ') unless options[:keep_punctuation]
|
89
|
+
v
|
89
90
|
end
|
90
91
|
|
91
|
-
def
|
92
|
-
|
93
|
-
v = v.is_a?(Array) ? v.join(" ") : v.to_s
|
94
|
-
v = v.gsub(/[[:punct:]]/, '') unless options[:keep_punctuation]
|
95
|
-
v.downcase.split.map {|word| [searchable_name, word].join(":")}
|
92
|
+
def searchable_value_for(obj)
|
93
|
+
value_for(obj).downcase.split.map {|word| [name, word].join(":")}
|
96
94
|
end
|
97
95
|
end
|
98
96
|
|
data/lib/search_magic/version.rb
CHANGED
@@ -6,13 +6,14 @@ describe SearchMagic::FullTextSearch do
|
|
6
6
|
it { subject.searchable_fields.keys.should include(:address) }
|
7
7
|
it { subject.searchable_fields.keys.should include(:phones) }
|
8
8
|
it { should respond_to(:searchables)}
|
9
|
-
its(
|
9
|
+
its("searchables.keys") { should include(:address_street, :address_city, :address_state, :address_post_code, :mobile_number)}
|
10
|
+
its("searchables.keys") { should_not include(:mobile_country_code)}
|
10
11
|
end
|
11
12
|
|
12
13
|
describe "model includes referenced document fields in :searchable_fields" do
|
13
14
|
subject { Part }
|
14
15
|
it { subject.searchable_fields.keys.should include(:part_number) }
|
15
|
-
its(
|
16
|
+
its("searchables.keys") { should include(:part_number, :category_name) }
|
16
17
|
end
|
17
18
|
|
18
19
|
context "when a model embeds one other document" do
|
@@ -26,12 +27,6 @@ describe SearchMagic::FullTextSearch do
|
|
26
27
|
its(:address) { should_not be_nil }
|
27
28
|
its(:phones) { should_not be_empty }
|
28
29
|
its(:searchable_values) { should include("address_street:123", "address_street:example", "address_street:st") }
|
29
|
-
it { should respond_to(:_address_street, :_address_city, :_address_state, :_address_post_code, :_mobile_numbers)}
|
30
|
-
its(:_address_street) { should include("123", "example", "st") }
|
31
|
-
its(:_address_state) { should == "ca" }
|
32
|
-
its(:_address_city) { should == "nowhereland" }
|
33
|
-
its(:_address_post_code) { should == "12345" }
|
34
|
-
its(:_mobile_numbers) { should include("555-1234", "333-7890") }
|
35
30
|
end
|
36
31
|
|
37
32
|
context "when searching for 'address_city:nowhereland'" do
|
@@ -46,6 +41,23 @@ describe SearchMagic::FullTextSearch do
|
|
46
41
|
its(:count) { should == 1 }
|
47
42
|
its("first.name") { should == "Samuel" }
|
48
43
|
end
|
44
|
+
|
45
|
+
context "when searching for 'mobile_number:555-'" do
|
46
|
+
subject { Person.search("mobile_number:555-") }
|
47
|
+
its(:count) { should == 2 }
|
48
|
+
end
|
49
|
+
|
50
|
+
context "when searching for 'mobile_number:333-'" do
|
51
|
+
subject { Person.search("mobile_number:333-") }
|
52
|
+
its(:count) { should == 1 }
|
53
|
+
its("first.name") { should == "Joshua" }
|
54
|
+
end
|
55
|
+
|
56
|
+
context "when searching for 'mobile_number:-0987'" do
|
57
|
+
subject { Person.search("mobile_number:-0987") }
|
58
|
+
its(:count) { should == 1 }
|
59
|
+
its("first.name") { should == "Samuel" }
|
60
|
+
end
|
49
61
|
end
|
50
62
|
|
51
63
|
context "when a model references other documents" do
|
@@ -100,10 +112,6 @@ describe SearchMagic::FullTextSearch do
|
|
100
112
|
it { should be }
|
101
113
|
its(:part_number) { should_not be_nil }
|
102
114
|
its(:searchable_values) { should include("part_number:t11001", "category_name:table", "status:available", "serial:t0411001") }
|
103
|
-
its(:_part_number) { should == "t11001" }
|
104
|
-
its(:_category_name) { should == "table" }
|
105
|
-
its(:_status) { should == "available" }
|
106
|
-
its(:_serial) { should == "T0411001" }
|
107
115
|
end
|
108
116
|
|
109
117
|
context "when searching for 'category_name:table'" do
|
@@ -25,8 +25,8 @@ describe SearchMagic::FullTextSearch do
|
|
25
25
|
its("searchable_fields.keys") { should include(:title, :description, :tags) }
|
26
26
|
its("searchable_fields.keys") { should_not include(:uuid) }
|
27
27
|
|
28
|
-
its(
|
29
|
-
its(
|
28
|
+
its("searchables.keys") { should include(:title, :description, :tag) }
|
29
|
+
its("searchables.keys") { should_not include(:uuid) }
|
30
30
|
end
|
31
31
|
|
32
32
|
describe "saving a model should run the :update_searchable_values callback" do
|