search_magic 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +8 -8
- data/README.textile +22 -9
- data/lib/search_magic/full_text_search.rb +37 -28
- data/lib/search_magic/metadata.rb +2 -2
- data/lib/search_magic/version.rb +1 -1
- data/search_magic.gemspec +1 -1
- data/spec/unit/search_magic/associations_spec.rb +10 -10
- data/spec/unit/search_magic/fields_spec.rb +35 -10
- data/spec/unit/search_magic/model_updates_spec.rb +80 -0
- metadata +7 -5
data/Gemfile.lock
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
search_magic (0.0.
|
5
|
-
mongoid (>= 2.0.0.rc.
|
4
|
+
search_magic (0.0.7)
|
5
|
+
mongoid (>= 2.0.0.rc.8)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: http://rubygems.org/
|
9
9
|
specs:
|
10
|
-
activemodel (3.0.
|
11
|
-
activesupport (= 3.0.
|
10
|
+
activemodel (3.0.5)
|
11
|
+
activesupport (= 3.0.5)
|
12
12
|
builder (~> 2.1.2)
|
13
13
|
i18n (~> 0.4)
|
14
|
-
activesupport (3.0.
|
14
|
+
activesupport (3.0.5)
|
15
15
|
bson (1.2.4)
|
16
16
|
bson_ext (1.2.4)
|
17
17
|
builder (2.1.2)
|
@@ -20,7 +20,7 @@ GEM
|
|
20
20
|
i18n (0.5.0)
|
21
21
|
mongo (1.2.4)
|
22
22
|
bson (>= 1.2.4)
|
23
|
-
mongoid (2.0.0.rc.
|
23
|
+
mongoid (2.0.0.rc.8)
|
24
24
|
activemodel (~> 3.0)
|
25
25
|
mongo (~> 1.2)
|
26
26
|
tzinfo (~> 0.3.22)
|
@@ -33,7 +33,7 @@ GEM
|
|
33
33
|
rspec-expectations (2.5.0)
|
34
34
|
diff-lcs (~> 1.1.2)
|
35
35
|
rspec-mocks (2.5.0)
|
36
|
-
tzinfo (0.3.
|
36
|
+
tzinfo (0.3.25)
|
37
37
|
will_paginate (3.0.pre2)
|
38
38
|
|
39
39
|
PLATFORMS
|
@@ -42,6 +42,6 @@ PLATFORMS
|
|
42
42
|
DEPENDENCIES
|
43
43
|
bson_ext
|
44
44
|
database_cleaner
|
45
|
-
mongoid (>= 2.0.0.rc.
|
45
|
+
mongoid (>= 2.0.0.rc.8)
|
46
46
|
rspec
|
47
47
|
search_magic!
|
data/README.textile
CHANGED
@@ -4,7 +4,11 @@ SearchMagic provides full-text search capabilities to "mongoid":http://github.co
|
|
4
4
|
|
5
5
|
h2. Installation
|
6
6
|
|
7
|
-
SearchMagic is built on top of the latest pre-release version of mongoid; in all likelihood, it will only work with versions greater than or equal to _2.0.0.rc.
|
7
|
+
SearchMagic is built on top of the latest pre-release version of mongoid; in all likelihood, it will only work with versions greater than or equal to _2.0.0.rc.8_. The project can be installed as a gem on a target system:
|
8
|
+
|
9
|
+
bc. gem install search_magic
|
10
|
+
|
11
|
+
For environments where bundler is being used, it can be installed by adding the following to your Gemfile and running @bundle@.
|
8
12
|
|
9
13
|
bc. gem 'search_magic'
|
10
14
|
|
@@ -29,9 +33,9 @@ bc.. class Address
|
|
29
33
|
search_on :post_code
|
30
34
|
end
|
31
35
|
|
32
|
-
p. At this point, *Address* can be searched by calling its _*:
|
36
|
+
p. At this point, *Address* can be searched by calling its _*:search_for*_ method:
|
33
37
|
|
34
|
-
bc. Address.
|
38
|
+
bc. Address.search_for("state:ca")
|
35
39
|
|
36
40
|
It is also possible to sort models on fields which have been marked as searchable through the _*:arrange*_ method:
|
37
41
|
|
@@ -86,6 +90,7 @@ Finally, it should be noted that nesting of searchable documents is possible. (C
|
|
86
90
|
|
87
91
|
bc.. class Part
|
88
92
|
include Mongoid::Document
|
93
|
+
include Mongoid::Timestamps
|
89
94
|
include SearchMagic::FullTextSearch
|
90
95
|
field :serial
|
91
96
|
references_in :part_number
|
@@ -116,17 +121,25 @@ end
|
|
116
121
|
|
117
122
|
p. *PartNumber* will be able to search on both _:number_ and _:category_name_. *Part*, on the other hand, will absorb all of the searchable fields of PartNumber, including its associations. So, it can be searched on _:serial_, _:number_, and _:category_name_
|
118
123
|
|
119
|
-
h3. :
|
124
|
+
h3. :search_for
|
120
125
|
|
121
|
-
Searching a model with SearchMagic is simple: each model gains a class method called _
|
126
|
+
Searching a model with SearchMagic is simple: each model gains a class method called _*:search_for*_ which accepts one parameter, the search pattern. This method is a "mongoid scope":http://mongoid.org/docs/querying/; it will always return a criteria object after executing. As such, it plays nicely with other scopes on your models.
|
122
127
|
|
123
128
|
SearchMagic expects the incoming _pattern_ to be a string containing whitespace delimited phrases. Each phrase can either be a single word, or a _selector:value_ pair. Multiple phrases will narrow the search field: each additional phrase places an additional requirement on a matching document. Single word phrases are matched across all entries in a model's _:searchable_values_ array. The pairs, on the other hand, restrict the search for _value_ against only those entries which match _selector_. In either case, _word_ or _value_ may contain fragments of whole entries stored within _:searchable_values_.
|
124
129
|
|
125
130
|
Using the models defined in the previous section, the following searches are all perfectly valid:
|
126
131
|
|
127
|
-
bc. Part.
|
128
|
-
Part.
|
129
|
-
Part.
|
132
|
+
bc. Part.search_for("table") # full text search on "table"
|
133
|
+
Part.search_for("category_name:table") # restricts the search for "table" to "category_name"
|
134
|
+
Part.search_for("bike serial:b1234") # full text search on "bike", with an extra requirement that the serial be "b1234"
|
135
|
+
|
136
|
+
As _*:search_for*_ is a scope, it can be called on any previous scope within the call chain:
|
137
|
+
|
138
|
+
bc. Part.where(:created_at.gt => 1.day.ago.time).search_for("table")
|
139
|
+
|
140
|
+
_*:search_for*_ can be called multiple times within the same scope chain. Doing so will append each successive pattern to the previous searches. Effectively, this is the same as performing a single _*:search_for*_ with whitespace delimited terms in the pattern. To make such expressions slightly more readable, _*:search_for*_ is aliased as _*:and_for*_:
|
141
|
+
|
142
|
+
bc. Part.search_for("bike").and_for("serial:b1234")
|
130
143
|
|
131
144
|
h3. :arrange
|
132
145
|
|
@@ -138,7 +151,7 @@ Part.arrange(:category_name, :desc) # arrange the parts in descending order by :
|
|
138
151
|
|
139
152
|
As mentioned, _*:arrange*_ is a scope, so it can be chained with other scopes on a given model:
|
140
153
|
|
141
|
-
bc. Part.
|
154
|
+
bc. Part.search_for("category_name:table").arrange(:serial, :asc)
|
142
155
|
|
143
156
|
h2. Problems? Comments?
|
144
157
|
|
@@ -1,15 +1,18 @@
|
|
1
1
|
module SearchMagic
|
2
2
|
module FullTextSearch
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_attribute :searchable_fields, :instance_writer => false
|
7
|
+
self.searchable_fields = {}
|
8
|
+
field :searchable_values, :type => Array, :default => []
|
9
|
+
field :arrangeable_values, :type => Hash, :default => {}
|
10
|
+
before_save :update_searchable_values
|
11
|
+
before_save :update_arrangeable_values
|
12
|
+
after_save :update_associated_documents
|
13
|
+
end
|
14
|
+
|
3
15
|
module ClassMethods
|
4
|
-
def self.extended(receiver)
|
5
|
-
receiver.send :class_attribute, :searchable_fields, :instance_writer => false
|
6
|
-
receiver.send :searchable_fields=, {}
|
7
|
-
receiver.send :field, :searchable_values, :type => Array, :default => []
|
8
|
-
receiver.send :field, :arrangeable_values, :type => Hash, :default => {}
|
9
|
-
receiver.send :before_save, :update_searchable_values
|
10
|
-
receiver.send :before_save, :update_arrangeable_values
|
11
|
-
end
|
12
|
-
|
13
16
|
def search_on(field_name, options = {})
|
14
17
|
searchable_fields[field_name] = options
|
15
18
|
end
|
@@ -18,7 +21,13 @@ module SearchMagic
|
|
18
21
|
@searchables ||= create_searchables
|
19
22
|
end
|
20
23
|
|
21
|
-
def
|
24
|
+
def inverse_searchables
|
25
|
+
@inverse_searchables ||= relations.values.
|
26
|
+
select {|metadata| metadata.class_name.constantize.searchable_fields.keys.include?(metadata.inverse_setter.chomp("=").to_sym)}.
|
27
|
+
map(&:name)
|
28
|
+
end
|
29
|
+
|
30
|
+
def search_for(pattern)
|
22
31
|
rval = /("[^"]+"|\S+)/
|
23
32
|
rsearch = /(?:(#{searchables.keys.join('|')}):#{rval})|#{rval}/i
|
24
33
|
unless pattern.blank?
|
@@ -32,6 +41,7 @@ module SearchMagic
|
|
32
41
|
criteria
|
33
42
|
end
|
34
43
|
end
|
44
|
+
alias :and_for :search_for
|
35
45
|
|
36
46
|
def arrange(arrangeable, direction = :asc)
|
37
47
|
arrangeable.blank? || !searchables.keys.include?(arrangeable.to_sym) ? criteria : order_by([["arrangeable_values.#{arrangeable}", direction]])
|
@@ -51,7 +61,7 @@ module SearchMagic
|
|
51
61
|
Metadata.new(:type => self, :through => lambda do |obj|
|
52
62
|
value = obj.send(field_name)
|
53
63
|
value.is_a?(Array) ? value.map {|item| metadata.through.call(item)} : metadata.through.call(value)
|
54
|
-
end, :prefix => field_name.to_s.singularize.to_sym, :field_name => name, :options => metadata.options.merge(options))
|
64
|
+
end, :prefix => field_name.to_s.singularize.to_sym, :field_name => name, :relation_metadata => association, :options => metadata.options.merge(options))
|
55
65
|
end
|
56
66
|
else
|
57
67
|
Metadata.new(:type => self, :through => lambda {|obj| obj.send(field_name) }, :field_name => field_name.to_s.pluralize.singularize.to_sym, :options => options)
|
@@ -62,24 +72,23 @@ module SearchMagic
|
|
62
72
|
end
|
63
73
|
end
|
64
74
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
self.searchable_values = self.class.searchables.values.map {|metadata| metadata.searchable_value_for(self)}.flatten
|
70
|
-
end
|
71
|
-
|
72
|
-
def update_arrangeable_values
|
73
|
-
self.arrangeable_values = {}
|
74
|
-
self.class.searchables.values.each do |metadata|
|
75
|
-
self.arrangeable_values[metadata.name] = metadata.arrangeable_value_for(self)
|
76
|
-
end
|
77
|
-
end
|
75
|
+
private
|
76
|
+
|
77
|
+
def update_searchable_values
|
78
|
+
self.searchable_values = self.class.searchables.values.map {|metadata| metadata.searchable_value_for(self)}.flatten
|
78
79
|
end
|
79
|
-
|
80
|
-
def
|
81
|
-
|
82
|
-
|
80
|
+
|
81
|
+
def update_arrangeable_values
|
82
|
+
self.arrangeable_values = Hash[*self.class.searchables.map {|key, metadata|
|
83
|
+
[key, metadata.arrangeable_value_for(self)]
|
84
|
+
}.flatten(1)]
|
85
|
+
end
|
86
|
+
|
87
|
+
def update_associated_documents
|
88
|
+
self.class.inverse_searchables.each do |relation_name|
|
89
|
+
relation = send(relation_name)
|
90
|
+
(relation.is_a?(Array) ? relation : [relation]).each(&:save!)
|
91
|
+
end
|
83
92
|
end
|
84
93
|
end
|
85
94
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module SearchMagic
|
2
2
|
class Metadata
|
3
|
-
attr_accessor :type, :through, :prefix, :field_name, :options
|
3
|
+
attr_accessor :type, :through, :prefix, :field_name, :relation_metadata, :options
|
4
4
|
|
5
5
|
def initialize(attributes = {})
|
6
6
|
attributes.each do |key, value|
|
@@ -9,7 +9,7 @@ module SearchMagic
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def name
|
12
|
-
@name ||= [options[:skip_prefix].presence ? nil : (prefix.present? ? options[:as] || prefix : nil),
|
12
|
+
@name ||= [options[:skip_prefix].presence ? nil : (prefix.present? ? (options[:as] || prefix) : nil),
|
13
13
|
prefix.present? ? field_name : (options[:as] || field_name)].compact.join("_").to_sym
|
14
14
|
end
|
15
15
|
|
data/lib/search_magic/version.rb
CHANGED
data/search_magic.gemspec
CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |s|
|
|
12
12
|
s.summary = %q{SearchMagic provides scoped full text search and sort capabilities to Mongoid documents}
|
13
13
|
s.description = %q{Adds scopes to a Mongoid document providing search and sort capabilities on arbitrary fields and associations.}
|
14
14
|
|
15
|
-
s.add_dependency("mongoid", ">= 2.0.0.rc.
|
15
|
+
s.add_dependency("mongoid", ">= 2.0.0.rc.8")
|
16
16
|
s.add_development_dependency("rspec")
|
17
17
|
s.add_development_dependency("database_cleaner")
|
18
18
|
s.add_development_dependency("bson_ext")
|
@@ -42,31 +42,31 @@ describe SearchMagic::FullTextSearch do
|
|
42
42
|
end
|
43
43
|
|
44
44
|
context "when searching for 'address_city:nowhereland'" do
|
45
|
-
subject { Person.
|
45
|
+
subject { Person.search_for("address_city:nowhereland") }
|
46
46
|
its("selector.keys") { should include(:searchable_values) }
|
47
47
|
its(:count) { should == 1 }
|
48
48
|
its("first.name") { should == "Joshua" }
|
49
49
|
end
|
50
50
|
|
51
51
|
context "when searching for 'arbitrary address_post_code:54321'" do
|
52
|
-
subject { Person.
|
52
|
+
subject { Person.search_for("arbitrary address_post_code:54321") }
|
53
53
|
its(:count) { should == 1 }
|
54
54
|
its("first.name") { should == "Samuel" }
|
55
55
|
end
|
56
56
|
|
57
57
|
context "when searching for 'mobile_number:555-'" do
|
58
|
-
subject { Person.
|
58
|
+
subject { Person.search_for("mobile_number:555-") }
|
59
59
|
its(:count) { should == 2 }
|
60
60
|
end
|
61
61
|
|
62
62
|
context "when searching for 'mobile_number:333-'" do
|
63
|
-
subject { Person.
|
63
|
+
subject { Person.search_for("mobile_number:333-") }
|
64
64
|
its(:count) { should == 1 }
|
65
65
|
its("first.name") { should == "Joshua" }
|
66
66
|
end
|
67
67
|
|
68
68
|
context "when searching for 'mobile_number:-0987'" do
|
69
|
-
subject { Person.
|
69
|
+
subject { Person.search_for("mobile_number:-0987") }
|
70
70
|
its(:count) { should == 1 }
|
71
71
|
its("first.name") { should == "Samuel" }
|
72
72
|
end
|
@@ -105,13 +105,13 @@ describe SearchMagic::FullTextSearch do
|
|
105
105
|
end
|
106
106
|
|
107
107
|
context "when searching for 'category_name:table'" do
|
108
|
-
subject { PartNumber.
|
108
|
+
subject { PartNumber.search_for("category_name:table").map(&:value) }
|
109
109
|
its(:count) { should == 2 }
|
110
110
|
it { should include("T11001", "T11002") }
|
111
111
|
end
|
112
112
|
|
113
113
|
context "when searching for '11001'" do
|
114
|
-
subject { PartNumber.
|
114
|
+
subject { PartNumber.search_for("11001").map(&:value) }
|
115
115
|
its(:count) { should == 2 }
|
116
116
|
it { should include("T11001", "C11001") }
|
117
117
|
end
|
@@ -127,19 +127,19 @@ describe SearchMagic::FullTextSearch do
|
|
127
127
|
end
|
128
128
|
|
129
129
|
context "when searching for 'category_name:table'" do
|
130
|
-
subject { Part.
|
130
|
+
subject { Part.search_for("category_name:table").map(&:serial) }
|
131
131
|
its(:count) { should == 4 }
|
132
132
|
it { should include("T0411001", "T0511010", "T0411037", "T0511178") }
|
133
133
|
end
|
134
134
|
|
135
135
|
context "when searching for 'broken chair'" do
|
136
|
-
subject { Part.
|
136
|
+
subject { Part.search_for("broken chair").map(&:serial) }
|
137
137
|
its(:count) { should == 2 }
|
138
138
|
it { should include("C0511010", "C0511010") }
|
139
139
|
end
|
140
140
|
|
141
141
|
context "when searching for 'part_number:T11001'" do
|
142
|
-
subject { Part.
|
142
|
+
subject { Part.search_for("part_number:T11001").map(&:serial) }
|
143
143
|
its(:count) { should == 2 }
|
144
144
|
it { should include("T0411001", "T0511010") }
|
145
145
|
end
|
@@ -17,7 +17,8 @@ describe SearchMagic::FullTextSearch do
|
|
17
17
|
its(:default) { should == [] }
|
18
18
|
end
|
19
19
|
|
20
|
-
it { should respond_to(:
|
20
|
+
it { should respond_to(:search_for).with(1).argument }
|
21
|
+
it { should respond_to(:and_for).with(1).argument }
|
21
22
|
end
|
22
23
|
|
23
24
|
context "when :search_on called with [:title, :description, :tags]" do
|
@@ -46,7 +47,7 @@ describe SearchMagic::FullTextSearch do
|
|
46
47
|
end
|
47
48
|
|
48
49
|
context "when :search is performed on a model without :searchables" do
|
49
|
-
subject { NoSearchables.
|
50
|
+
subject { NoSearchables.search_for("foo") }
|
50
51
|
it { should be_a(Mongoid::Criteria) }
|
51
52
|
its(:count) { should == 0 }
|
52
53
|
end
|
@@ -59,50 +60,74 @@ describe SearchMagic::FullTextSearch do
|
|
59
60
|
end
|
60
61
|
|
61
62
|
context "when searching for nil" do
|
62
|
-
subject { Asset.
|
63
|
+
subject { Asset.search_for(nil) }
|
63
64
|
it { should be_a(Mongoid::Criteria) }
|
64
65
|
its("selector.keys") { should_not include(:searchable_values) }
|
65
66
|
end
|
66
67
|
|
67
68
|
context "when searching on an empty string" do
|
68
|
-
subject { Asset.
|
69
|
+
subject { Asset.search_for("") }
|
69
70
|
it { should be_a(Mongoid::Criteria) }
|
70
71
|
its("selector.keys") { should_not include(:searchable_values) }
|
71
72
|
end
|
72
73
|
|
73
74
|
context "when searching for anything" do
|
74
|
-
subject { Asset.
|
75
|
+
subject { Asset.search_for("foo") }
|
75
76
|
it { should be_a(Mongoid::Criteria) }
|
76
77
|
its("selector.keys") { should include(:searchable_values) }
|
77
78
|
end
|
78
79
|
|
79
80
|
context "when searching for 'foo'" do
|
80
|
-
subject { Asset.
|
81
|
+
subject { Asset.search_for("foo").map(&:title) }
|
81
82
|
its(:count) { should == 2 }
|
82
83
|
it { should include("Foo Bar: The Bazzening", "Undercover Foo") }
|
83
84
|
end
|
84
85
|
|
85
86
|
context "when searching for 'title:foo'" do
|
86
|
-
subject { Asset.
|
87
|
+
subject { Asset.search_for("title:foo").map(&:title) }
|
87
88
|
its(:count) { should == 2 }
|
88
89
|
it { should include("Foo Bar: The Bazzening", "Undercover Foo") }
|
89
90
|
end
|
90
91
|
|
91
92
|
context "when searching for 'description:bazzening'" do
|
92
|
-
subject { Asset.
|
93
|
+
subject { Asset.search_for("description:bazzening") }
|
93
94
|
its(:count) { should == 0 }
|
94
95
|
end
|
95
96
|
|
96
97
|
context "when searching for 'tag:foo.bar'" do
|
97
|
-
subject { Asset.
|
98
|
+
subject { Asset.search_for("tag:foo.bar").map(&:title) }
|
98
99
|
its(:count) { should == 1 }
|
99
100
|
its(:first) { should == "Foo Bar: The Bazzening" }
|
100
101
|
end
|
101
102
|
|
102
103
|
context "when searching for 'tag:movies cheese" do
|
103
|
-
subject { Asset.
|
104
|
+
subject { Asset.search_for("tag:movies cheese").map(&:title) }
|
104
105
|
its(:count) { should == 1 }
|
105
106
|
its(:first) { should == "Cheese of the Damned" }
|
106
107
|
end
|
108
|
+
|
109
|
+
context "when chaining a search for 'foo' off of :all" do
|
110
|
+
subject { Asset.all.search_for("foo").map(&:title) }
|
111
|
+
its(:count) { should == 2 }
|
112
|
+
it { should include("Foo Bar: The Bazzening", "Undercover Foo") }
|
113
|
+
end
|
114
|
+
|
115
|
+
context "when chaining a search for 'foo' off of a search for 'bar'" do
|
116
|
+
subject { Asset.search_for("bar").search_for("foo").map(&:title) }
|
117
|
+
its(:count) { should == 1 }
|
118
|
+
it { should == ["Foo Bar: The Bazzening"] }
|
119
|
+
end
|
120
|
+
|
121
|
+
context "when chaining a search for 'foo' off of a search for 'bar' using :and_for" do
|
122
|
+
subject { Asset.search_for("bar").and_for("foo").map(&:title) }
|
123
|
+
its(:count) { should == 1 }
|
124
|
+
it { should == ["Foo Bar: The Bazzening"] }
|
125
|
+
end
|
126
|
+
|
127
|
+
context "when chaining a search for 'foo' off of :arrange" do
|
128
|
+
subject { Asset.arrange(:title, :desc).search_for("foo").map(&:title) }
|
129
|
+
its(:count) { should == 2 }
|
130
|
+
it { should include("Undercover Foo", "Foo Bar: The Bazzening") }
|
131
|
+
end
|
107
132
|
end
|
108
133
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
describe SearchMagic::FullTextSearch do
|
2
|
+
context "when included in a document without searchables" do
|
3
|
+
subject { NoSearchables }
|
4
|
+
it { should respond_to(:inverse_searchables) }
|
5
|
+
its(:inverse_searchables) { should be_a(Array)}
|
6
|
+
its(:inverse_searchables) { should be_blank }
|
7
|
+
end
|
8
|
+
|
9
|
+
context "when included in a document which is not searched on from another document" do
|
10
|
+
subject { Person }
|
11
|
+
it { should respond_to(:inverse_searchables) }
|
12
|
+
its(:inverse_searchables) { should be_a(Array)}
|
13
|
+
its(:inverse_searchables) { should be_blank }
|
14
|
+
end
|
15
|
+
|
16
|
+
context "when included in a document with embedded searchables" do
|
17
|
+
subject { Address }
|
18
|
+
it { should respond_to(:inverse_searchables) }
|
19
|
+
its(:inverse_searchables) { should be_a(Array) }
|
20
|
+
its(:inverse_searchables) { should_not be_blank }
|
21
|
+
its(:inverse_searchables) { should include(:person) }
|
22
|
+
end
|
23
|
+
|
24
|
+
context "when included in a document with referenced searchables" do
|
25
|
+
subject { PartNumber }
|
26
|
+
it { should respond_to(:inverse_searchables) }
|
27
|
+
its(:inverse_searchables) { should be_a(Array) }
|
28
|
+
its(:inverse_searchables) { should_not be_blank }
|
29
|
+
its(:inverse_searchables) { should include(:parts) }
|
30
|
+
its(:inverse_searchables) { should_not include(:part_categories) }
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when included in a document which is searched on by another document, but which does not search on another" do
|
34
|
+
subject { PartCategory }
|
35
|
+
its(:inverse_searchables) { should include(:part_numbers) }
|
36
|
+
end
|
37
|
+
|
38
|
+
context "when an embedded model is updated" do
|
39
|
+
before(:each) do
|
40
|
+
Person.create(:name => "Joshua", :address => {:street => "123 Example St.", :city => "Nowhereland", :state => "CA", :post_code => 12345}, :phones => [{:country_code => 1, :number => "555-1234"}, {:country_code => 2, :number => "333-7890"}])
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "the top-level document should have its :searchable_values and :arrangeable_values updated" do
|
44
|
+
subject { Person.first }
|
45
|
+
before(:each) { subject.address.update_attributes!(:city => "Nowhereville") && subject.reload }
|
46
|
+
its(:searchable_values) { should include("address_city:nowhereville") }
|
47
|
+
its(:searchable_values) { should_not include("address_city:nowhereland") }
|
48
|
+
its(:arrangeable_values) { should include("address_city" => "Nowhereville") }
|
49
|
+
its(:arrangeable_values) { should_not include("address_city" => "Nowhereland") }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "when a referenced model is updated the current model's :searchable_values should change" do
|
54
|
+
before(:each) do
|
55
|
+
PartCategory.create(:name => "Table").tap do |category|
|
56
|
+
category.part_numbers.create(:value => "T11001").tap do |number|
|
57
|
+
number.parts.create(:status => "available", :serial => "T0411001")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "when a model is updated" do
|
63
|
+
subject { PartNumber.first }
|
64
|
+
before(:each) { subject.part_category.update_attributes!(:name => "Desk" ) && subject.reload }
|
65
|
+
its(:searchable_values) { should include("category_name:desk") }
|
66
|
+
its(:searchable_values) { should_not include("category_name:table") }
|
67
|
+
its(:arrangeable_values) { should include("category_name" => "Desk") }
|
68
|
+
its(:arrangeable_values) { should_not include("category_name" => "Table") }
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "when a deeply nested model is updated" do
|
72
|
+
subject { Part.first }
|
73
|
+
before(:each) { subject.part_number.part_category.update_attributes!(:name => "Desk" ) && subject.reload }
|
74
|
+
its(:searchable_values) { should include("category_name:desk") }
|
75
|
+
its(:searchable_values) { should_not include("category_name:table") }
|
76
|
+
its(:arrangeable_values) { should include("category_name" => "Desk") }
|
77
|
+
its(:arrangeable_values) { should_not include("category_name" => "Table") }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 7
|
9
|
+
version: 0.0.7
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Joshua Bowers
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2011-03-
|
17
|
+
date: 2011-03-28 00:00:00 -07:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -30,8 +30,8 @@ dependencies:
|
|
30
30
|
- 0
|
31
31
|
- 0
|
32
32
|
- rc
|
33
|
-
-
|
34
|
-
version: 2.0.0.rc.
|
33
|
+
- 8
|
34
|
+
version: 2.0.0.rc.8
|
35
35
|
type: :runtime
|
36
36
|
version_requirements: *id001
|
37
37
|
- !ruby/object:Gem::Dependency
|
@@ -108,6 +108,7 @@ files:
|
|
108
108
|
- spec/unit/search_magic/arrangements_spec.rb
|
109
109
|
- spec/unit/search_magic/associations_spec.rb
|
110
110
|
- spec/unit/search_magic/fields_spec.rb
|
111
|
+
- spec/unit/search_magic/model_updates_spec.rb
|
111
112
|
has_rdoc: true
|
112
113
|
homepage: http://github.com/joshuabowers/search_magic
|
113
114
|
licenses: []
|
@@ -156,3 +157,4 @@ test_files:
|
|
156
157
|
- spec/unit/search_magic/arrangements_spec.rb
|
157
158
|
- spec/unit/search_magic/associations_spec.rb
|
158
159
|
- spec/unit/search_magic/fields_spec.rb
|
160
|
+
- spec/unit/search_magic/model_updates_spec.rb
|