dm-is-slug 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -82,6 +82,7 @@ group :runtime do # Runtime dependencies (as in the gemspec)
82
82
  end
83
83
 
84
84
  gem 'dm-core', DM_VERSION, :git => "#{DATAMAPPER}/dm-core.git"
85
+ gem 'dm-validations', DM_VERSION, :git => "#{DATAMAPPER}/dm-validations.git"
85
86
 
86
87
  gem 'unidecode', '~> 1.0.0'
87
88
 
@@ -42,9 +42,23 @@ DataMapper plugin for creating and slugs(permalinks).
42
42
  is :slug, :source => :slug_for_email, :size => 255
43
43
  end
44
44
 
45
+ === Scoped slugs
46
+
47
+ class Post
48
+ include DataMapper::Resource
49
+
50
+ property :id, Serial
51
+ property :title, String
52
+ property :content, String
53
+ property :category, String
54
+
55
+ # Same as above but slugs will be unique only within their category.
56
+ is :slug, :source => :title, :slug => :category
57
+ end
58
+
45
59
  === Finding objects by slug
46
60
 
47
- post = Post.first(:slug => "your_slug")
61
+ post = Post.first(:slug => "your_slug")
48
62
 
49
63
  == Development
50
64
 
data/Rakefile CHANGED
@@ -21,6 +21,7 @@ begin
21
21
  gem.authors = ['Aaron Qian', 'James Herdman', 'Nik Radford', 'Paul', 'Mike Frawley', 'Alexander Mankuta']
22
22
 
23
23
  gem.add_dependency "dm-core", "~> 1.0.2"
24
+ gem.add_dependency "dm-validations", "~> 1.0.2"
24
25
  gem.add_dependency "unidecode", "~> 1.0.0"
25
26
 
26
27
  gem.add_development_dependency 'rspec', '~> 1.3'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.1
1
+ 1.1.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{dm-is-slug}
8
- s.version = "1.0.1"
8
+ s.version = "1.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Aaron Qian", "James Herdman", "Nik Radford", "Paul", "Mike Frawley", "Alexander Mankuta"]
12
- s.date = %q{2010-10-17}
12
+ s.date = %q{2010-10-28}
13
13
  s.description = %q{DataMapper plugin that generates unique slugs}
14
14
  s.email = ["aq1018@gmail.com", "james.herdman@gmail.com", "nik [a] terminaldischarge [d] net", "maverick.stoklosa@gmail.com", "frawl021@gmail.com", "cheba+github@pointlessone.org"]
15
15
  s.extra_rdoc_files = [
@@ -55,15 +55,18 @@ Gem::Specification.new do |s|
55
55
 
56
56
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
57
57
  s.add_runtime_dependency(%q<dm-core>, ["~> 1.0.2"])
58
+ s.add_runtime_dependency(%q<dm-validations>, ["~> 1.0.2"])
58
59
  s.add_runtime_dependency(%q<unidecode>, ["~> 1.0.0"])
59
60
  s.add_development_dependency(%q<rspec>, ["~> 1.3"])
60
61
  else
61
62
  s.add_dependency(%q<dm-core>, ["~> 1.0.2"])
63
+ s.add_dependency(%q<dm-validations>, ["~> 1.0.2"])
62
64
  s.add_dependency(%q<unidecode>, ["~> 1.0.0"])
63
65
  s.add_dependency(%q<rspec>, ["~> 1.3"])
64
66
  end
65
67
  else
66
68
  s.add_dependency(%q<dm-core>, ["~> 1.0.2"])
69
+ s.add_dependency(%q<dm-validations>, ["~> 1.0.2"])
67
70
  s.add_dependency(%q<unidecode>, ["~> 1.0.0"])
68
71
  s.add_dependency(%q<rspec>, ["~> 1.3"])
69
72
  end
@@ -1,6 +1,7 @@
1
1
  require 'unidecode'
2
2
  require 'dm-core'
3
3
  require 'dm-core/support/chainable'
4
+ require 'dm-validations'
4
5
 
5
6
  module DataMapper
6
7
  module Is
@@ -66,6 +67,12 @@ module DataMapper
66
67
  @slug_options[:permanent_slug] = options.delete(:permanent_slug)
67
68
  @slug_options[:permanent_slug] = true if @slug_options[:permanent_slug].nil?
68
69
 
70
+ if options.has_key? :scope
71
+ @slug_options[:scope] = [options.delete(:scope)].flatten
72
+ end
73
+
74
+ @slug_options[:unique] = options.delete(:unique) || false
75
+
69
76
  @slug_options[:source] = options.delete(:source)
70
77
  raise InvalidSlugSourceError, 'You must specify a :source to generate slug.' unless slug_source
71
78
 
@@ -74,10 +81,16 @@ module DataMapper
74
81
  if slug_property && slug_property.class >= DataMapper::Property::String
75
82
  options.merge! slug_property.options
76
83
  end
77
- property :slug, String, options.merge(:unique => true)
84
+ property :slug, String, options
78
85
 
79
- before respond_to?(:valid?) ? :valid? : :save, :generate_slug
80
- before :save, :generate_slug
86
+ if @slug_options[:unique]
87
+ scope_options = @slug_options[:scope] && @slug_options[:scope].any? ?
88
+ {:scope => @slug_options[:scope]} : {}
89
+
90
+ validates_uniqueness_of :slug, scope_options
91
+ end
92
+
93
+ before :valid?, :generate_slug
81
94
  end
82
95
 
83
96
  module ClassMethods
@@ -139,8 +152,15 @@ module DataMapper
139
152
  # The slug is not stale if
140
153
  # 1. the slug is permanent, and slug column has something valid in it
141
154
  # 2. the slug source value is nil or empty
155
+ # 3. scope is not changed
142
156
  def stale_slug?
143
- !((permanent_slug? && !slug.blank?) || slug_source_value.blank?)
157
+ !(
158
+ (permanent_slug? && !slug.blank?) ||
159
+ slug_source_value.blank?
160
+ ) ||
161
+ !(!new? && (dirty_attributes.keys.map(&:name) &
162
+ (self.class.slug_options[:scope] || [])).compact.blank?
163
+ )
144
164
  end
145
165
 
146
166
  private
@@ -176,15 +196,22 @@ module DataMapper
176
196
  end
177
197
  end
178
198
 
199
+ scope_conditions = {}
200
+ if self.class.slug_options[:scope]
201
+ self.class.slug_options[:scope].each do |subject|
202
+ scope_conditions[subject] = self.__send__(subject)
203
+ end
204
+ end
205
+
179
206
  max_index = slugs.map do |s|
180
- self.class.all(not_self_conditions.merge :slug.like => "#{s}-%")
207
+ self.class.all(not_self_conditions.merge(scope_conditions).merge :slug.like => "#{s}-%")
181
208
  end.flatten.map do |r|
182
209
  index = r.slug.gsub /^(#{slugs.join '|'})-/, ''
183
210
  index =~ /\d+/ ? index.to_i : nil
184
211
  end.compact.max
185
212
 
186
213
  new_index = if max_index.nil?
187
- self.class.first(not_self_conditions.merge :slug => base_slug).present? ? 2 : 1
214
+ self.class.first(not_self_conditions.merge(scope_conditions).merge :slug => base_slug).present? ? 2 : 1
188
215
  else
189
216
  max_index + 1
190
217
  end
@@ -41,6 +41,16 @@ describe DataMapper::Is::Slug do
41
41
  belongs_to :user
42
42
  end
43
43
 
44
+ class ::Task
45
+ include DataMapper::Resource
46
+ property :id, Serial
47
+ property :title, String
48
+ property :description, String
49
+ property :category, String
50
+
51
+ is :slug, :source => :title, :scope => :category
52
+ end
53
+
44
54
  class ::SlugKey
45
55
  include DataMapper::Resource
46
56
  property :title, String
@@ -63,6 +73,9 @@ describe DataMapper::Is::Slug do
63
73
 
64
74
  before :all do
65
75
  DataMapper.repository do
76
+ puts ">>> #{DataMapper.repository.adapter.inspect}"
77
+ DataMapper.logger.set_log STDOUT, :debug, ' ~ ', true
78
+ DataMapper.logger << 'boo!'
66
79
  @u1 = User.create(:email => "john@ekohe.com")
67
80
  @p1 = Post.create(:user => @u1, :title => "My first shinny blog post")
68
81
  @p2 = Post.create(:user => @u1, :title => "My second shinny blog post")
@@ -96,10 +109,10 @@ describe DataMapper::Is::Slug do
96
109
  property :id, Serial
97
110
 
98
111
  is :slug, {}
99
- end
112
+ end
100
113
  }.should raise_error(DataMapper::Is::Slug::InvalidSlugSourceError)
101
114
  end
102
-
115
+
103
116
  it "should display obsolete warning if :size option is used" do
104
117
  module M
105
118
  class Thingy
@@ -222,11 +235,11 @@ describe DataMapper::Is::Slug do
222
235
  it 'should unidecode latin characters from the slug' do
223
236
  @p6.slug.should == 'a-fancy-cafe'
224
237
  end
225
-
238
+
226
239
  it 'should unidecode chinese characters from the slug' do
227
240
  @p7.slug.should == 'ni-hao'
228
241
  end
229
-
242
+
230
243
  it 'should have slug_property on instance' do
231
244
  @p1.slug_property.should == @p1.class.properties.detect{|p| p.name == :slug}
232
245
  end
@@ -271,5 +284,51 @@ describe DataMapper::Is::Slug do
271
284
  post.slug.should == 'the-other-post'
272
285
  end
273
286
  end
287
+
288
+ describe 'scoping' do
289
+ before :each do
290
+ Task.all.destroy!
291
+ end
292
+
293
+ it 'should allow duplicate slugs within different scopes' do
294
+ t1 = Task.create :category => 'programming', :title => 'fix'
295
+ t1.slug.should == 'fix'
296
+
297
+ t2 = Task.create :category => 'plumbing', :title => 'fix'
298
+ t2.slug.should == 'fix'
299
+ end
300
+
301
+ it 'should not allow dupliate slugs within same scope' do
302
+ t1 = Task.create :category => 'programming', :title => 'fix'
303
+ t1.slug.should == 'fix'
304
+
305
+ t2 = Task.create :category => 'programming', :title => 'fix'
306
+ t2.slug.should == 'fix-2'
307
+ end
308
+
309
+ it 'should recalculate slug upon change of scope' do
310
+ t1 = Task.create :category => 'programming', :title => 'fix'
311
+ t1.slug.should == 'fix'
312
+
313
+ t2 = Task.create :category => 'plumbing', :title => 'fix'
314
+ t2.slug.should == 'fix'
315
+
316
+ t2.category = 'programming'
317
+ t2.save
318
+ t2.slug.should == 'fix-2'
319
+ end
320
+
321
+ it 'should keep slug if scope did not change' do
322
+ t1 = Task.create :category => 'programming', :title => 'fix'
323
+ t1.slug.should == 'fix'
324
+
325
+ t2 = Task.create :category => 'plumbing', :title => 'fix'
326
+ t2.slug.should == 'fix'
327
+
328
+ t2.description = 'copper pipes are good'
329
+ t2.save
330
+ t2.slug.should == 'fix'
331
+ end
332
+ end
274
333
  end
275
334
  end
metadata CHANGED
@@ -1,13 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dm-is-slug
3
3
  version: !ruby/object:Gem::Version
4
- hash: 21
5
4
  prerelease: false
6
5
  segments:
7
6
  - 1
8
- - 0
9
7
  - 1
10
- version: 1.0.1
8
+ - 0
9
+ version: 1.1.0
11
10
  platform: ruby
12
11
  authors:
13
12
  - Aaron Qian
@@ -20,7 +19,7 @@ autorequire:
20
19
  bindir: bin
21
20
  cert_chain: []
22
21
 
23
- date: 2010-10-17 00:00:00 -07:00
22
+ date: 2010-10-28 00:00:00 -07:00
24
23
  default_executable:
25
24
  dependencies:
26
25
  - !ruby/object:Gem::Dependency
@@ -31,7 +30,6 @@ dependencies:
31
30
  requirements:
32
31
  - - ~>
33
32
  - !ruby/object:Gem::Version
34
- hash: 19
35
33
  segments:
36
34
  - 1
37
35
  - 0
@@ -40,36 +38,49 @@ dependencies:
40
38
  type: :runtime
41
39
  version_requirements: *id001
42
40
  - !ruby/object:Gem::Dependency
43
- name: unidecode
41
+ name: dm-validations
44
42
  prerelease: false
45
43
  requirement: &id002 !ruby/object:Gem::Requirement
46
44
  none: false
47
45
  requirements:
48
46
  - - ~>
49
47
  - !ruby/object:Gem::Version
50
- hash: 23
48
+ segments:
49
+ - 1
50
+ - 0
51
+ - 2
52
+ version: 1.0.2
53
+ type: :runtime
54
+ version_requirements: *id002
55
+ - !ruby/object:Gem::Dependency
56
+ name: unidecode
57
+ prerelease: false
58
+ requirement: &id003 !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ~>
62
+ - !ruby/object:Gem::Version
51
63
  segments:
52
64
  - 1
53
65
  - 0
54
66
  - 0
55
67
  version: 1.0.0
56
68
  type: :runtime
57
- version_requirements: *id002
69
+ version_requirements: *id003
58
70
  - !ruby/object:Gem::Dependency
59
71
  name: rspec
60
72
  prerelease: false
61
- requirement: &id003 !ruby/object:Gem::Requirement
73
+ requirement: &id004 !ruby/object:Gem::Requirement
62
74
  none: false
63
75
  requirements:
64
76
  - - ~>
65
77
  - !ruby/object:Gem::Version
66
- hash: 9
67
78
  segments:
68
79
  - 1
69
80
  - 3
70
81
  version: "1.3"
71
82
  type: :development
72
- version_requirements: *id003
83
+ version_requirements: *id004
73
84
  description: DataMapper plugin that generates unique slugs
74
85
  email:
75
86
  - aq1018@gmail.com
@@ -121,7 +132,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
121
132
  requirements:
122
133
  - - ">="
123
134
  - !ruby/object:Gem::Version
124
- hash: 3
125
135
  segments:
126
136
  - 0
127
137
  version: "0"
@@ -130,7 +140,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
130
140
  requirements:
131
141
  - - ">="
132
142
  - !ruby/object:Gem::Version
133
- hash: 3
134
143
  segments:
135
144
  - 0
136
145
  version: "0"