noodall-core 0.1.0

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