I'm using the following:
gem 'friendly_id', github: 'FriendlyId/friendly_id', branch: 'master'
I am creating an Article section on my Rails 4 website. The problem I am having is that when I change a existing article's name the slug is not updated.
This is what I have so far:
extend FriendlyId
friendly_id :name, use: :slugged
add_column :articles, :slug, :string
add_index :articles, :slug, unique: true
In FriendlyId 4 (Rails 3 compatible) there was a method
should_generate_new_friendly_id?
and you could define it on your model to control when slug is regenerated.
Try
def should_generate_new_friendly_id?
name_changed?
end
to regenerate slug when name changes.
EDIT
FriendlyId version 5 (Rails 4 compatible) doesn't regenerate slugs on save anymore. To restore this functionality you can either set slug column to nil before saving or use the solution provided above.
EDIT 2
You need to override the slug setter for your saves to work for Rails <5 & FriendlyId > 5 as referenced in this issue.
Add this to the model file
def slug=(value)
if value.present?
write_attribute(:slug, value)
end
end
I have this issues and just want to point out what I've noticed.
if you only do as in docs
class Post < ActiveRecord::Base
extend FriendlyId
friendly_id :title, use: :slugged
end
and then run Post.find_each(&:save) - slug is gonna get updated...
However in my case, I also have these in my model
class Post < ActiveRecord::Base
extend FriendlyId
friendly_id :title, use: :slugged
def normalize_friendly_id(text)
text.to_slug.normalize(transliterations: :russian).to_s
end
def should_generate_new_friendly_id?
title_changed?
end
end
with the code above it won't do anything when you run Post.find_each(&:save) I assume since your title doesn't change. (first method handles russian language)
so when working with the first model all worked great, but then when I copied ready code to next model I wanted to slugify, I run into some issues. Hope it helps someone.
I was facing a similar issue where the slug don't want to change on update even though I had the following in my model:
def should_generate_new_friendly_id?
slug.blank? || title_changed?
end
In my case I wasn't setting the title directly via the form but in a before_save callback, and that was the reason, because it seems that FriendlyID needs the change to happen not only before saving but even before_validation so I changed my code to:
before_validation :set_title
def set_title
self.title = 'some dynamic way of getting title'
end
And, that worked for me!
I really spent too much time trying to figure out why it wasn't working, so I'm posting this case here for anyone who may get stuck in the same situation (and hopefully save you long hours of debugging)
Related
I have a model, and in accordance with the friendly_id gem it looks like this:
class FinancialYear < ApplicationRecord
extend FriendlyId
friendly_id :slug_candidates, use: :slugged
def slug_candidates
[
:end_year,
[:end_year, :max_id]
]
end
def should_generate_new_friendly_id?
self.slug.blank? || self.year_changed?
end
def end_year
if !self.year.nil? && self.year.length > 1
self.year.split('-')[-1].strip
else
self.year
end
end
def max_id
FinancialYear.where(year: end_year).count + 1
end
end
What it's supposed to do is turn a year:'1999-2000' into a slug: '2000' and 2000-2...etc to avoid collisions.
Unfortunately my tests are failing expected: "2000", got: "2000-f7608e8b-a2e7-449c-ae54-4785c7a68dec"
I am using friendly_id on another model in my app and am using the same technique, and its working perfectly. Any help or suggestions as to why this isn't working would be very appreciated.
UPDATE
After more experimentation I've discovered that this seems to only be happening in my rspec tests - but I don't understand why? Any thoughts?
i had the same issue,i resolved it by deleting all slug values first and then re-running my save.
###delete all slug values
User.update_all(:slug=>nil)
###re-run to get the new slug candidate affective overriding the default alpanumeric slug
User.find_each(&:save)
Hope it helps.
Inside my model, I have the following:
friendly_id :id_and_title, use: [:slugged, :finders]
...
def id_and_title
"#{self.id}-#{self.title}"[0,100]
end
However, when creating a new record, the ID isn't being used on the slug field.
What I'm currently doing is:
after_save :regenerate_slug
...
def regenerate_slug
self.slug = nil
self.save
end
and I'm wondering if there is any other way of doing this?
So the problem is that friendly_id does not have access to "id" until after the record is created. The issue here is the after_save causes infinite recurrence, like Michal said, since it calls save.
You should use an after_create instead. You only need to do this once. In all subsequent updates to the record the id should be available for friendly_id to pick up.
#user3062913 has the solution for it here:
Rails4 Friendly_id Unique Slug Formatting
I am using the friendly_id gem. In the portfolio.rb I placed these two lines:
extend FriendlyId
friendly_id :title, use: :slugged
As you can see I am also using the slug option. When I create a project with title "example" it works find and I can find the project under mysite.com/projects/example. Now, if I create a second one with the same title I get a title for it like this one: mysite.com/projects/example-74b6c506-5c61-41a3-8b77-a261e3fab5d3. I don't really like this title. I was hoping for a friendlier title like example-2.
At this question, RSB (user) told me that its friendly_id that causes that. I was wondering if there is a way to create a more friendly. At first I thought of "manually" checking if the same title exists (in a while loop) and assigning another title using either example-2 or example-3 or... example-N.
However do I need to do something like that or am I missing something? Is there an easier way to do something like that?
Check the documentation for the latest version of friendly_id:
A new "candidates" functionality which makes it easy to set up a list of alternate slugs that can be used to uniquely distinguish records, rather than appending a sequence.
Example straight from the docs:
class Restaurant < ActiveRecord::Base
extend FriendlyId
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],
[:name, :street, :city],
[:name, :street_number, :street, :city]
]
end
end
UUID
The problem you're alluding to is the way in which friendly-id appends a hash (they call it a UUID) to duplicate entries:
Now that candidates have been added, FriendlyId no longer uses a
numeric sequence to differentiate conflicting slug, but rather a UUID
(e.g. something like 2bc08962-b3dd-4f29-b2e6-244710c86106). This makes
the codebase simpler and more reliable when running concurrently, at
the expense of uglier ids being generated when there are conflicts.
I don't understand why they have done this, as it goes against the mantra of friendly ID, but nonetheless, you have to appreciate that's how it works. And whilst I don't think the slug_candidates method above will prove any more successful, I do think that you'll be able to use something like a custom method to define the slug you wish
--
You'll want to read this documentation (very informative)
It says there are two ways to determine the "slug" your records are assigned, either by using a custom method, or by overriding the normalize_friendly_id method. Here's my interpretation of both of these for you:
Custom Method
#app/models/project.rb
Class Project < ActiveRecord::Base
extend FriendlyID
friendly_id :custom_name, use: :slugged
def custom_name
name = self.count "name = #{name}"
count = (name > 0) ? "-" + name : nil
"#{name}#{count}"
end
end
Normalize_Friendly_ID
#app/models/project.rb
Class Project < ActiveRecord::Base
extend FriendlyID
friendly_id :name, use: :slugged
def normalize_friendly_id
count = self.count "name = #{name}"
super + "-" + count if name > 0
end
end
I just added the Friendly_id gem to my rails project looking to not use the database id and create a slug, but I can't seem to create slugs for old records. My model looks like this.
class Mapping < ActiveRecord::Base
extend FriendlyId
friendly_id :title, use: :slugged
# Friendly_Id code to only update the url for new records
def should_generate_new_friendly_id?
new_record? || slug.blank?
end
end
I then am running Model.find_each(&:save) but it keeps spitting out a nil result. I've tried commenting out the should_generate_new_friendly_id completely but with no luck. Anyone see what I'm doing wrong here?
EDIT
I rolled my database back and rewrote my migratations and that has appeared to have fixed the issue.
If anyone finds this 6 year old question:
Without a "do and end" block:
find_each returns an Enumerable (not an ActiveRecord Class)
Use:
Model.find_each.select(&:save)
Notice the added "select" method
Doing stuff with Rails’ find_each
Trying to get my app running the FriendlyId gem (version 4.0.1)
I think I'm doing this in the wrong order, but I want to strip out apostrophes before my friendly_id slug is generated when creating a new record. But I think the method normalize_friend_id is being called after the id is already generated.
I've added the following to my model:
class Team < ActiveRecord::Base
extend FriendlyId
friendly_id :name, :use => :slugged
def normalize_friendly_id(string)
super.gsub("\'", "")
end
end
super calls the superclass first, meaning the friendly id is being generated then you're running gsub on THAT result. What you really want is to override this method completely.
Refer to: https://github.com/norman/friendly_id/blob/master/lib/friendly_id/slugged.rb#L244-246
your code should look like this:
def normalize_friendly_id(string)
string.to_s.gsub("\'", "").parameterize
end
or
def normalize_friendly_id(string)
super(string.to_s.gsub("\'", ""))
end
Hope that helps