noodall-core 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,27 @@
1
+ module Noodall
2
+ class Permalink < Array
3
+ def initialize(*args)
4
+ if args.length > 1
5
+ super args
6
+ else
7
+ super args.first.to_s.split('/')
8
+ end
9
+ end
10
+
11
+ def to_s
12
+ self.join('/')
13
+ end
14
+
15
+ def inspect
16
+ "<Permalink #{self.to_s}>"
17
+ end
18
+
19
+ def self.to_mongo(value)
20
+ value.to_s
21
+ end
22
+
23
+ def self.from_mongo(value)
24
+ new( *value.to_s.split('/') )
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,123 @@
1
+ module Noodall
2
+ module Search
3
+ STOPWORDS = ["about", "above", "above", "across", "after", "afterwards", "again", "against", "all", "almost", "alone", "along", "already", "also", "although", "always", "among", "amongst", "amoungst", "amount", "and", "another", "any", "anyhow", "anyone", "anything", "anyway", "anywhere", "are", "around", "back", "became", "because", "become", "becomes", "becoming", "been", "before", "beforehand", "behind", "being", "below", "beside", "besides", "between", "beyond", "bill", "both", "bottom", "but", "call", "can", "cannot", "cant", "con", "could", "couldnt", "cry", "describe", "detail", "done", "down", "due", "during", "each", "eight", "either", "eleven", "else", "elsewhere", "empty", "enough", "etc", "even", "ever", "every", "everyone", "everything", "everywhere", "except", "few", "fifteen", "fify", "fill", "find", "fire", "first", "five", "for", "former", "formerly", "forty", "found", "four", "from", "front", "full", "further", "get", "give", "had", "has", "hasnt", "have", "hence", "her", "here", "hereafter", "hereby", "herein", "hereupon", "hers", "herself", "him", "himself", "his", "how", "however", "hundred", "inc", "indeed", "interest", "into", "its", "itself", "keep", "last", "latter", "latterly", "least", "less", "ltd", "made", "many", "may", "meanwhile", "might", "mill", "mine", "more", "moreover", "most", "mostly", "move", "much", "must", "myself", "name", "namely", "neither", "never", "nevertheless", "next", "nine", "nobody", "none", "noone", "nor", "not", "nothing", "now", "nowhere", "off", "often", "once", "one", "only", "onto", "other", "others", "otherwise", "our", "ours", "ourselves", "out", "over", "own", "part", "per", "perhaps", "please", "put", "rather", "same", "see", "seem", "seemed", "seeming", "seems", "serious", "several", "she", "should", "show", "side", "since", "sincere", "six", "sixty", "some", "somehow", "someone", "something", "sometime", "sometimes", "somewhere", "still", "such", "system", "take", "ten", "than", "that", "the", "the", "their", "them", "themselves", "then", "thence", "there", "thereafter", "thereby", "therefore", "therein", "thereupon", "these", "they", "thickv", "thin", "third", "this", "those", "though", "three", "through", "throughout", "thru", "thus", "together", "too", "top", "toward", "towards", "twelve", "twenty", "two", "under", "until", "upon", "very", "via", "was", "well", "were", "what", "whatever", "when", "whence", "whenever", "where", "whereafter", "whereas", "whereby", "wherein", "whereupon", "wherever", "whether", "which", "while", "whither", "who", "whoever", "whole", "whom", "whose", "why", "will", "with", "within", "without", "would", "yet", "you", "your", "yours", "yourself", "yourselves"]
4
+
5
+ def self.configure(model)
6
+ require 'lingua/stemmer'
7
+
8
+ model.class_eval do
9
+ key :_keywords, Array, :index => true
10
+ attr_accessor :relevance
11
+
12
+ before_save :_update_keywords
13
+ end
14
+ end
15
+
16
+ module ClassMethods
17
+ def searchable_keys(*keys)
18
+ @@searchable_keys ||= Set.new
19
+ @@searchable_keys += keys
20
+
21
+ @@searchable_keys
22
+ end
23
+
24
+ def language(lang = 'en')
25
+ @language ||= lang
26
+ end
27
+
28
+ def search(query, options = {})
29
+ if options[:per_page] || options[:per_page]
30
+ per_page = options.delete(:per_page)
31
+ page = options.delete(:page)
32
+ end
33
+ plucky_query = query(options.reverse_merge(
34
+ :order => 'relevance DESC'
35
+ ))
36
+ criteria = plucky_query.criteria.to_hash
37
+ options = plucky_query.options.to_hash
38
+
39
+ # Extract words from the query and clean up
40
+ words = query.downcase.split(/\W/) - STOPWORDS
41
+ words.reject!{|w| w.length < 3}
42
+ criteria.merge!( :_keywords => { :$all => words } )
43
+
44
+ # The Search result
45
+ search_result = collection.map_reduce(search_map(words), search_reduce, {:query => criteria, :finalize => search_finalize})
46
+
47
+ # Add value to sort options because model is stored in the value key
48
+ options[:sort].map! do |s,v|
49
+ ["value.#{s}",v]
50
+ end
51
+
52
+ search_query = Plucky::Query.new(search_result, options)
53
+
54
+ if per_page
55
+ results = search_query.paginate(:per_page => per_page, :page => page)
56
+ else
57
+ results = search_query.all
58
+ end
59
+ # clean up tmp collection
60
+ search_result.drop
61
+ #return results mappped to objects
62
+ results.tap do |docs|
63
+ docs.map! { |hash| load(hash['value']) }
64
+ end
65
+ end
66
+
67
+ def search_map(words)
68
+ #convert words into Regex OR
69
+ q = words.map do |k|
70
+ Regexp.escape(k)
71
+ end.join("|")
72
+ "function(){" +
73
+ "this.relevance = this._keywords.filter(" +
74
+ "function(z){" +
75
+ "return String(z).match(/(#{q})/);" +
76
+ "}).length;" +
77
+ "emit(this._id, this);" +
78
+ "}"
79
+ end
80
+
81
+ def search_reduce
82
+ "function( key , values ){return { model: values[0]};}"
83
+ end
84
+
85
+ def search_finalize
86
+ "function( key , values ){return values.model;}"
87
+ end
88
+ end
89
+
90
+ module InstanceMethods
91
+ protected
92
+ def _update_keywords
93
+ s = Lingua::Stemmer.new(:language => self.class.language)
94
+
95
+ self._keywords = []
96
+
97
+ self.class.searchable_keys.each do |search_key|
98
+ self._keywords += keywords_for_value(s, send(search_key)).compact
99
+ end
100
+ end
101
+
102
+ private
103
+ def keywords_for_value(stemmer, val)
104
+ if val.kind_of?(String)
105
+ words = val.downcase.split(/\W/) - STOPWORDS
106
+ words.reject!{|w| w.length < 3}
107
+ words.map do |word|
108
+ stem = stemmer.stem(word)
109
+ if stem != word
110
+ [stem, word]
111
+ else
112
+ word
113
+ end
114
+ end.flatten
115
+ elsif val.kind_of?(Array)
116
+ val.map { |e| keywords_for_value(stemmer, e) }.flatten
117
+ else
118
+ [val]
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,68 @@
1
+ module Noodall
2
+ module Tagging
3
+ def self.configure(model)
4
+ model.class_eval do
5
+ key :tags, Array, :index => true
6
+ end
7
+ end
8
+
9
+ module ClassMethods
10
+ def tag_cloud(options = {})
11
+ return [] if self.count == 0 # Don't bother if there is nothing in this collection
12
+ query = query(options.reverse_merge(
13
+ :order => 'value DESC'
14
+ ))
15
+ tags_map = collection.map_reduce(tag_cloud_map, tag_cloud_reduce, {:query => query.criteria.to_hash})
16
+ if tags_map.count > 0
17
+ tags = tags_map.find({}, query.options.to_hash ).to_a.collect{ |hash| Tag.new(hash['_id'], hash['value']) }
18
+ tags_map.drop # clean up tmp collection
19
+ tags
20
+ else
21
+ []
22
+ end
23
+ end
24
+
25
+ def tag_cloud_map
26
+ "function(){" +
27
+ "this.tags.forEach(" +
28
+ "function(z){" +
29
+ "emit( z , 1 );" +
30
+ "}" +
31
+ ")}"
32
+ end
33
+
34
+ def tag_cloud_reduce
35
+ "function( key , values ){" +
36
+ "var total = 0;" +
37
+ "for ( var i=0; i<values.length; i++ ){" +
38
+ "total += values[i];" +
39
+ "}" +
40
+ "return total;" +
41
+ "}"
42
+ end
43
+ end
44
+
45
+ module InstanceMethods
46
+ def tag_list=(string)
47
+ self.tags = string.to_s.split(',').map{ |t| t.strip.downcase }.reject(&:blank?).compact.uniq
48
+ end
49
+
50
+ def tag_list
51
+ tags.join(', ')
52
+ end
53
+
54
+ def related(options ={})
55
+ self.class.all(options.merge({:_id => {'$ne' => self._id}, :tags => /(#{self.tags.join('|')})/i}))
56
+ end
57
+ end
58
+
59
+ class Tag
60
+ attr_reader :name, :count
61
+
62
+ def initialize(name, count)
63
+ @name = name
64
+ @count = count.to_i
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,79 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{noodall-core}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Steve England"]
12
+ s.date = %q{2010-10-10}
13
+ s.description = %q{Core data objects for Noodall}
14
+ s.email = %q{steve@wearebeef.co.uk}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "Gemfile",
23
+ "Gemfile.lock",
24
+ "LICENSE",
25
+ "README.rdoc",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "lib/noodall-core.rb",
29
+ "lib/noodall/component.rb",
30
+ "lib/noodall/global_update_time.rb",
31
+ "lib/noodall/indexer.rb",
32
+ "lib/noodall/multi_parameter_attributes.rb",
33
+ "lib/noodall/node.rb",
34
+ "lib/noodall/permalink.rb",
35
+ "lib/noodall/search.rb",
36
+ "lib/noodall/tagging.rb",
37
+ "noodall-core.gemspec",
38
+ "spec/component_spec.rb",
39
+ "spec/factories/component.rb",
40
+ "spec/factories/node.rb",
41
+ "spec/node_spec.rb",
42
+ "spec/spec_helper.rb"
43
+ ]
44
+ s.homepage = %q{http://github.com/beef/noodall-core}
45
+ s.rdoc_options = ["--charset=UTF-8"]
46
+ s.require_paths = ["lib"]
47
+ s.rubygems_version = %q{1.3.7}
48
+ s.summary = %q{Core data objects for Noodall}
49
+ s.test_files = [
50
+ "spec/component_spec.rb",
51
+ "spec/factories/component.rb",
52
+ "spec/factories/node.rb",
53
+ "spec/node_spec.rb",
54
+ "spec/spec_helper.rb"
55
+ ]
56
+
57
+ if s.respond_to? :specification_version then
58
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
59
+ s.specification_version = 3
60
+
61
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
62
+ s.add_runtime_dependency(%q<mongo_mapper>, ["= 0.8.4"])
63
+ s.add_runtime_dependency(%q<ramdiv-mongo_mapper_acts_as_tree>, ["= 0.1.1"])
64
+ s.add_runtime_dependency(%q<canable>, ["= 0.1.1"])
65
+ s.add_runtime_dependency(%q<ruby-stemmer>, [">= 0"])
66
+ else
67
+ s.add_dependency(%q<mongo_mapper>, ["= 0.8.4"])
68
+ s.add_dependency(%q<ramdiv-mongo_mapper_acts_as_tree>, ["= 0.1.1"])
69
+ s.add_dependency(%q<canable>, ["= 0.1.1"])
70
+ s.add_dependency(%q<ruby-stemmer>, [">= 0"])
71
+ end
72
+ else
73
+ s.add_dependency(%q<mongo_mapper>, ["= 0.8.4"])
74
+ s.add_dependency(%q<ramdiv-mongo_mapper_acts_as_tree>, ["= 0.1.1"])
75
+ s.add_dependency(%q<canable>, ["= 0.1.1"])
76
+ s.add_dependency(%q<ruby-stemmer>, [">= 0"])
77
+ end
78
+ end
79
+
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ describe Noodall::Component do
4
+
5
+ it "should allow you to define the slots that are available" do
6
+ Noodall::Node.slots :wide, :small, :main
7
+
8
+ Noodall::Component.possible_slots.should == [:wide, :small, :main]
9
+
10
+ class Promo < Noodall::Component
11
+ allowed_positions :small, :wide, :main, :egg, :nog
12
+ end
13
+
14
+ Promo.positions.should have(3).things
15
+ end
16
+
17
+ it "should list components classes avaiable to a slot" do
18
+
19
+ class Promo < Noodall::Component
20
+ allowed_positions :small, :wide
21
+ end
22
+
23
+ Promo.positions.should have(2).things
24
+
25
+ Noodall::Component.positions_classes(:small).should include(Promo)
26
+ Noodall::Component.positions_classes(:main).should_not include(Promo)
27
+ end
28
+
29
+ it "should be validated by the node" do
30
+ class Promo < Noodall::Component
31
+ allowed_positions :small, :wide
32
+ end
33
+
34
+ node = Factory(:page)
35
+ node.main_slot_0 = Promo.new()
36
+
37
+ node.save
38
+
39
+ node.errors.should have(1).things
40
+ end
41
+
42
+ it "should know it's node" do
43
+ node = Factory(:page)
44
+ node.small_slot_0 = Factory(:content)
45
+
46
+ node.save!
47
+
48
+ node.small_slot_0.node.should == node
49
+ end
50
+
51
+ end
@@ -0,0 +1,16 @@
1
+ # A Dummy slot for tests
2
+ class Content < Noodall::Component
3
+ key :title, String
4
+ key :url, String
5
+ key :url_text, String
6
+
7
+ allowed_positions :small, :wide
8
+ end
9
+
10
+ # And a factory to build it
11
+ Factory.define :content do |component|
12
+ component.title { Faker::Lorem.words(4).join('') }
13
+ component.url { 'http://www.google.com' }
14
+ component.url_text { 'More' }
15
+ end
16
+
@@ -0,0 +1,15 @@
1
+ # Dummy node class for the tests
2
+ class Page < Noodall::Node
3
+ #sub_templates PageA, PageB, ArticlesList, LandingPage, EventsList
4
+ root_template!
5
+
6
+ main_slots 1
7
+ small_slots 4
8
+ wide_slots 3
9
+ end
10
+
11
+ # And a factory to build it
12
+ Factory.define :page do |node|
13
+ node.title { Faker::Lorem.words(3).join(' ') }
14
+ node.published_at { Time.now }
15
+ end
data/spec/node_spec.rb ADDED
@@ -0,0 +1,330 @@
1
+ require 'spec_helper'
2
+
3
+ describe Noodall::Node do
4
+ before(:each) do
5
+ @valid_attributes = {
6
+ :title => "My First Node"
7
+ }
8
+ end
9
+
10
+ it "should create a new instance given valid attributes" do
11
+ Noodall::Node.create!(@valid_attributes)
12
+ end
13
+
14
+ it "should be invalid if attributes are incorrect" do
15
+ c = Noodall::Node.create
16
+ c.valid?.should == false
17
+ end
18
+
19
+ it "should know it's root templates" do
20
+ class LandingPage < Noodall::Node
21
+ root_template!
22
+ end
23
+
24
+ Noodall::Node.template_classes.should include(LandingPage)
25
+ end
26
+
27
+ it "should know what class it is" do
28
+ page = Page.create!(@valid_attributes)
29
+ page.reload
30
+
31
+ page.class.should == Page
32
+
33
+ node = Noodall::Node.find(page.id)
34
+
35
+ node.class.should == Page
36
+ end
37
+
38
+ it "should be found by permalink" do
39
+ page = Factory(:page, :title => "My Page", :publish => true)
40
+ Noodall::Node.find_by_permalink('my-page').should == page
41
+ end
42
+
43
+ it "should allow you to set the number of slots" do
44
+ class NicePage < Noodall::Node
45
+ wide_slots 3
46
+ small_slots 5
47
+ end
48
+
49
+ NicePage.slots_count.should == 8
50
+ end
51
+
52
+ describe "within a tree" do
53
+ before(:each) do
54
+ @root = Page.create!(:title => "Root")
55
+
56
+ @child = Page.create!(:title => "Ickle Kid", :parent => @root)
57
+
58
+ @grandchild = Page.create!(:title => "Very Ickle Kid", :parent => @child)
59
+ end
60
+
61
+ it "should create permlink based on tree" do
62
+ @grandchild.permalink.to_s.should == "root/ickle-kid/very-ickle-kid"
63
+ @grandchild.reload
64
+ @grandchild.permalink.to_s.should == "root/ickle-kid/very-ickle-kid"
65
+ end
66
+
67
+ it "should be under the correct path once moved" do
68
+ grand_child_2 = Page.create!(:title => "Ickle Kid", :parent => @child)
69
+ root_2 = Page.create!(:title => "Root 2")
70
+
71
+ grand_child_2.parent = root_2
72
+ grand_child_2.save!
73
+
74
+ grand_child_2.path.should_not include(@child.id)
75
+ grand_child_2.path.should include(root_2.id)
76
+ end
77
+
78
+ it "should be under the correct path once it's parent is moved" do
79
+ grand_child_2 = Page.create!(:title => "Ickle Kid 2", :parent => @child)
80
+ root_2 = Page.create!(:title => "Root 2")
81
+ @child.parent = root_2
82
+ @child.save(:validate => false)
83
+
84
+ grand_child_2.reload
85
+
86
+ grand_child_2.path.should_not include(@root.id)
87
+ grand_child_2.path.should include(root_2.id)
88
+ end
89
+
90
+ it "should allow parent to be set to nill using a the string 'none' to make it easy to set via forms" do
91
+ @grandchild.update_attributes!(:parent => 'none')
92
+ @grandchild.reload
93
+ @grandchild.parent.should be(nil)
94
+ end
95
+
96
+ end
97
+
98
+ describe "Publish dates" do
99
+ before(:each) do
100
+ @node = Factory(:page, :published_at => 3.days.ago, :published_to => 3.days.since)
101
+ end
102
+
103
+ it "should be able to be published now" do
104
+ node = Factory(:page, :publish => true)
105
+ node.published?.should == true
106
+ end
107
+
108
+ it "should be able to be hidden now" do
109
+ @node.hide = true
110
+ @node.save
111
+ @node.published?.should == false
112
+ end
113
+
114
+ it "should determine if it is published" do
115
+ @node.published?.should == true
116
+ end
117
+
118
+ it "should know it it is to be published" do
119
+ @node.published_at = 1.hour.since
120
+ @node.save
121
+ @node.published?.should == false
122
+ @node.pending?.should == true
123
+ @node.expired?.should == false
124
+ end
125
+
126
+ it "should know it has been published" do
127
+ @node.published_to = 1.hour.ago
128
+ @node.save
129
+ @node.published?.should == false
130
+ @node.pending?.should == false
131
+ @node.expired?.should == true
132
+ end
133
+
134
+ it "should allow you to clear published to" do
135
+ @node.published_to = 1.hour.ago
136
+ @node.save
137
+ @node.published?.should == false
138
+
139
+ @node.published_to = nil
140
+ @node.save
141
+ @node.published?.should == true
142
+ end
143
+
144
+ it "should be findable by publish dates" do
145
+ nodes = Noodall::Node.all(:published_at => { :$lte => Time.now }, :published_to => { :$gte => Time.now })
146
+ nodes.should have(1).things
147
+
148
+ nodes = Noodall::Node.all(:published_at => { :$lte => 4.days.ago }, :published_to => { :$gte => 4.days.ago })
149
+ nodes.should have(0).things
150
+
151
+ nodes = Noodall::Node.all(:published_at => { :$lte => 4.days.since }, :published_to => { :$gte => 4.days.since })
152
+ nodes.should have(0).things
153
+
154
+ end
155
+
156
+ end
157
+
158
+ it "should update the global update timestamp" do
159
+ Noodall::GlobalUpdateTime::Stamp.should_receive(:update!)
160
+ p = Factory(:page)
161
+ end
162
+
163
+ it "should be able to list all slots" do
164
+ ObjectSpace.each_object(Class) do |c|
165
+ next unless c.ancestors.include?(Noodall::Node) and (c != Noodall::Node)
166
+ c.new.slots.should be_instance_of(Array)
167
+ end
168
+
169
+ node = Factory(:page)
170
+ node.small_slot_0 = Content.new(:body => "Some text")
171
+ node.small_slot_1 = Content.new(:body => "Some more text")
172
+
173
+ node.save!
174
+
175
+ node.slots.should have(2).things
176
+
177
+ node.slots.first.body.should == "Some text"
178
+ node.slots.last.body.should == "Some more text"
179
+ end
180
+
181
+ it "should use a tree structure" do
182
+ root = Page.create!(@valid_attributes)
183
+
184
+ child = Page.create!(:title => "Ickle Kid", :parent => root)
185
+
186
+ grandchild = Page.create!(:title => "Very Ickle Kid", :parent => child)
187
+
188
+ child.parent.should == root
189
+
190
+ grandchild.root.should == root
191
+
192
+ root.children.first.should == child
193
+
194
+ sec_child = Page.create!(:title => "Ickle Kid 2", :parent => root)
195
+
196
+ child.siblings.first.should == sec_child
197
+
198
+ root.children.last.should == sec_child
199
+
200
+ sec_child.position = 0
201
+ sec_child.save
202
+
203
+ root.children.first.should == sec_child
204
+
205
+ child.reload
206
+
207
+ child.position.should == 1
208
+
209
+ third_child = Page.create!(:title => "Ickle Kid 3", :parent => root, :position => 0)
210
+
211
+ root.children.first.should == third_child
212
+
213
+ sec_child.reload
214
+
215
+ sec_child.position.should == 1
216
+
217
+ child.reload
218
+
219
+ child.position.should == 2
220
+
221
+ last_child = Page.create!(:title => "Ickle Kid 4", :parent => root, :position => 33)
222
+
223
+ last_child.reload
224
+
225
+ root.children.should have(4).things
226
+
227
+
228
+ root.children.last.should == last_child
229
+
230
+ last_child.position.should == 3
231
+ end
232
+
233
+ it "shold allow groups to be set by strings for easy form access" do
234
+ node = Factory(:page)
235
+ node.destroyable_groups_list = 'Webbies, Dudes,Things, Dudes, ,'
236
+ node.destroyable_groups.should == ['Webbies', 'Dudes', 'Things']
237
+
238
+ node = Factory(:page, :destroyable_groups_list => 'Webbies, Dudes,Things, Dudes, ,')
239
+ node.destroyable_groups.should == ['Webbies', 'Dudes', 'Things']
240
+ end
241
+
242
+ it "should restrict user accces by groups" do
243
+ # Stub a simple user model
244
+ class ::User
245
+ include MongoMapper::Document
246
+ include Canable::Cans
247
+
248
+ key :name, String
249
+ key :groups, Array
250
+ key :admin, Boolean
251
+ end
252
+
253
+ john = User.create!(:name => 'John', :groups => ['Webbies','Blokes'])
254
+ steve = User.create!(:name => 'Steve', :groups => ['Dudes'])
255
+
256
+ ruby = Factory(:page, :updatable_groups => ['Dudes'], :destroyable_groups => ['Webbies', 'Dudes'], :publishable_groups => ['Dudes'] )
257
+
258
+ ruby.all_groups.should have(2).things
259
+
260
+ john.can_create?(ruby).should == true
261
+ steve.can_create?(ruby).should == true
262
+
263
+ ruby.creator = john
264
+ ruby.save
265
+
266
+ john.can_view?(ruby).should == true
267
+ steve.can_view?(ruby).should == true
268
+
269
+ john.can_update?(ruby).should == false
270
+ steve.can_update?(ruby).should == true
271
+
272
+ john.can_destroy?(ruby).should == true
273
+ steve.can_destroy?(ruby).should == true
274
+ end
275
+
276
+ it "should be searchable" do
277
+ 3.times do |i|
278
+ Factory(:page, :title => "My Searchable Page #{i}")
279
+ end
280
+
281
+ top_hit = Factory(:page, :title => "My Extra Searchable Page", :description => "My Extra Searchable Page")
282
+
283
+ 3.times do |i|
284
+ Factory(:page, :title => "My Unfit Page #{i}")
285
+ end
286
+
287
+ results = Page.search("Searchable")
288
+
289
+ results.should have(4).things
290
+
291
+ results.first.should == top_hit
292
+
293
+ results = Page.search("Searchable", :per_page => 2)
294
+
295
+ results.should have(2).things
296
+ results.total_pages.should == 2
297
+
298
+ results.first.should == top_hit
299
+
300
+ results = Page.search("supercalifragilistic")
301
+
302
+ results.should have(0).things
303
+ end
304
+
305
+ it "should return related" do
306
+ Factory(:page, :title => "My Page 1", :tag_list => 'one,two,three')
307
+ Factory(:page, :title => "My Page 2", :tag_list => 'two,three,four')
308
+ Factory(:page, :title => "My Page 3", :tag_list => 'three,four,five')
309
+ ref = Factory(:page, :title => "My Page 4", :tag_list => 'five,nine')
310
+
311
+ ref.related.should have(1).things
312
+
313
+ Factory(:page, :title => "My Page 5", :tag_list => 'nine')
314
+
315
+ ref.related.should have(2).things
316
+ end
317
+
318
+ it "should know who can be a parent" do
319
+ class LandindPage < Noodall::Node
320
+ sub_templates Page
321
+ end
322
+ class ArticlesList < Noodall::Node
323
+ sub_templates LandindPage
324
+ end
325
+
326
+ Page.parent_classes.should include(LandingPage)
327
+ Page.parent_classes.should_not include(ArticlesList)
328
+ end
329
+
330
+ end