search_magic 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- search_magic (0.0.1)
4
+ search_magic (0.0.2)
5
5
  mongoid (>= 2.0.0.rc.7)
6
6
 
7
7
  GEM
@@ -9,20 +9,11 @@ module SearchMagic
9
9
  end
10
10
 
11
11
  def search_on(field_name, options = {})
12
- metadata = reflect_on_association(field_name)
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 ||= Hash[*searchable_fields.values.map {|metadata| metadata.searchable_names(nil).map {|a| [a.first, a.last]}}.flatten].tap do |hash|
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
- send :searchable_values=, self.searchable_fields.values.map {|metadata| metadata.searchable_value(self)}.flatten
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 :field_name, :field, :association, :options
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 searchable_value(model)
69
- searchable_names(model).map {|searchable_name, value, sub_name, options| value_for(searchable_name, value, sub_name, options)}
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 searchable_names(model)
73
- name = options[:skip_prefix].presence ? nil : (options[:as] || self.field_name)
74
- value = model.present? ? model.send(self.field_name) : nil
75
- sub_fields = self.association.class_name.constantize.searchables if self.association
76
- fields = (sub_fields.keys - options[:except]) & (options[:only].blank? ? sub_fields.keys : options[:only]) if sub_fields
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 value_for(searchable_name, value, field_name, options)
92
- v = field_name.present? && value.present? ? [value].flatten.map{|i| i.send("_#{field_name}")} : value
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
 
@@ -1,3 +1,3 @@
1
1
  module SearchMagic
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -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(:searchables) { should include(:address_street, :address_city, :address_state, :address_post_code, :mobile_numbers)}
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(:searchables) { should include(:part_number, :category_name) }
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(:searchables) { should include(:title, :description, :tag) }
29
- its(:searchables) { should_not include(:uuid) }
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
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 2
9
- version: 0.0.2
8
+ - 3
9
+ version: 0.0.3
10
10
  platform: ruby
11
11
  authors:
12
12
  - Joshua Bowers