I develop my Rails app on the Cloud9.
What I'd like to do is to set a part of title to the URL such as stackoverflow.
(e.g. example.com/part-of-tile-here)
Although I found the similar questions, I don't understand what I should do because some of them are old posts, links in the answer are not found.
(e.g. Adding title to rails route)
I will not use Gem as yet.
It would be appreciated if you could give me any hint.
Generally, Ho Man is right but there are a few caveats to the proposed solution.
First of all, you should override the to_param method as well so it actually uses the slug:
# app/models/page.rb
def to_param
slug
end
# ... which allows you to do this in views/controllers:
page_path(#page)
# instead of
page_path(#page.slug)
Second, you should use find_by!(slug: params[:id]) to 1) be up to date (find_by_xxx was deprecated in Rails 4.0 and removed from Rails 4.1) and 2) to replicate the behavior of find and raise an ActiveRecord::RecordNotFound error in case no post can be found for the given slug.
Third, I suggest to always keep the slug up to date and include the ID like so:
# app/models/page.rb
before_validation :set_slug
def to_param
"#{id}-#{slug}"
end
private
def set_slug
self.slug = title.parameterize
end
# ... which allows you to use the regular ActiveRecord find again because it just looks at the ID in this case:
#post = Post.find(params[:id]) # even if params[:id] is something like 1-an-example-post
If you care about search engine results, you should then also include the canonical URL in the <head> section and/or redirect with a 301 status in the controller to avoid duplicate content which search engines generally don't like:
# in the view
<%= tag(:link, rel: :canonical, href: page_url(#page)) %>
# and/or in the controller:
redirect_to(post_url(#post), status: 301) and return unless params[:id] == #post.to_param
Hope that helps.
Have a look at this Railscast which is roughly what you want.
tl;dr
You'll want to save a slug which is a parameterized title, delimited by - when you save the page. (In a before_validation or before_save)
For example, "Random Title Of Page" will get random-title-of-page generated as it's slug.
before_validation :generate_slug
def generate_slug
self.slug ||= title.parameterize
end
In the page controller, you'll need to enable searching by slug.
def show
#page = Page.find_by_slug(params[:id])
end
Related
I working on project using Rails 4.1.6 now. And I have strange problem. Method to_param for my models (Product, Category) sometimes not calling. I use it for SEO-friendly urls.
Here is my to_param method for Category model:
def to_param
puts 'I am in Category to_param'
"#{id}-#{title.to_slug.normalize.to_s}"
end
I use puts for find out is this method working or no. So, when my urls looks good (/categories/39-средства-дезинфекции) I can see the string 'I am in Category to_param' on my server console. This is correct case and all it's great.
But sometimes I have urls like /categories/39 for the same objects. When I look into console for this case, I don't see any prints from my to_param method form Category model.
These two cases I have on the same pages, same views and using the same helpers for category url (category_path).
Most complicated for this situation is that I can't reproduce this bug and don't see any regularity. For the same objects I have correct urls most of times, but sometimes it's not. If I restart rails server and refresh browser with clear cache – problem may out and urls will be correct again.
During my debug and research I found source code for base class. But I can't see there any reasons for the situation described above.
def to_param(method_name = nil)
if method_name.nil?
super()
else
define_method :to_param do
if (default = super()) &&
(result = send(method_name).to_s).present? &&
(param = result.squish.truncate(20, separator: /\s/, omission: nil).parameterize).present?
"#{default}-#{param}"
else
default
end
end
end
end
Also I can tell that this problem was appear, when I used FriendlyID before, using regex for clear and build slugs, and now for babosa gem. So, I think the problem is my to_param sometimes not calling for my model.
So, I found the reason of this behaviour. Now it's resolved!
The reason was I have redefined to_param for Product and Category in my ActiveAdmin files:
before_filter do
Product.class_eval do
def to_param
puts "I am in admin Product to_param"
id.to_s
end
end
Category.class_eval do
def to_param
puts "I am in admin Category to_param"
id.to_s
end
end
end
So, when I was log in Admin panel and go to Product page – "bug" will appear on front-end views.
So, I need to remove Product.class_eval and Category.class_eval blocks from my admin classes.
StackOverflow seems to have this style of routes for questions:
/questions/:id/*slug
Which is easy enough to achieve, both in routes and to_param.
However, StackOverflow seems to also redirect to that path when just an ID is passed.
Example:
stackoverflow.com/questions/6841333
redirects to:
stackoverflow.com/questions/6841333/why-is-subtracting-these-two-times-in-1927-giving-a-strange-result/
Same goes for any variation of the slug
stackoverflow.com/questions/6841333/some-random-stuff
Will still redirect to the same URL.
My question is: Is this type of redirection typically handled in the controller (comparing the request to the route) or is there a way to do this in routes.rb?
The reason I wouldn't think this possible in the routes.rb file is that typically, you don't have access to the object (so you couldn't get the slug based off the ID, right?)
For anyone interested, Rails 3.2.13 and also using FriendlyID
Ok, so I think I've got this.
I was looking into doing something with middleware, but then decided that's probably not the place for this type of functionality (since we need to access ActiveRecord).
So I ended up building a service object, known as a PathCheck. The service looks like this:
class PathCheck
def initialize(model, request)
#model = model
#request = request
end
# Says if we are already where we need to be
# /:id/*slug
def at_proper_path?
#request.fullpath == proper_path
end
# Returns what the proper path is
def proper_path
Rails.application.routes.url_helpers.send(path_name, #model)
end
private
def path_name
return "edit_#{model_lowercase_name}_path" if #request.filtered_parameters["action"] == "edit"
"#{model_lowercase_name}_path"
end
def model_lowercase_name
#model.class.name.underscore
end
end
This is easy enough to implement into my controller:
def show
#post = Post.find params[:post_id] || params[:id]
check_path
end
private
def check_path
path_check = PathCheck.new #post, request
redirect_to path_check.proper_path if !path_check.at_proper_path?
end
My || in my find method is because in order to maintain resourceful routes, I did something like...
resources :posts do
get '*id' => 'posts#show'
end
Which will make a routes like: /posts/:post_id/*id on top of /posts/:id
This way, the numeric id is primarily used to look up the record, if available. This allows us to loosely match /posts/12345/not-the-right-slug to be redirected to /posts/12345/the-right-slug
The service is written in a universal fashion, so I can use it in any resourceful controller. I have't found a way to break it yet, but I'm open to correction.
Resources
Railscast #398: Service Objects by Ryan Bates
This Helpful Tweet by Jared Fine
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
making a pretty url via ruby
I have looked around but I can't find an answer. I am either not asking the right question and/or I am not looking in the right place.
I have the following defined:
config/routes.rb
resources :members
members_controller.rb
def show
#member = Member.find(params[:id])
end
As of now when showing a member, the URL is http://myapp.tld/members/1. Let's say the member's name was cooldude. How hard would it be to show http://myapp.tld/members/cooldude or even http://myapp.tld/cooldude if possible?
Thanks for your help.
I recommend friendly_id but if you want to roll a solution of your own, this should get you started:
Add a slug field to your model
Set the slug field before saving
Override to_param in your model to return the slug instead of id
Setup your model:
# member.rb
before_save do
self.slug = name.parameterize
end
def to_param
slug
end
Change your finder to:
# members_controller.rb
def show
#member = Member.find_by_name!(params[:id])
end
Note that find_by_name! (see the bang?) will raise a ActiveRecord::RecordNotFound exception rather than returning nil. In production, this will automatically show a 404 page.
In your views, you can use member_path(#member) which will now return /members/cool-dude instead of /members/1.
Check out the docs:
http://apidock.com/rails/ActiveRecord/Integration/to_param
http://apidock.com/rails/ActiveRecord/Base (section on dynamic attribute-based finders)
Lets say I have a Page resource, and a particular instance has id = 5 and permalink = foobar.
With resources :pages I can use <%= link_to #page.title, #page %> which outputs the url "/pages/5".
How would I make it output "/pages/foobar" instead? Likewise with the edit url... How do I make edit_page_path(#page) output "/pages/foobar/edit"?
UPDATE
Answers so far have said to override to_param in Page.rb which is a great start. +1 to each. But what if I want <%=link_to #page.title, #page%> to output "/:permalink" rather than "/pages/:permalink"?? I'll accept the answer that comes up with that.
You can override the to_param method in your model which will tell Rails what to use instead of your primary key for routing.
For example
class Page
def to_param
"#{self.id}-#{self.title.parameterize}"
end
end
The parameterize call makes your title URL friendly, you might also notice the use of self.id, this is recommended in case you have a duplicate title.
You need to overide to_param method in your model to return the field you want. Here's a blog post with some examples:
You want to use a permalink.
Add this to your model:
class Post
def to_param
"#{id}-{title}"
end
end
This assumes that you have a title.
Once you get this you want to look look up permalink-fu, or it's actually really simple to do your own with an after save:
class Post
before_save :manage_peramlink
def manage_peramlink
permalink = "#{name.gsub(/\s/, '_').gsub(/[^\w-]/, '').downcase}"
end
def to_param
"permalink"
end
end
Make sure you add peramlink as a field to your model.
I am currently developing a blogging system with Ruby on Rails and want the user to define his "permalinks" for static pages or blog posts, meaning:
the user should be able to set the page name, eg. "test-article" (that should be available via /posts/test-article) - how would I realize this in the rails applications and the routing file?
for user-friendly permalinks you can use gem 'has_permalink'. For more details http://haspermalink.org
Modifying the to_param method in the Model indeed is required/convenient, like the others said already:
def to_param
pagename.parameterize
end
But in order to find the posts you also need to change the Controller, since the default Post.find methods searches for ID and not pagename. For the show action you'd need something like this:
def show
#post = Post.where(:pagename => params[:id]).first
end
Same goes for the other action methods.
You routing rules can stay the same as for regular routes with an ID number.
I personally prefer to do it this way:
Put the following in your Post model (stick it at the bottom before the closing 'end' tag)
def to_param
permalink
end
def permalink
"#{id}-#{title.parameterize}"
end
That's it. You don't need to change any of the find_by methods. This gives you URL's of the form "123-title-of-post".
You can use the friendly_id gem. There are no special controller changes required. Simple add an attribute for example slug to your model..for more details check out the github repo of the gem.
The #63 and #117 episodes of railscasts might help you. Also check out the resources there.
You should have seolink or permalink attribute in pages' or posts' objects. Then you'd just use to_param method for your post or page model that would return that attribute.
to_param method is used in *_path methods when you pass them an object.
So if your post has title "foo bar" and seolink "baz-quux", you define a to_param method in model like this:
def to_param
seolink
end
Then when you do something like post_path(#post) you'll get the /posts/baz-quux or any other relevant url that you have configured in config/routes.rb file (my example applies to resourceful urls). In the show action of your controller you'll just have to find_by_seolink instead of find[_by_id].