The context is pretty simple, I have a Course model that extends from FriendlyId as follow:
extend FriendlyId
friendly_id :friendly_name, use: [:slugged, :history]
def friendly_name
slugs = [self.type_name, self.name]
slugs << self.city.name if self.city
slugs << self.structure.name if self.structure
return slugs
end
And if I create a course with same type, name, city and structure I get the following error:
!! #<ActiveRecord::RecordNotUnique: PG::Error: ERROR: duplicate key value
violates unique constraint "index_courses_on_slug"
DETAIL: Key (slug)=(cours-sevillanas-copie-paris-12-la-trianera) already exists.
I don't understand why FriendlyId doesn't add a sequence number at the end of the slug...
Any suggestion is welcomed.
I have tried to return a string instead of an array in the friendly_name method but the error persists.
Edit
Removing :history fixes the problem.
I'have also tried other branches (4.0-stable, 4.1.x) of FriendlyId but it doesn't fixes the problem.
I had the same problem with the :history features: it's because FriendlyId will use a separate table to store slugs, and will not check the existing slug column.
You can create a migration and re-save the whole table to generate missing slugs in the new slugs table.
For example:
def up
MyModel.all.map(&:save)
end
Related
Given that my slug candidate is my title, and it is already used, the slug will return something like: my-title-49c9938b-ece5-4175-a4a4-0bb2b0f26a27
Is it possible to have friendly_id return a smaller hash? Like: my-title-705d62eea60a
In that case you can create your own method, which you can pass then to friendly_id. There you can define what are going to be the combinations that FriendlyId will use in order to assign a unique identifier to your record as a slug.
For example:
friendly_id :column_candidates, use: :slugged
def column_candidates
[
:name,
[:name, :another_column],
[...more columns combinated as a fallback]
]
end
If FriendlyId can't create a unique record (by slug) after every combination of columns in column_candidates is evaluated, then it's going to append a UUID anyway.
You're free to add the objects you want to column_candidates, being strings, procs, lambdas or symbols. Also the method name doesn't have to be exactly that, you can modify it as needed.
As a last resource, and if a unique identifier can't be created, you can rely on creating your own short and always able to not be unique hash using Digest::SHA1:
...
[-> { Digest::SHA1.hexdigest(name).chars.sample(6).join }]
I'm using FriendlyId gem on Ruby on Rails 5.
Is there any way to stop FriendlyId to create a different slug if the current one has already been taken? I'd like the user to have full control over the slug.
Add this method to your Model.
def should_generate_new_friendly_id?
new_record?
end
or modify the content of the method to match your needs.
I have a Company Model, and i am using friendly_id like this
friendly_id :name, use: :slugged
But since there can be many Company with the same name (different branches). I am trying to handle that case by using city attribute from the address of the Company.
But the Company address is stored in a different table Address.
so company.address.city gives me the city of the company.
friendly_id :slug_candidates, use: :slugged
# Try building a slug based on the following fields in
# increasing order of specificity.
def slug_candidates
[
:name,
[:name, :city]
]
end
I'm aware i can do something like above. But since city is not an attribute of company how can i achieve this?
Update:
Possible solution for this is to create a helper method city which returns the company's city.
But the problem never was that.
The version of friendly_id i am using is 4.0.10.1
and the feature which enables the usage of slug_candidates are available in versions 5 and above.
I tried updating the gem. But it wont get updated as version 5 has dependency on activerecord 4.0 and rails has dependency on activerecord 3.2.13
It's kind of a deadlock. Don't know what to do
class Company < ActiveRecord::Base
.............................
def city
self.address.city
end
end
I'm current using acts_as_paranoid and friendly_id (5.0.1) on a model and when I destroy a model and try to create a new one that will generate the same slug I get:
ERROR: duplicate key value violates unique constraint "index_papers_on_slug"
I need to somehow get the code that checks if a slug already exists check within the scope of all of the objects not just the non-deleted ones.
How can I get friendly_id to use with_deleted when checking if a slug already exists. I should note that I am also using slug history which may be complicating things further.
Upon digging deeper I realized that since I am using history the slug is being fully deleted while the object is just being soft deleted:
DELETE FROM "friendly_id_slugs" WHERE "friendly_id_slugs"."id" = $1 [["id", 9423]]
So, I just need to figure out how to prevent this and I should be okay since it looks like the friendly_id code itself is already using unscoped when trying to find a valid slug.
Adding the following to the model allowed me to overrride the dependent destroy on the slugs
def has_many_dependent_for_slugs; end
The solution comes from a comment on this github issue.
Friendly_id has a module called scoped which allows you to generate unique slugs within a scope. So, probably
class Paper < ActiveRecord::Base
extend FriendlyId
friendly_id :title, :use => :scoped, :scope => :unscoped
end
will resolve the problem.
I just came across this issue too and I figured two different ways to address it.
Solution 1:
Use dependent: false.
friendly_id :title, dependent: false
Solution: 2
Overcoming this problem without overriding the dependent destroy for anyone that wants to avoid that.
The friendly_id gem uses a method called scope_for_slug_generator to set the model scope. That means we could override this method by adding the following to app/config/initializers/friendly_id.rb.
module FriendlyId
def scope_for_slug_generator
scope = if self.class.base_class.include?(Paranoia)
self.class.base_class.unscoped.with_deleted
else
self.class.base_class.unscoped
end
scope = self.class.base_class.unscoped
scope = scope.friendly unless scope.respond_to?(:exists_by_friendly_id?)
primary_key_name = self.class.primary_key
scope.where(self.class.base_class.arel_table[primary_key_name].not_eq(send(primary_key_name)))
end
end
I'm trying to use Friendly ID to create more custom urls. However, the column I want to use for a slug is still a number. I don't want to create a separate column just for the slug, since it would be identical.
I have a Lot that belongs to an Auction.
Auctions have many Lots (items for sale).
A Lot has a lot_number, which is unique to an Auction. It's not unique throughout the entire table, though. It's basically just a way to order the lots in each auction.
My URLs look like: /auctions/1/lots/21 (/auctions/:auction_id/lots/:id)
I want them to be: /auctions/1/lots/1 (/auctions/:auction_id/lots/:lot_number)
I added the following to lot.rb:
extend FriendlyId
friendly_id :lot_number
It almost works. It shows me a Lot with the correct lot number, but the wrong auction.
I read about scopes on the Friendly ID docs, which sounded perfect. I could scope the lot against the auction...
So I tried:
extend FriendlyId
friendly_id :lot_number, :use => :scoped, :scope => :auction_id
Now I see the following error:
SQLite3::SQLException: no such column: lots.slug: SELECT "lots".* FROM "lots" WHERE "lots"."slug" = '1' LIMIT 1
Why is the SQL WHERE lots.slug = 1? Shouldn't it be WHERE lots.auction_id = 1?
Am I using the wrong syntax? Not sure where I've gone wrong.
Any help would be appreciated. Thanks.
i think that you should have a look at from_param and to_param methods that are used in rails to lookup models: http://apidock.com/rails/ActiveRecord/Base/to_param