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].
Related
I am completely new to Rails and I have a database that links to a certain page depending on the user's search but it will always give me the id.
For example if a user searches, I will get, "localhost:3000/fruit/1" instead of "localhost:3000/fruit/apple". Does anyone know how to switch the url from an id to name?
You need to define a 'to_param' method in the model you are generating a link for, e.g.
class Fruit < ActiveRecord:Base
def to_param
name
end
end
Then in your controller you need to change the find for the show action to find by the attribute you are using in your 'to_param' method, e.g.
#fruit = Fruit.find_by(name: params[:id])
See http://api.rubyonrails.org/classes/ActiveRecord/Integration.html#method-i-to_param for additional details
I'm wondering if it's possible to edit the default Rails routing convention to fetch a specific record based on a field that is not the ID?
For instance, instead of retrieving a specific record based on ID, with the verb/url combination:
GET /users/:id
Retrieve a specific record based on username, with the verb/url combination:
GET /users/:username
I don't see why this would be a problem theoretically, as long as usernames were required to be unique, but I'm having trouble understanding how to implement it based on the Rails Routing Guide.
I have gathered that I will need to add a line to my routes.rb file, to define a singular resource, just prior to:
resources :users
However, I'm having trouble understanding the syntax to accomplish this. Any help in understanding this would be greatly appreciated.
Yes it is possible and they are called Non Restful Routes in the rails documentation
A trivial example is doing the below in your routes.rb
get ':users/:show/:username', controller: "users", action: "show"
and in your UsersController you have a show action that looks like this:
def show
if params[:id].present?
#user = User.find(params[:id])
elsif params[:username].present?
#user = User.find_by(username: params[:username])
end
end
This way you support showing by id and username, if you want do disable support for either of them, modify the if clause as you wish
I think you are looking to change the to_param method like so:
class User < ActiveRecord::Base
def to_param
"#{id} #{name}".parameterize
end
end
This would give the url as: /user/id-name. If you want to get rid of the id before the name it gets a little more complicated. If you were just to remove it, it will more than likely break since ActiveRecord needs the id first for finds.
To get around this I would suggest using FriendlyId gem: https://github.com/norman/friendly_id
There is also a RailsCast showing how to use Friendly_id but its pretty straight forward.
The routes does not care if it is an ID or username.
It is really how you find it in the controller.
Just in the user show controller:
def show
#user = User.find_by_username params[:id]
end
I've got this helper method in my application controller:
def current_team
#current_team ||= Team.find(params[:team_id])
end
Problem is, it works for urls of the format:
/teams/20/members/11
but it doesn't work for:
/teams/20
In order to get it to work for those, I have to change :team_id to be :id.
How can I tidy it up so it 'just works'?
Thanks!
Set instance variables (#current_team) in controllers, never in helpers. It's not what helpers are for.
If you follow this advice, you will naturally use params[:id] in TeamsController, but params[:team_id] in MembersController.
(Some people even go on to say that you shouldn't use helpers at all. For facilitating presentation (custom links, buttons, tables, etc), they propose to use Presenter pattern. But you don't have to listen to them. :))
It is not the best thing to do, but to accomplish that you can do the following:
def current_team
#current_team ||= Team.find(params[:team_id].presence || params[:id])
end
Documentation about the Object.presence method:
http://api.rubyonrails.org/classes/Object.html#method-i-presence
#SergioTulentsev is right, you shall not set instance variables in helpers, only in controllers.
I'm assuming you have other resources besides just Team. Rails is going to use the :id param for all of your resources. You will need to look into customizing the routes for your teams#show action. Easier in Rails 4 than in Rails 3.
Have a look at this post for the gory details: Change the name of the :id parameter in Routing resources for Rails
I wouldn't do params[:team_id] || params[:id], because of course in some controller contexts you'd get an id parameter that represents the id for something other than a Team. Assuming that the /teams/:id route is handled by the TeamsController, then you could do the following (to keep your method in ApplicationController and avoid repeating yourself in different controllers):
def current_team
id = controller_name == "teams" ? params[:id] : params[:team_id]
#current_team ||= Team.find(id)
end
Alternatively, you could change your routes so that the url to show a Team is /teams/:team_id and leave your helper as-is, but that would go against the grain of Rails routing conventions.
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.
Having some issues, I want my urls for the show action to be like:
/post/some-title
i.e. so wherever I reference the show_post_path tag (or whatever it is) it should make that url.
BUT, when editing/updating I want to do this using the ID of the post ie.
/post/234/edit
How can I achieve this, it seems what I am doing is messing things up because I used:
def to_param
#{title}"
end
In my post model.
I always add an attribute called 'slug' to posts and it acts as a slug for that post.
Then just find your posts with Post.find_by_slug(params[:id]).
You can even make it translatable.
Create a to_param method. For example:
def to_param
"#{self[:id]}-#{title.gsub(/[^a-z0-9]+/i, '-')}"
end
Assuming the field you wanted to be in the URL is title, then your posts' URLs would be like /posts/1-this-is-a-post.