dm-is-slug 1.0.1 → 1.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.
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"