friendly_id 5.2.3 → 5.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/stale.yml +17 -0
  4. data/.github/workflows/test.yml +60 -0
  5. data/Changelog.md +39 -1
  6. data/Gemfile +3 -0
  7. data/README.md +54 -163
  8. data/Rakefile +2 -2
  9. data/UPGRADING.md +115 -0
  10. data/certs/parndt.pem +25 -0
  11. data/friendly_id.gemspec +7 -3
  12. data/gemfiles/Gemfile.rails-5.0.rb +2 -2
  13. data/gemfiles/{Gemfile.rails-4.2.rb → Gemfile.rails-5.1.rb} +4 -5
  14. data/gemfiles/{Gemfile.rails-4.1.rb → Gemfile.rails-5.2.rb} +5 -7
  15. data/gemfiles/{Gemfile.rails-4.0.rb → Gemfile.rails-6.0.rb} +5 -8
  16. data/lib/friendly_id/base.rb +4 -8
  17. data/lib/friendly_id/candidates.rb +0 -2
  18. data/lib/friendly_id/configuration.rb +3 -2
  19. data/lib/friendly_id/finder_methods.rb +18 -7
  20. data/lib/friendly_id/finders.rb +1 -1
  21. data/lib/friendly_id/history.rb +21 -12
  22. data/lib/friendly_id/initializer.rb +11 -0
  23. data/lib/friendly_id/migration.rb +9 -3
  24. data/lib/friendly_id/object_utils.rb +9 -2
  25. data/lib/friendly_id/scoped.rb +8 -1
  26. data/lib/friendly_id/sequentially_slugged.rb +12 -2
  27. data/lib/friendly_id/slug.rb +4 -0
  28. data/lib/friendly_id/slugged.rb +2 -2
  29. data/lib/friendly_id/version.rb +1 -1
  30. data/test/databases.yml +6 -4
  31. data/test/finders_test.rb +24 -0
  32. data/test/helper.rb +13 -3
  33. data/test/history_test.rb +86 -7
  34. data/test/numeric_slug_test.rb +31 -0
  35. data/test/object_utils_test.rb +2 -0
  36. data/test/schema.rb +19 -2
  37. data/test/scoped_test.rb +13 -0
  38. data/test/sequentially_slugged_test.rb +59 -0
  39. data/test/shared.rb +2 -2
  40. data/test/simple_i18n_test.rb +2 -2
  41. data/test/slugged_test.rb +168 -4
  42. data.tar.gz.sig +2 -0
  43. metadata +44 -15
  44. metadata.gz.sig +0 -0
  45. data/.travis.yml +0 -40
data/test/history_test.rb CHANGED
@@ -65,8 +65,7 @@ class HistoryTest < TestCaseClass
65
65
  test "should not be read only when found by slug" do
66
66
  with_instance_of(model_class) do |record|
67
67
  refute model_class.friendly.find(record.friendly_id).readonly?
68
- assert record.update_attribute :name, 'foo'
69
- assert record.update_attributes name: 'foo'
68
+ assert record.update name: 'foo'
70
69
  end
71
70
  end
72
71
 
@@ -93,6 +92,28 @@ class HistoryTest < TestCaseClass
93
92
  end
94
93
  end
95
94
 
95
+ test 'should maintain history even if current slug is not the most recent one' do
96
+ with_instance_of(model_class) do |record|
97
+ record.name = 'current'
98
+ assert record.save
99
+
100
+ # this feels like a hack. only thing i can get to work with the HistoryTestWithSti
101
+ # test cases. (Editorialist vs Journalist.)
102
+ sluggable_type = FriendlyId::Slug.first.sluggable_type
103
+ # create several slugs for record
104
+ # current slug does not have max id
105
+ FriendlyId::Slug.delete_all
106
+ FriendlyId::Slug.create(sluggable_type: sluggable_type, sluggable_id: record.id, slug: 'current')
107
+ FriendlyId::Slug.create(sluggable_type: sluggable_type, sluggable_id: record.id, slug: 'outdated')
108
+
109
+ record.reload
110
+ record.slug = nil
111
+ assert record.save
112
+
113
+ assert_equal 2, FriendlyId::Slug.count
114
+ end
115
+ end
116
+
96
117
  test "should not create new slugs that match old slugs" do
97
118
  transaction do
98
119
  first_record = model_class.create! :name => "foo"
@@ -109,10 +130,10 @@ class HistoryTest < TestCaseClass
109
130
  first_record = model_class.create! :name => "foo"
110
131
  second_record = model_class.create! :name => 'another'
111
132
 
112
- second_record.update_attributes :name => 'foo', :slug => nil
133
+ second_record.update :name => 'foo', :slug => nil
113
134
  assert_match(/foo-.*/, second_record.slug)
114
135
 
115
- first_record.update_attributes :name => 'another', :slug => nil
136
+ first_record.update :name => 'another', :slug => nil
116
137
  assert_match(/another-.*/, first_record.slug)
117
138
  end
118
139
  end
@@ -172,7 +193,7 @@ class HistoryTestWithAutomaticSlugRegeneration < HistoryTest
172
193
  end
173
194
  end
174
195
 
175
- class DependentDestroyTest < HistoryTest
196
+ class DependentDestroyTest < TestCaseClass
176
197
 
177
198
  include FriendlyId::Test
178
199
 
@@ -211,6 +232,37 @@ class DependentDestroyTest < HistoryTest
211
232
  end
212
233
  end
213
234
 
235
+ if ActiveRecord::VERSION::STRING >= '5.0'
236
+ class HistoryTestWithParanoidDeletes < HistoryTest
237
+ class ParanoidRecord < ActiveRecord::Base
238
+ extend FriendlyId
239
+ friendly_id :name, :use => :history, :dependent => false
240
+
241
+ default_scope { where(deleted_at: nil) }
242
+ end
243
+
244
+ def model_class
245
+ ParanoidRecord
246
+ end
247
+
248
+ test 'slug should have a sluggable even when soft deleted by a library' do
249
+ transaction do
250
+ assert FriendlyId::Slug.find_by_slug('paranoid').nil?
251
+ record = model_class.create(name: 'paranoid')
252
+ assert FriendlyId::Slug.find_by_slug('paranoid').present?
253
+
254
+ record.update deleted_at: Time.now
255
+
256
+ orphan_slug = FriendlyId::Slug.find_by_slug('paranoid')
257
+ assert orphan_slug.present?, 'Orphaned slug should exist'
258
+
259
+ assert orphan_slug.valid?, "Errors: #{orphan_slug.errors.full_messages}"
260
+ assert orphan_slug.sluggable.present?, 'Orphaned slug should still find corresponding paranoid sluggable'
261
+ end
262
+ end
263
+ end
264
+ end
265
+
214
266
  class HistoryTestWithSti < HistoryTest
215
267
  class Journalist < ActiveRecord::Base
216
268
  extend FriendlyId
@@ -248,7 +300,7 @@ class HistoryTestWithFriendlyFinders < HistoryTest
248
300
  begin
249
301
  assert model_class.find(old_friendly_id)
250
302
  assert model_class.exists?(old_friendly_id), "should exist? by old id for #{model_class.name}"
251
- rescue ActiveRecord::RecordNotFound => e
303
+ rescue ActiveRecord::RecordNotFound
252
304
  flunk "Could not find record by old id for #{model_class.name}"
253
305
  end
254
306
  end
@@ -346,6 +398,33 @@ class ScopedHistoryTest < TestCaseClass
346
398
  end
347
399
  end
348
400
 
401
+ test "should record history when scope changes" do
402
+ transaction do
403
+ city1 = City.create!
404
+ city2 = City.create!
405
+ with_instance_of(Restaurant) do |record|
406
+ record.name = "x"
407
+ record.slug = nil
408
+
409
+ record.city = city1
410
+ record.save!
411
+ assert_equal("city_id:#{city1.id}", record.slugs.reload.first.scope)
412
+ assert_equal("x", record.slugs.reload.first.slug)
413
+
414
+ record.city = city2
415
+ record.save!
416
+ assert_equal("city_id:#{city2.id}", record.slugs.reload.first.scope)
417
+
418
+ record.name = "y"
419
+ record.slug = nil
420
+ record.city = city1
421
+ record.save!
422
+ assert_equal("city_id:#{city1.id}", record.slugs.reload.first.scope)
423
+ assert_equal("y", record.slugs.reload.first.slug)
424
+ end
425
+ end
426
+ end
427
+
349
428
  test "should allow equal slugs in different scopes" do
350
429
  transaction do
351
430
  city = City.create!
@@ -356,4 +435,4 @@ class ScopedHistoryTest < TestCaseClass
356
435
  assert_equal record.slug, second_record.slug
357
436
  end
358
437
  end
359
- end
438
+ end
@@ -0,0 +1,31 @@
1
+ require 'helper'
2
+
3
+ class NumericSlugTest < TestCaseClass
4
+ include FriendlyId::Test
5
+ include FriendlyId::Test::Shared::Core
6
+
7
+ def model_class
8
+ Article
9
+ end
10
+
11
+ test "should generate numeric slugs" do
12
+ transaction do
13
+ record = model_class.create! :name => "123"
14
+ assert_equal "123", record.slug
15
+ end
16
+ end
17
+
18
+ test "should find by numeric slug" do
19
+ transaction do
20
+ record = model_class.create! :name => "123"
21
+ assert_equal model_class.friendly.find("123").id, record.id
22
+ end
23
+ end
24
+
25
+ test "should exist? by numeric slug" do
26
+ transaction do
27
+ record = model_class.create! :name => "123"
28
+ assert model_class.friendly.exists?("123")
29
+ end
30
+ end
31
+ end
@@ -19,6 +19,8 @@ class ObjectUtilsTest < TestCaseClass
19
19
  end
20
20
 
21
21
  test "ActiveRecord::Base instances should be unfriendly_ids" do
22
+ FriendlyId.mark_as_unfriendly(ActiveRecord::Base)
23
+
22
24
  model_class = Class.new(ActiveRecord::Base) do
23
25
  self.table_name = "authors"
24
26
  end
data/test/schema.rb CHANGED
@@ -2,7 +2,14 @@ require "friendly_id/migration"
2
2
 
3
3
  module FriendlyId
4
4
  module Test
5
- class Schema < ActiveRecord::Migration
5
+ migration_class =
6
+ if ActiveRecord::VERSION::MAJOR >= 5
7
+ ActiveRecord::Migration[4.2]
8
+ else
9
+ ActiveRecord::Migration
10
+ end
11
+
12
+ class Schema < migration_class
6
13
  class << self
7
14
  def down
8
15
  CreateFriendlyIdSlugs.down
@@ -41,6 +48,12 @@ module FriendlyId
41
48
  add_column table_name, :slug, :string
42
49
  end
43
50
 
51
+ paranoid_tables.each do |table_name|
52
+ add_column table_name, :slug, :string
53
+ add_column table_name, :deleted_at, :datetime
54
+ add_index table_name, :deleted_at
55
+ end
56
+
44
57
  # This will be used to test scopes
45
58
  add_column :novels, :novelist_id, :integer
46
59
  add_column :novels, :publisher_id, :integer
@@ -78,6 +91,10 @@ module FriendlyId
78
91
  %w[journalists articles novelists novels manuals cities]
79
92
  end
80
93
 
94
+ def paranoid_tables
95
+ ["paranoid_records"]
96
+ end
97
+
81
98
  def tables_with_uuid_primary_key
82
99
  ["menu_items"]
83
100
  end
@@ -91,7 +108,7 @@ module FriendlyId
91
108
  end
92
109
 
93
110
  def tables
94
- simple_tables + slugged_tables + scoped_tables
111
+ simple_tables + slugged_tables + scoped_tables + paranoid_tables
95
112
  end
96
113
  end
97
114
  end
data/test/scoped_test.rb CHANGED
@@ -81,4 +81,17 @@ class ScopedTest < TestCaseClass
81
81
  end
82
82
  end
83
83
 
84
+ test "should generate new slug when scope changes" do
85
+ transaction do
86
+ novelist = Novelist.create! :name => "a"
87
+ publisher = Publisher.create! :name => "b"
88
+ novel1 = Novel.create! :name => "c", :novelist => novelist, :publisher => publisher
89
+ novel2 = Novel.create! :name => "c", :novelist => novelist, :publisher => Publisher.create(:name => "d")
90
+ assert_equal novel1.friendly_id, novel2.friendly_id
91
+ novel2.publisher = publisher
92
+ novel2.save!
93
+ assert novel2.friendly_id != novel1.friendly_id
94
+ end
95
+ end
96
+
84
97
  end
@@ -137,4 +137,63 @@ class SequentiallySluggedTestWithHistory < TestCaseClass
137
137
  assert_equal 'test-name-2', record2.slug
138
138
  end
139
139
  end
140
+
141
+ test "should work with regeneration with history when 2 slugs already exists and the second is changed" do
142
+ transaction do
143
+ record1 = model_class.create! :name => "Test name"
144
+ record2 = model_class.create! :name => "Test name"
145
+ record3 = model_class.create! :name => "Another test name"
146
+ assert_equal 'test-name', record1.slug
147
+ assert_equal 'test-name-2', record2.slug
148
+ assert_equal 'another-test-name', record3.slug
149
+
150
+ record2.name = "One more test name"
151
+ record2.slug = nil
152
+ record2.save!
153
+ assert_equal 'one-more-test-name', record2.slug
154
+
155
+ record3.name = "Test name"
156
+ record3.slug = nil
157
+ record3.save!
158
+ assert_equal 'test-name-3', record3.slug
159
+ end
160
+ end
161
+ end
162
+
163
+ class City < ActiveRecord::Base
164
+ has_many :restaurants
165
+ end
166
+
167
+ class Restaurant < ActiveRecord::Base
168
+ extend FriendlyId
169
+ belongs_to :city
170
+ friendly_id :name, :use => [:sequentially_slugged, :scoped, :history], :scope => :city
171
+ end
172
+
173
+ class SequentiallySluggedTestWithScopedHistory < TestCaseClass
174
+ include FriendlyId::Test
175
+ include FriendlyId::Test::Shared::Core
176
+
177
+ def model_class
178
+ Restaurant
179
+ end
180
+
181
+ test "should work with regeneration with scoped history" do
182
+ transaction do
183
+ city1 = City.create!
184
+ city2 = City.create!
185
+ record1 = model_class.create! :name => "Test name", :city => city1
186
+ record2 = model_class.create! :name => "Test name", :city => city1
187
+
188
+ assert_equal 'test-name', record1.slug
189
+ assert_equal 'test-name-2', record2.slug
190
+
191
+ record2.name = 'Another test name'
192
+ record2.slug = nil
193
+ record2.save!
194
+
195
+ record3 = model_class.create! :name => "Test name", :city => city1
196
+ assert_equal 'test-name-3', record3.slug
197
+ end
198
+ end
140
199
  end
data/test/shared.rb CHANGED
@@ -62,7 +62,7 @@ module FriendlyId
62
62
  my_model_class = Class.new(model_class)
63
63
  self.class.const_set("Foo", my_model_class)
64
64
  with_instance_of my_model_class do |record|
65
- record.update_attributes my_model_class.friendly_id_config.slug_column => nil
65
+ record.update my_model_class.friendly_id_config.slug_column => nil
66
66
  record = my_model_class.friendly.find(record.id)
67
67
  record.class.validate Proc.new {errors.add(:name, "FAIL")}
68
68
  record.save
@@ -132,7 +132,7 @@ module FriendlyId
132
132
  test "updating record's other values should not change the friendly_id" do
133
133
  with_instance_of model_class do |record|
134
134
  old = record.friendly_id
135
- record.update_attributes! :active => false
135
+ record.update! active: false
136
136
  assert model_class.friendly.find old
137
137
  end
138
138
  end
@@ -91,14 +91,14 @@ class SimpleI18nTest < TestCaseClass
91
91
  class RegressionTest < TestCaseClass
92
92
  include FriendlyId::Test
93
93
 
94
- test "should not overwrite other locale's slugs on update_attributes" do
94
+ test "should not overwrite other locale's slugs on update" do
95
95
  transaction do
96
96
  journalist = Journalist.create!(:name => "John Smith")
97
97
  journalist.set_friendly_id("Juan Fulano", :es)
98
98
  journalist.save!
99
99
  assert_equal "john-smith", journalist.to_param
100
100
  journalist.slug = nil
101
- journalist.update_attributes :name => "Johnny Smith"
101
+ journalist.update :name => "Johnny Smith"
102
102
  assert_equal "johnny-smith", journalist.to_param
103
103
  I18n.with_locale(:es) do
104
104
  assert_equal "juan-fulano", journalist.to_param
data/test/slugged_test.rb CHANGED
@@ -92,7 +92,7 @@ class SluggedTest < TestCaseClass
92
92
  end
93
93
  end
94
94
 
95
- test "should not set slug on create if unrelated validations fail" do
95
+ test "should set slug on create if unrelated validations fail" do
96
96
  klass = Class.new model_class do
97
97
  validates_presence_of :active
98
98
  friendly_id :name, :use => :slugged
@@ -106,11 +106,30 @@ class SluggedTest < TestCaseClass
106
106
  instance = klass.new :name => 'foo'
107
107
  refute instance.save
108
108
  refute instance.valid?
109
+ assert_equal 'foo', instance.slug
110
+ end
111
+ end
112
+
113
+ test "should not set slug on create if slug validation fails" do
114
+ klass = Class.new model_class do
115
+ validates_presence_of :active
116
+ validates_length_of :slug, :minimum => 2
117
+ friendly_id :name, :use => :slugged
118
+
119
+ def self.name
120
+ "Journalist"
121
+ end
122
+ end
123
+
124
+ transaction do
125
+ instance = klass.new :name => 'x'
126
+ refute instance.save
127
+ refute instance.valid?
109
128
  assert_nil instance.slug
110
129
  end
111
130
  end
112
131
 
113
- test "should not set slug on create if unrelated validations fail with custom slug_column" do
132
+ test "should set slug on create if unrelated validations fail with custom slug_column" do
114
133
  klass = Class.new(ActiveRecord::Base) do
115
134
  self.table_name = 'authors'
116
135
  extend FriendlyId
@@ -126,11 +145,31 @@ class SluggedTest < TestCaseClass
126
145
  instance = klass.new :name => 'foo'
127
146
  refute instance.save
128
147
  refute instance.valid?
148
+ assert_equal 'foo', instance.subdomain
149
+ end
150
+ end
151
+
152
+ test "should not set slug on create if custom slug column validations fail" do
153
+ klass = Class.new(ActiveRecord::Base) do
154
+ self.table_name = 'authors'
155
+ extend FriendlyId
156
+ validates_length_of :subdomain, :minimum => 2
157
+ friendly_id :name, :use => :slugged, :slug_column => :subdomain
158
+
159
+ def self.name
160
+ "Author"
161
+ end
162
+ end
163
+
164
+ transaction do
165
+ instance = klass.new :name => 'x'
166
+ refute instance.save
167
+ refute instance.valid?
129
168
  assert_nil instance.subdomain
130
169
  end
131
170
  end
132
171
 
133
- test "should not update slug on save if unrelated validations fail" do
172
+ test "should keep new slug on save if unrelated validations fail" do
134
173
  klass = Class.new model_class do
135
174
  validates_presence_of :active
136
175
  friendly_id :name, :use => :slugged
@@ -149,10 +188,31 @@ class SluggedTest < TestCaseClass
149
188
  instance.active = nil
150
189
  refute instance.save
151
190
  refute instance.valid?
152
- assert_equal 'foo', instance.slug
191
+ assert_equal 'foobar', instance.slug
153
192
  end
154
193
  end
155
194
 
195
+ test "should not update slug on save if slug validations fail" do
196
+ klass = Class.new model_class do
197
+ validates_length_of :slug, :minimum => 2
198
+ friendly_id :name, :use => :slugged
199
+
200
+ def self.name
201
+ "Journalist"
202
+ end
203
+ end
204
+
205
+ transaction do
206
+ instance = klass.new :name => 'foo', :active => true
207
+ assert instance.save
208
+ assert instance.valid?
209
+ instance.name = 'x'
210
+ instance.slug = nil
211
+ instance.active = nil
212
+ refute instance.save
213
+ assert_equal 'foo', instance.slug
214
+ end
215
+ end
156
216
  end
157
217
 
158
218
  class SlugGeneratorTest < TestCaseClass
@@ -419,6 +479,110 @@ class FailedValidationAfterUpdateRegressionTest < TestCaseClass
419
479
 
420
480
  end
421
481
 
482
+ class ToParamTest < TestCaseClass
483
+
484
+ include FriendlyId::Test
485
+
486
+ class Journalist < ActiveRecord::Base
487
+ extend FriendlyId
488
+ validates_presence_of :active
489
+ validates_length_of :slug, :minimum => 2
490
+ friendly_id :name, :use => :slugged
491
+
492
+ attr_accessor :to_param_in_callback
493
+
494
+ after_save do
495
+ self.to_param_in_callback = to_param
496
+ end
497
+ end
498
+
499
+ test "to_param should return nil if record is unpersisted" do
500
+ assert_nil Journalist.new.to_param
501
+ end
502
+
503
+ test "to_param should return original slug if record failed validation" do
504
+ journalist = Journalist.new :name => 'Clark Kent', :active => nil
505
+ refute journalist.save
506
+ assert_equal 'clark-kent', journalist.to_param
507
+ end
508
+
509
+ test "to_param should clear slug attributes if slug attribute fails validation" do
510
+ journalist = Journalist.new :name => 'x', :active => true
511
+ refute journalist.save
512
+ assert_nil journalist.to_param
513
+ end
514
+
515
+ test "to_param should clear slug attribute if slug attribute fails validation and unrelated validation fails" do
516
+ journalist = Journalist.new :name => 'x', :active => nil
517
+ refute journalist.save
518
+ assert_nil journalist.to_param
519
+ end
520
+
521
+ test "to_param should use slugged attribute if record saved successfully" do
522
+ transaction do
523
+ journalist = Journalist.new :name => 'Clark Kent', :active => true
524
+ assert journalist.save
525
+ assert_equal 'clark-kent', journalist.to_param
526
+ end
527
+ end
528
+
529
+ test "to_param should use new slug if existing record changes but fails to save" do
530
+ transaction do
531
+ journalist = Journalist.new :name => 'Clark Kent', :active => true
532
+ assert journalist.save
533
+ journalist.name = 'Superman'
534
+ journalist.slug = nil
535
+ journalist.active = nil
536
+ refute journalist.save
537
+ assert_equal 'superman', journalist.to_param
538
+ end
539
+ end
540
+
541
+ test "to_param should use original slug if new slug attribute is not valid" do
542
+ transaction do
543
+ journalist = Journalist.new :name => 'Clark Kent', :active => true
544
+ assert journalist.save
545
+ journalist.name = 'x'
546
+ journalist.slug = nil
547
+ journalist.active = nil
548
+ refute journalist.save
549
+ assert_equal 'clark-kent', journalist.to_param
550
+ end
551
+ end
552
+
553
+ test "to_param should use new slug if existing record changes successfully" do
554
+ transaction do
555
+ journalist = Journalist.new :name => 'Clark Kent', :active => true
556
+ assert journalist.save
557
+ journalist.name = 'Superman'
558
+ journalist.slug = nil
559
+ assert journalist.save
560
+ assert_equal 'superman', journalist.to_param
561
+ end
562
+ end
563
+
564
+ test "to_param should use new slug within callbacks if new record is saved successfully" do
565
+ transaction do
566
+ journalist = Journalist.new :name => 'Clark Kent', :active => true
567
+ assert journalist.save
568
+ assert_equal 'clark-kent', journalist.to_param_in_callback, "value of to_param in callback should use the new slug value"
569
+ end
570
+ end
571
+
572
+ test "to_param should use new slug within callbacks if existing record changes successfully" do
573
+ transaction do
574
+ journalist = Journalist.new :name => 'Clark Kent', :active => true
575
+ assert journalist.save
576
+ assert journalist.valid?
577
+ journalist.name = 'Superman'
578
+ journalist.slug = nil
579
+ assert journalist.save, "save should be successful"
580
+ assert_equal 'superman', journalist.to_param_in_callback, "value of to_param in callback should use the new slug value"
581
+ end
582
+ end
583
+
584
+ end
585
+
422
586
  class ConfigurableRoutesTest < TestCaseClass
423
587
  include FriendlyId::Test
424
588
 
data.tar.gz.sig ADDED
@@ -0,0 +1,2 @@
1
+ �Hi�/��P:L�񔢣$�v���ҿ�Z��c�����B�C�݁�n��^���KӤ��&%�s[��oϗ*�(0&��g��⸡�3<p�X��]����Z�'�&.3f�Ȫ�,�#-�S�YF�Ԛ�_� L�N�6�W �E�dl��N �biS��f�+�� �[
2
+ eY~��˔�� O� �y~B��K8v$"O'�:�