nagybence-sluggable_finder 2.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2008-11-03
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/Manifest.txt ADDED
@@ -0,0 +1,16 @@
1
+ History.txt
2
+ Manifest.txt
3
+ PostInstall.txt
4
+ README.markdown
5
+ Rakefile
6
+ lib/sluggable_finder.rb
7
+ lib/sluggable_finder/finder.rb
8
+ lib/sluggable_finder/orm.rb
9
+ script/console
10
+ script/destroy
11
+ script/generate
12
+ spec/sluggable_finder_spec.rb
13
+ spec/spec.opts
14
+ spec/spec_helper.rb
15
+ tasks/rspec.rake
16
+ tasks/db.rake
data/PostInstall.txt ADDED
@@ -0,0 +1,4 @@
1
+
2
+ For more information on sluggable_finder, see http://github.com/ismasan/sluggable-finder or the README file
3
+
4
+ If you want to contribute don't forget to add your specs!
data/README.markdown ADDED
@@ -0,0 +1,139 @@
1
+ ## sluggable_finder
2
+
3
+ Ismael Celis
4
+
5
+ http://github.com/ismasan/sluggable_finder
6
+
7
+ This gem is originally based on http://github.com/smtlaissezfaire/acts_as_sluggable/tree/master/lib%2Facts_as_slugable.rb
8
+
9
+ ## DESCRIPTION:
10
+
11
+ This is a variation of acts_as_sluggable, permalink_fu and acts_as_permalink.
12
+ This plugin allows models to generate a unique "slug" (url-enabled name) from any regular attribute.
13
+ Sluggable models can have a scope parameter so slugs are unique relative to a parent model.
14
+
15
+ The plugin intercepts ActiveRecord::Base.find to look into the slug field if passed a single string as an argument. It works as normal if you pass an integer or more finder parameters.
16
+
17
+ The plugin modifies to_param so it's transparent to link_to and url_for view helpers
18
+
19
+ ## FEATURES/PROBLEMS:
20
+
21
+ Slugs are created when a model is created. Subsequent changes to the source field will not modify the slug unless you specifically change the value of the slug field. This is because permalinks should never change.
22
+
23
+ Complete specs. To test, make sure you create an empty SQLite database file in spec/db/test.db
24
+
25
+ Then run the following to load the test schema:
26
+
27
+ rake db:create
28
+
29
+ ## SYNOPSIS:
30
+
31
+ ### Models
32
+ class Category < ActiveRecord::Base
33
+ has_many :posts
34
+ sluggable_finder :title #slugifies the :title field into the :slug field
35
+ end
36
+
37
+ class Post < ActiveRecord::Base
38
+ belongs_to :category
39
+ has_many :comments
40
+ sluggable_finder :title, :scope => :category_id #Post slugs are unique to the parent category
41
+ end
42
+
43
+ class Comment < ActiveRecord::Base
44
+ belongs_to :post
45
+ sluggable_finder :get_slug, :to => :permalink #creates slug from custom attribute and stores it in "permalink" field
46
+
47
+ def get_slug #we define the custom attribute
48
+ "#{post.id}-#{Time.now}"
49
+ end
50
+ end
51
+
52
+ # Provide a list or reserved slugs you don't want available as permalinks
53
+ #
54
+ sluggable_finder :title, :reserved_slugs => %w(admin settings users)
55
+
56
+ ### Controllers
57
+
58
+ You can do Model.find(slug) just how you would with a single numerical ID. It will also raise a RecordNotFound exception so you can handle that in your application controller.
59
+ The idea is that you keep your controller actions clean and handle Not Found errors elsewhere. You can still use Model.find the regular way.
60
+
61
+ class PostsController < ApplicationController
62
+ # params[:id] is a string, URL-encoded slug
63
+ def show
64
+ @post = Post.find( params[:id] ) #raises ActiveRecord::RecordNotFound if not found
65
+ end
66
+ end
67
+
68
+ ### Merb and custom NotFound exception
69
+
70
+ In merb it would be nice to raise a NotFound exception instead of ActiveRecord's RecordNotFoundm so we don't need to clutter our controller with exception-handling code and we let the framework handle them.
71
+
72
+ SluggableFinder.not_found_exception = Merb::ControllerExceptions::NotFound
73
+
74
+ You can configure this to raise any exception you want.
75
+
76
+ class CustomException < StandardError; end
77
+
78
+ SluggableFinder.not_found_exception = CustomException
79
+
80
+ ### Links
81
+
82
+ Link generation remains the same, because the plugin overwrites your model's to_param method
83
+
84
+ <%= link_to h(@post.title), @post %> # => <a href="/posts/hello-world">Hello world</a>
85
+
86
+ ## REQUIREMENTS:
87
+
88
+ ActiveRecord, ActiveSupport
89
+
90
+ ## INSTALL:
91
+
92
+ If you haven't yet, add github.com to your gem sources (you only need to do that once):
93
+
94
+ gem sources -a http://gems.github.com
95
+
96
+ Now you can install the normal way:
97
+
98
+ sudo gem install ismasan-sluggable_finder
99
+
100
+ Then, in your Rails app's environment:
101
+
102
+ config.gem "ismasan-sluggable_finder", :lib => 'sluggable_finder'
103
+
104
+ If you wan to unpack the gem to you app's "vendor" directory:
105
+
106
+ rake gems:unpack
107
+
108
+ ## TODO:
109
+
110
+ *Refactor. It works but I hate the code.
111
+
112
+ *Avoid including in ALL models. At the moment we need to extend associations for all models. Not sure how to only extend associations for models that use the plugin.
113
+
114
+ *Better testing for scoped collections. @user.posts.find 'blah' should only look in posts for @user without needing to add :scope
115
+
116
+ ## LICENSE:
117
+
118
+ (The MIT License)
119
+
120
+ Copyright (c) 2008 Ismael Celis
121
+
122
+ Permission is hereby granted, free of charge, to any person obtaining
123
+ a copy of this software and associated documentation files (the
124
+ 'Software'), to deal in the Software without restriction, including
125
+ without limitation the rights to use, copy, modify, merge, publish,
126
+ distribute, sublicense, and/or sell copies of the Software, and to
127
+ permit persons to whom the Software is furnished to do so, subject to
128
+ the following conditions:
129
+
130
+ The above copyright notice and this permission notice shall be
131
+ included in all copies or substantial portions of the Software.
132
+
133
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
134
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
135
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
136
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
137
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
138
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
139
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ %w[rubygems rake rake/clean fileutils newgem rubigen].each { |f| require f }
2
+ require File.dirname(__FILE__) + '/lib/sluggable_finder'
3
+
4
+ # Generate all the Rake tasks
5
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
6
+ $hoe = Hoe.new('sluggable_finder', SluggableFinder::VERSION) do |p|
7
+ p.developer('Ismael Celis', 'ismaelct@gmail.com')
8
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
9
+ p.post_install_message = 'PostInstall.txt' # TODO remove if post-install message not required
10
+ p.rubyforge_name = p.name # TODO this is default value
11
+ # p.extra_deps = [
12
+ # ['activesupport','>= 2.0.2'],
13
+ # ]
14
+ p.extra_dev_deps = [
15
+ ['newgem', ">= #{::Newgem::VERSION}"]
16
+ ]
17
+
18
+ p.clean_globs |= %w[**/.DS_Store tmp *.log]
19
+ path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
20
+ p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
21
+ p.rsync_args = '-av --delete --ignore-errors'
22
+ end
23
+
24
+ require 'newgem/tasks' # load /tasks/*.rake
25
+ Dir['tasks/**/*.rake'].each { |t| load t }
26
+
27
+ # TODO - want other tests/tasks run by default? Add them to the list
28
+ # task :default => [:spec, :features]
@@ -0,0 +1,56 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ module SluggableFinder
5
+ VERSION = '2.0.5'
6
+
7
+ @@not_found_exception = nil
8
+
9
+ def self.not_found_exception=(ex)
10
+ @@not_found_exception = ex
11
+ end
12
+
13
+ def self.not_found_exception
14
+ @@not_found_exception || ActiveRecord::RecordNotFound
15
+ end
16
+
17
+ class << self
18
+
19
+ def enable_activerecord
20
+ ActiveRecord::Base.extend SluggableFinder::Orm::ClassMethods
21
+ # support for associations
22
+ a = ActiveRecord::Associations
23
+ returning([ a::AssociationCollection ]) { |classes|
24
+ # detect http://dev.rubyonrails.org/changeset/9230
25
+ unless a::HasManyThroughAssociation.superclass == a::HasManyAssociation
26
+ classes << a::HasManyThroughAssociation
27
+ end
28
+ }.each do |klass|
29
+ klass.send :include, SluggableFinder::Finder
30
+ klass.send :include, SluggableFinder::AssociationProxyFinder
31
+ klass.alias_method_chain :find, :slug
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+
38
+ def self.encode(str)
39
+ if defined?(ActiveSupport::Inflector.parameterize)
40
+ ActiveSupport::Inflector.parameterize(str).to_s
41
+ else
42
+ ActiveSupport::Multibyte::Handlers::UTF8Handler.
43
+ normalize(str,:d).split(//u).reject { |e| e.length > 1 }.join.strip.gsub(/[^a-z0-9]+/i, '-').downcase
44
+ end
45
+ end
46
+
47
+ end
48
+
49
+ require 'rubygems'
50
+ require 'active_record'
51
+
52
+ Dir.glob(File.dirname(__FILE__)+'/sluggable_finder/*.rb').each do |file|
53
+ require file
54
+ end
55
+
56
+ SluggableFinder.enable_activerecord if (defined?(ActiveRecord) && !ActiveRecord::Base.respond_to?(:sluggable_finder))
@@ -0,0 +1,37 @@
1
+ module SluggableFinder
2
+ # This module is included by the base class as well as AR asociation collections
3
+ #
4
+ module Finder
5
+ def find_sluggable(opts,*args)
6
+ key = args.first
7
+ if key.is_a?(String) and not (opts[:allow_numerical] == false and key =~ /\A\d+\Z/)
8
+ options = {:conditions => ["#{opts[:to]} = ?", key]}
9
+ error = "There is no #{opts[:sluggable_type]} with #{opts[:to]} '#{key}'"
10
+ with_scope(:find => options) do
11
+ find_without_slug(:first) or
12
+ raise SluggableFinder.not_found_exception.new(error)
13
+ end
14
+ else
15
+ find_without_slug(*args)
16
+ end
17
+ end
18
+ end
19
+
20
+ module BaseFinder
21
+
22
+ def find_with_slug(*args)
23
+ return find_without_slug(*args) unless respond_to?(:sluggable_finder_options)
24
+ options = sluggable_finder_options
25
+ find_sluggable(options,*args)
26
+ end
27
+ end
28
+
29
+ module AssociationProxyFinder
30
+ def find_with_slug(*args)
31
+ return find_without_slug(*args) unless @reflection.klass.respond_to?(:sluggable_finder_options)
32
+ options = @reflection.klass.sluggable_finder_options
33
+ find_sluggable(options,*args)
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,109 @@
1
+ module SluggableFinder
2
+ module Orm
3
+
4
+ module ClassMethods
5
+
6
+ def sluggable_finder(field = :title, options = {})
7
+ return if self.included_modules.include?(SluggableFinder::Orm::InstanceMethods)
8
+ extend SluggableFinder::Finder
9
+ extend SluggableFinder::BaseFinder
10
+ include SluggableFinder::Orm::InstanceMethods
11
+
12
+ class << self
13
+ alias_method_chain :find, :slug
14
+ end
15
+
16
+ write_inheritable_attribute(:sluggable_finder_options, {
17
+ :sluggable_type => ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s,
18
+ :from => field,
19
+ :scope => nil,
20
+ :to => :slug,
21
+ :reserved_slugs => [],
22
+ :allow_numerical => false
23
+ }.merge( options ))
24
+ class_inheritable_reader :sluggable_finder_options
25
+
26
+ if sluggable_finder_options[:scope]
27
+ scope_condition_method = %(
28
+ def scope_condition
29
+ "#{sluggable_finder_options[:scope].to_s} = \#{#{sluggable_finder_options[:scope].to_s}}"
30
+ end
31
+ )
32
+ else
33
+ scope_condition_method = %(
34
+ def scope_condition
35
+ '1 = 1'
36
+ end
37
+ )
38
+ end
39
+
40
+ class_eval <<-EOV
41
+
42
+ def slugable_class
43
+ ::#{self.name}
44
+ end
45
+
46
+ def source_column
47
+ "#{sluggable_finder_options[:from]}"
48
+ end
49
+
50
+ def destination_column
51
+ "#{sluggable_finder_options[:to]}"
52
+ end
53
+
54
+ def to_param
55
+ self.#{sluggable_finder_options[:to]}
56
+ end
57
+
58
+ #{scope_condition_method}
59
+
60
+ after_validation :set_slug
61
+ EOV
62
+
63
+ end
64
+
65
+ end
66
+
67
+ module InstanceMethods
68
+
69
+
70
+ def set_slug
71
+ s = self.create_sluggable_slug
72
+ write_attribute(destination_column, s)
73
+ end
74
+
75
+ def create_sluggable_slug
76
+ suffix = ''
77
+ begin
78
+ proposed_slug = if self.send(destination_column.to_sym).blank?
79
+ SluggableFinder.encode self.send(source_column.to_sym)
80
+ else
81
+ SluggableFinder.encode self.send(destination_column.to_sym)
82
+ end
83
+ rescue Exception => e
84
+ raise e
85
+ end
86
+ cond = if new_record?
87
+ ''
88
+ else
89
+ "id != #{id} AND "
90
+ end
91
+ slugable_class.transaction do
92
+ #case insensitive
93
+ existing = slugable_class.find(:first, :conditions => ["#{cond}#{destination_column} LIKE ? and #{scope_condition}", proposed_slug + suffix])
94
+ while existing != nil or sluggable_finder_options[:reserved_slugs].include?(proposed_slug + suffix)
95
+ if suffix.empty?
96
+ suffix = "-2"
97
+ else
98
+ suffix.succ!
99
+ end
100
+ existing = slugable_class.find(:first, :conditions => ["#{cond}#{destination_column} = ? and #{scope_condition}", proposed_slug + suffix])
101
+ end
102
+ end # end of transaction
103
+ proposed_slug + suffix
104
+ end
105
+
106
+ end
107
+
108
+ end
109
+ end
data/script/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7
+ # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/sluggable_finder.rb'}"
9
+ puts "Loading sluggable_finder gem"
10
+ exec "#{irb} #{libs} --simple-prompt"
data/script/destroy ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
data/script/generate ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
@@ -0,0 +1,253 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ ActiveRecord::Base.establish_connection(
4
+ :adapter=>'sqlite3',
5
+ :dbfile=> File.join(File.dirname(__FILE__),'db','test.db')
6
+ )
7
+
8
+ LOGGER = Logger.new(File.dirname(__FILE__)+'/log/test.log')
9
+ ActiveRecord::Base.logger = LOGGER
10
+
11
+ # A test Model according to test schema in db/test.db
12
+ #
13
+ class Item < ActiveRecord::Base
14
+ named_scope :published, :conditions => {:published => true}
15
+ end
16
+
17
+ # No sluggable finder, should be unnaffected
18
+ #
19
+ class NoFinder < Item
20
+
21
+ end
22
+ # Simple slug
23
+ #
24
+ class SimpleItem < Item
25
+ sluggable_finder :title, :reserved_slugs => ['admin','settings'] # defaults :to => :slug
26
+ end
27
+
28
+ # Slug from virtual attribute
29
+ #
30
+ class VirtualItem < Item
31
+ sluggable_finder :some_method
32
+
33
+ def some_method
34
+ "#{self.class.name} #{title}"
35
+ end
36
+
37
+ end
38
+
39
+ # This one saves slug into 'permalink' field
40
+ #
41
+ class PermalinkItem < Item
42
+ sluggable_finder :title, :to => :permalink
43
+ end
44
+
45
+ # A top level object to test scoped slugs
46
+ #
47
+ class Category < ActiveRecord::Base
48
+ has_many :scoped_items
49
+ has_many :simple_items
50
+ end
51
+
52
+ class ScopedItem < Item
53
+ belongs_to :category
54
+ sluggable_finder :title, :scope => :category_id
55
+ end
56
+
57
+ describe SimpleItem, 'encoding permalinks' do
58
+ before(:each) do
59
+ Item.delete_all
60
+ @item = SimpleItem.create!(:title => 'Hello World')
61
+ @item2 = SimpleItem.create(:title => 'Hello World')
62
+ @item3 = SimpleItem.create(:title => 'Admin')
63
+ end
64
+
65
+ it "should connect to test sqlite db" do
66
+ Item.count.should == 3
67
+ end
68
+
69
+ it "should create unique slugs" do
70
+ @item.slug.should == 'hello-world'
71
+ @item2.slug.should == 'hello-world-2'
72
+ end
73
+
74
+ it "should define to_param to return slug" do
75
+ @item.to_param.should == 'hello-world'
76
+ end
77
+
78
+ it "should raise ActiveRecord::RecordNotFound" do
79
+ SimpleItem.create!(:title => 'Hello World')
80
+ lambda {
81
+ SimpleItem.find 'non-existing-slug'
82
+ }.should raise_error(ActiveRecord::RecordNotFound)
83
+ end
84
+
85
+ it "should find normally by ID" do
86
+ SimpleItem.find(@item.id).should == @item
87
+ end
88
+
89
+ it "should by ID even if ID is string" do
90
+ SimpleItem.find(@item.id.to_s).should == @item
91
+ end
92
+
93
+ it "should not create reserved slug" do
94
+ lambda {
95
+ SimpleItem.find 'admin'
96
+ }.should raise_error(ActiveRecord::RecordNotFound)
97
+ SimpleItem.find('admin-2').to_param.should == @item3.to_param
98
+ end
99
+
100
+ it "should keep original slugs" do
101
+ @item.title = 'some other title'
102
+ @item.save
103
+ @item.to_param.should == 'hello-world'
104
+ end
105
+ end
106
+
107
+ # Raising custom not found exceptions allows us to use this with merb's NotFound exception
108
+ # or any framework
109
+ describe "with custom exception" do
110
+ it "should raise custom exception if configured that way" do
111
+ class CustomException < StandardError;end
112
+
113
+ SluggableFinder.not_found_exception = CustomException
114
+ lambda {
115
+ SimpleItem.find 'non-existing-slug'
116
+ }.should raise_error(CustomException)
117
+ end
118
+
119
+ after(:all) do
120
+ SluggableFinder.not_found_exception = ActiveRecord::RecordNotFound
121
+ end
122
+ end
123
+
124
+ describe SimpleItem, "with non-english characters" do
125
+ before(:each) do
126
+ @item = SimpleItem.create!(:title => "Un ñandú super ñoño I've seen")
127
+ end
128
+
129
+ it "should turn them to english characters" do
130
+ @item.to_param.should == "un-nandu-super-nono-i-ve-seen"
131
+ end
132
+ end
133
+
134
+ describe VirtualItem, 'using virtual fields as permalink source' do
135
+ before(:each) do
136
+ Item.delete_all
137
+ @item = VirtualItem.create!(:title => 'prefixed title')
138
+ end
139
+
140
+ it "should generate slug from a virtual attribute" do
141
+ @item.to_param.should == 'virtualitem-prefixed-title'
142
+ end
143
+
144
+ it "should find by slug" do
145
+ VirtualItem.find('virtualitem-prefixed-title').to_param.should == @item.to_param
146
+ end
147
+ end
148
+
149
+ describe PermalinkItem,'writing to custom field' do
150
+ before(:each) do
151
+ Item.delete_all
152
+ @item = PermalinkItem.create! :title => 'Hello World'
153
+ end
154
+
155
+ it "should create slug in custom field if provided" do
156
+
157
+ @item.permalink.should == 'hello-world'
158
+ @item.slug.should == nil
159
+ end
160
+ end
161
+
162
+ describe SimpleItem,"scoping finder" do
163
+ before(:each) do
164
+ Item.delete_all
165
+ @category1 = Category.create!(:name => 'Category one')
166
+ @category2 = Category.create!(:name => 'Category two')
167
+ # Lets create 3 items with the same title, two of them in the same category
168
+ @item1 = @category1.simple_items.create!(:title => '1 in 1')
169
+ @item2 = @category1.simple_items.create!(:title => '2 in 1')
170
+ @item3 = @category2.simple_items.create!(:title => '1 in 2')
171
+ end
172
+
173
+ it "should find in scope" do
174
+ @category1.simple_items.find('1-in-1').should == @item1
175
+ end
176
+
177
+ it "should not find out of scope" do
178
+ lambda{
179
+ @category2.simple_items.find('1-in-1')
180
+ }.should raise_error(ActiveRecord::RecordNotFound)
181
+ end
182
+ end
183
+
184
+ describe ScopedItem,'scoped to parent object' do
185
+ before(:each) do
186
+ Item.delete_all
187
+ @category1 = Category.create!(:name => 'Category one')
188
+ @category2 = Category.create!(:name => 'Category two')
189
+ # Lets create 3 items with the same title, two of them in the same category
190
+ @item1 = @category1.scoped_items.create!(:title => 'A scoped item',:published => true)
191
+ @item2 = @category1.scoped_items.create!(:title => 'A scoped item', :published => false)
192
+ @item3 = @category2.scoped_items.create!(:title => 'A scoped item')
193
+ end
194
+
195
+ it "should scope slugs to parent items" do
196
+ @item1.to_param.should == 'a-scoped-item'
197
+ @item2.to_param.should == 'a-scoped-item-2' # because this slug is not available for this category
198
+ @item3.to_param.should == 'a-scoped-item'
199
+ end
200
+
201
+ it "should include sluggable methods in collections" do
202
+ @category1.scoped_items.respond_to?(:find_with_slug).should == true
203
+ end
204
+
205
+ it "should find by scoped slug" do
206
+ item1 = @category1.scoped_items.find('a-scoped-item')
207
+ item1.to_param.should == @item1.to_param
208
+ end
209
+
210
+ it "should find published one (named scope)" do
211
+ @category1.scoped_items.published.find('a-scoped-item').to_param.should == @item1.to_param
212
+ end
213
+
214
+ it "should not find unpublished one (named scope)" do
215
+ lambda{
216
+ @category1.scoped_items.published.find('a-scoped-item-2')
217
+ }.should raise_error(ActiveRecord::RecordNotFound)
218
+ end
219
+ end
220
+
221
+ describe SimpleItem, 'with AR named scopes' do
222
+ before(:each) do
223
+ Item.delete_all
224
+ @published_one = SimpleItem.create! :title => 'published 1',:published => true
225
+ @published_two = SimpleItem.create! :title => 'published 2',:published => true
226
+ @unpublished = SimpleItem.create! :title => 'not published',:published => false
227
+ end
228
+
229
+ it "should find published ones" do
230
+ SimpleItem.published.find('published-1').to_param.should == @published_one.to_param
231
+ SimpleItem.published.find('published-2').to_param.should == @published_two.to_param
232
+ end
233
+
234
+ it "should not find unpublished ones" do
235
+ lambda {
236
+ SimpleItem.published.find('not-published')
237
+ }.should raise_error(ActiveRecord::RecordNotFound)
238
+ end
239
+ end
240
+
241
+ describe NoFinder, "with no finder" do
242
+ before(:each) do
243
+ NoFinder.delete_all
244
+ @item = NoFinder.create(:title => 'no finder here')
245
+ @string_id = "#{@item.id}-some-string"
246
+ end
247
+
248
+ it "should use find normally" do
249
+ NoFinder.find(:first).should == @item
250
+ NoFinder.find(@item.id).should == @item
251
+ NoFinder.find(@string_id).should == @item
252
+ end
253
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --colour
@@ -0,0 +1,10 @@
1
+ begin
2
+ require 'spec'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ gem 'rspec'
6
+ require 'spec'
7
+ end
8
+
9
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
10
+ require 'sluggable_finder'
data/tasks/db.rake ADDED
@@ -0,0 +1,42 @@
1
+ db = {
2
+ :adapter=>'sqlite3',
3
+ :dbfile=> File.join(File.dirname(__FILE__),'..','spec','db','test.db')
4
+ }
5
+ ActiveRecord::Base.establish_connection( db )
6
+ # define a migration
7
+ class TestSchema < ActiveRecord::Migration
8
+ def self.up
9
+ create_table :categories do |t|
10
+ t.string :name
11
+ t.timestamps
12
+ end
13
+ create_table :items do |t|
14
+ t.string :title
15
+ t.string :slug
16
+ t.string :permalink
17
+ t.boolean :published
18
+ t.integer :category_id
19
+ t.timestamps
20
+ end
21
+ end
22
+
23
+ def self.down
24
+ drop_table :items
25
+ end
26
+ end
27
+
28
+
29
+ namespace :db do
30
+ desc "Create test schema"
31
+ task :create do
32
+ # run the migration
33
+ File.unlink(db[:dbfile]) if File.exists?(db[:dbfile])
34
+ ActiveRecord::Base.connection
35
+ TestSchema.migrate(:up)
36
+ end
37
+
38
+ desc "Destroy test schema"
39
+ task :destroy do
40
+ TestSchema.migrate(:down)
41
+ end
42
+ end
data/tasks/rspec.rake ADDED
@@ -0,0 +1,21 @@
1
+ begin
2
+ require 'spec'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ require 'spec'
6
+ end
7
+ begin
8
+ require 'spec/rake/spectask'
9
+ rescue LoadError
10
+ puts <<-EOS
11
+ To use rspec for testing you must install rspec gem:
12
+ gem install rspec
13
+ EOS
14
+ exit(0)
15
+ end
16
+
17
+ desc "Run the specs under spec/models"
18
+ Spec::Rake::SpecTask.new do |t|
19
+ t.spec_opts = ['--options', "spec/spec.opts"]
20
+ t.spec_files = FileList['spec/**/*_spec.rb']
21
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nagybence-sluggable_finder
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.6
5
+ platform: ruby
6
+ authors:
7
+ - Ismael Celis
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-11-16 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: newgem
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.0.5
23
+ version:
24
+ - !ruby/object:Gem::Dependency
25
+ name: hoe
26
+ version_requirement:
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 1.8.0
32
+ version:
33
+ description:
34
+ email:
35
+ - ismaelct@gmail.com
36
+ executables: []
37
+
38
+ extensions: []
39
+
40
+ extra_rdoc_files:
41
+ - History.txt
42
+ - Manifest.txt
43
+ - PostInstall.txt
44
+ files:
45
+ - History.txt
46
+ - Manifest.txt
47
+ - PostInstall.txt
48
+ - README.markdown
49
+ - Rakefile
50
+ - lib/sluggable_finder.rb
51
+ - lib/sluggable_finder/finder.rb
52
+ - lib/sluggable_finder/orm.rb
53
+ - script/console
54
+ - script/destroy
55
+ - script/generate
56
+ - spec/sluggable_finder_spec.rb
57
+ - spec/spec.opts
58
+ - spec/spec_helper.rb
59
+ - tasks/rspec.rake
60
+ - tasks/db.rake
61
+ has_rdoc: true
62
+ homepage:
63
+ post_install_message: PostInstall.txt
64
+ rdoc_options:
65
+ - --main
66
+ - README.markdown
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: "0"
74
+ version:
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: "0"
80
+ version:
81
+ requirements: []
82
+
83
+ rubyforge_project: sluggable_finder
84
+ rubygems_version: 1.2.0
85
+ signing_key:
86
+ specification_version: 2
87
+ summary: SEO friendly permalinks for your Active Record models
88
+ test_files: []
89
+