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
@@ -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