slug candidates rails 4 - ruby-on-rails

I have Job model and Location model in my rails application. I am using postgresql as a database.so i have location_ids as an array field in my Job model for holding locations. I am using FeriendlyId in my application to make my url friendly. when i go to my job show page i am getting this friendly url
http://localhost:3000/jobs/seo-trainee
but now i also want to include the locations the job has in my url , something like this
http://localhost:3000/jobs/seo-trainee-mumbai-tokyo
i know we can use slug_candidates for this purpose. but i dont know how can i achieve this exactly
currently i have this in my Job model
extend FriendlyId
friendly_id :slug_candidates, use: [:slugged, :finders]
def slug_candidates
[
:title,
[:title, :id]
]
end

You need to define a custom method to generate your slug definition, and then tell FriendlyId to use that method.
The documentation gives this example:
class Person < ActiveRecord::Base
friendly_id :name_and_location
def name_and_location
"#{name} from #{location}"
end
end
bob = Person.create! :name => "Bob Smith", :location => "New York City"
bob.friendly_id #=> "bob-smith-from-new-york-city"
So in your case, you would use something like this:
class SomeClass
friendly_id :job_name_and_location
def job_name_and_location
"#{name} #{locations.map(&:name).join(' ')}"
end
end
I've made a few assumptions:
Your job model has a name attribute (seo training)
Your job model has_many locations, each of which have a name attribute
We then create a method which defines the non-friendly string which FriendlyId will use to create a slug from. In this case it'll come up with something like SEO Training Mumbai Tokyo and use that to create your seo-training-mumbai-tokyo slug.

You can use something like the following:
extend FriendlyId
friendly_id :slug_candidates, use: [:slugged, :finders]
def slug_candidates
locs = Location.where("id IN(?)", self.location_ids).collect{|l| l.name}.join("-") // here, we find the locations for the current job, then joins the each locations name with a '-' sign
return self.title+"-"+locs // here, returns the job title with the location names
end
So, if your current Job holds location_ids = [1,2,3]
then from Location table we find the locations with id = 1,2,3. Then join their names.

Related

Use the ID inside slug when using FriendlyID

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

Rails 4 + Friendly_id gem: How to generate slug with a specific string?

I'm building an app that uses the globalize gem. I'm trying to add the Friendly ID gem for permalinks.
My Product table doesn't actually have a name column. The name column is stored in Product::Translation, so I can't use the friendly_id :name, use: :slugged set up that they recommend in the github page.
I want to set the permalink to the name attribute of the english translation. This is what I have so far:
before_save :update_slug
def update_slug
english_translation = translations.find { |t| t.locale == :en }
self.slug = english_translation.name if english_translation
end
However, this doesn't convert the english_translation.name variable to a parameter friendly format. If I have the name as something sdfsdfs sdfsf, the slug will be the same and that would break things since the path would be /products/something sdfsdfs sdfsf with all the spaces.
Is there a method I can run to convert a string into slug format? Something like:
before_save :update_slug
def update_slug
english_translation = translations.find { |t| t.locale == :en }
self.slug = slugify(english_translation.name)
end
I need a method that I can run manually with a parameter to generate the slug.
Friendly_ID
As Greg Burgardt said, your goal is to get the value into friendly_id, allowing it to build your slug.
Your method is to try and manually create the slug attribute - you'll be much better taking the value you derive from the translation and passing it into friendly_id. To do this, here is what I'd do:
#app/models/product.rb
class Product < ActiveRecord::Base
extend Friendly_ID
friendly_id :name, use: :slugged
def name
english_translation = translations.find { |t| t.locale == :en }
english_translation.name
end
end
Friendly_ID builds your slug, meaning you don't want to create the slug yourself:
Since UUIDs are ugly, FriendlyId provides a "slug candidates"
functionality to let you specify alternate slugs to use in the event
the one you want to use is already taken. For example:
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
r1 = Restaurant.create! name: 'Plaza Diner', city: 'New Paltz'
r2 = Restaurant.create! name: 'Plaza Diner', city: 'Kingston'
r1.friendly_id #=> 'plaza-diner'
r2.friendly_id #=> 'plaza-diner-kingston'
To use candidates, make your FriendlyId base method return an array.
The method need not be named slug_candidates; it can be anything you
want. The array may contain any combination of symbols, strings, procs
or lambdas and will be evaluated lazily and in order. If you include
symbols, FriendlyId will invoke a method on your model class with the
same name. Strings will be interpreted literally. Procs and lambdas
will be called and their return values used as the basis of the
friendly id. If none of the candidates can generate a unique slug,
then FriendlyId will append a UUID to the first candidate as a last
resort.
I guess for the sake of posterity:
class Product < ActiveRecord::Base
friendly_id :default_slug, use: :slugged
def default_slug
english_translation = translations.find { |t| t.locale == :en }
english_translation.try(:name)
end
end

Change the unique generated title names of friendly-id

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

Friendly ID Random Url

I am using friendly for my site, and at the moment I have it displaying the title in the url
ie: /articles/hello-world
but say when i create the page, it generates a random number, to avoid duplication
so
ie: /articles/75475848
I know if i get rid of friendly id it will display numbers but
it will be
/articles/1
/articles/2
etc...
Basically how do i get it to show /articles/23456789(random number) instead of /articles/hello-world
Thanks
It seems that generating a UUID (Universally Unique Identifier) for each article might be a good solution for you.
The Ruby standard library gives us a UUID-generating method, so all you need to do is create a database field and then use a before_save callback to give each article its own UUID. Like this:
class Article < ActiveRecord::Base
before_save :set_uuid
def set_uuid
self.uuid = SecureRandom.uuid if self.uuid.nil?
end
end
Edit: No need for external dependencies as per #olleolleolle's comment!
From the FriendlyID docs:
class Person < ActiveRecord::Base
friendly_id :name_and_location
def name_and_location
"#{name} from #{location}"
end
end
bob = Person.create! :name => "Bob Smith", :location => "New York City"
bob.friendly_id #=> "bob-smith-from-new-york-city"
So, if you provide a method for friendly_id that generates a random number that method will be used to generate the slug instead.

Rails FriendlyId and normalize_friendly_id

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

Resources