Currently my URL's appear as www.website.com/entries/1, I'd like to make them appear as www.website.com/title-of-entry. I've been messing around with routes and have been able to get the entry title to display in the URL, but Rails is unable to find the entry without supplying an ID. If I send the ID along with the parameters, the URL appears as www.website.com/title-of-entry?=1. Is there anyway I can pass the ID without having it appear in the URL as a parameter? Thanks!
Like most things, there's a gem for this.
FriendlyID.
Installation is easy and you'll be up and running in minutes. Give it a whirl.
Ususally you'll want to to save this part in the database title-of-entry (call the field slug or something`). Your model could look something like this:
class Entry < ActiveRecord::Base
before_validation :set_slug
def set_slug
self.slug = self.title.parameterize
end
def to_param
self.slug
end
end
Now your generated routes look like this: /entries/title-of-entry
To find the corresponding entries you'll have to change your controller:
# instad of this
#entry = Entry.find(params[:id]
# use this
#entry = Entry.find_by_slug(params[:id])
Update
A few things to bear in mind:
You'll have to make sure that slug is unique, otherwise Entry.find_by_slug(params[:id]) will always return the first entry with this slug it encounters.
Entry.find_by_slug(params[:id]) will not raise a ActiveRecord::RecordNotFound exception, but instead just return nil. Consider using Entry.find_by_slug!(params[:id]).
If you really want your routes to look like this /title-of-entry, you'll probably run into problems later on. The router might get you unexpected results if a entry slug looks the same as another controller's name.
Related
I had a functioning redirect in my routes.rb like so;
match "/invoices" => redirect("/dashboard")
I now want to add a query string to this so that, e.g.,
/invoices?show=overdue
will be redirected to
/dashboard?show=overdue
I've tried several things. The closest I have got is;
match "/invoices?:string" => redirect("/dashboard?%{string}")
which gives me the correct output but with the original URL still displayed in the browser.
I'm sure I'm missing something pretty simple, but I can't see what.
You can use request object in this case:
match "/invoices" => redirect{ |p, request| "/dashboard?#{request.query_string}" }
The simplest way to do this (at least in Rails 4) is do use the options mode for the redirect call..
get '/invoices' => redirect(path: '/dashboard')
This will ONLY change the path component and leave the query parameters alone.
While the accepted answer works perfectly, it is not quite suitable for keeping things DRY — there is a lot of duplicate code once you need to redirect more than one route.
In this case, a custom redirector is an elegant approach:
class QueryRedirector
def call(params, request)
uri = URI.parse(request.original_url)
if uri.query
"#{#destination}?#{uri.query}"
else
#destination
end
end
def initialize(destination)
#destination = destination
end
end
Now you can provide the redirect method with a new instance of this class:
get "/invoices", to: redirect(QueryRedirector.new("/dashboard"))
I have a written an article with a more detailed explanation.
I'm looking to load a single (chosen randomly) object from a single table in my database on every page of my rails app.
For example, a quotes table which has several quotes in the table, and I just want one on every page load.
What's the best way to accomplish this? Obviously copy & pasting the query into each controller isn't the right way to go about this.
I wouldn't use before_filter for this, there is no need to access database on redirecting actions and other "not rendered" actions. Instead I would use helper_function, and I would call it in the layout, as you need to position it anyways.
def random_quote
#quote ||= "Select random quote here"
end
helper_method :random_quote
Your method of selecting a quote is up to you. You just need to access random_quote as a normal helper method in the layout. This only access one quote per action, only if the layout is rendered.
This kind of stuff typically goes into a before_filter in the ApplicationController :
before_filter :get_random_quote
#This code is executed before every action of your app
def get_random_quote
random_id = ...#Generate a random ID ...
#random_quote = Quote.find(random_id)
end
Then in your views, just refer to #random_quote. Done!
Edit : on second thought, Matzi solution seems smarter. The request will only get called when you actually output something. Nothing's wasted.
Assuming PostgreSQL:
before_filter :get_quote
def get_quote
#quote = Quote.order('RANDOM()').limit(1)
end
Imagine i have a blog, and i want a footer or sidebar displaying my 3 most recent posts at any given time.
What is the best way to do this?
I can call #recent_posts in every single controller to have them ready for the layout but this doesn't seem like the best way...at all...
#recent_posts = Posts.all(:limit => 3)
I've been fiddling around with partials, but they do need an instance variable carrying the #recent_posts.
There may be two parts to your concern: 1) performance, and 2) effort required. Both are easily addressed.
As Andrei S notes in his answer, the convenience/effort issue is mitigated by using a before_filter that calls the method that does the work from the ApplicationController class.
The performance issue is only slightly more work. Instead of the method being
def most_recent_posts
Posts.order(created_at DESC).limit(3)
end
instead do this
def most_recent_posts
#most_recent_posts ||= Posts.order(created_at DESC).limit(3)
end
which checks the instance variable for nil; if nil, it does the query and assigns the result to the instance variable.
You'll also need a way to update when a new post is added, so perhaps something like
def clear_most_recent_posts!
#most_recent_posts = nil
end
and then just call clear_most_recent_posts! from the method(s) that modify the table. The before_filter will do its work only when needed.
I am sure some more eloquent rubyist has a nicer way of doing this, but this is an idea.
You could put the part where you have your posts in a partial and use it in the general layout of your app.
To load them all in every controller you could do a before_filter in your ApplicationController in which you set your instance variable, which will be available in your partial that gets rendered in the layout
This way you only get to do it once, and it will get done everywhere (of course you could set conditions on the filter and the layout to load them when you need, that's if you don't really need them on every page)
I have two unrelated models, say Person and Building. When the app receives a url like www.mysite.com/JohnDoe/EmpireState I would like to show properties of the Person with the name johnDoe, and the same for the building with the name EmpireState.
I'm confused as to the routing part specifically. I'm unsure if I need to create a pages controller that can return the objects from the database. How should I go about doing this?
Am hoping for something like below?
match ':user_name/:building_name', :controller => pages
If those two are not related, you shouldn't do it that way. If they ARE related, we call that nested resources.
Example:
resources :projecs do
resources :tasks
end
Sample URL: "/projects/12/tasks/1281"
Edit:
If they are NOT related (taken from my comment):
In your BuildingsController you can fetch the parent informations too. If you use the match route in your question, you'll have params[:user_name] AND params[:building_name] available and can fetch anything you want with them...
Building.find_by_name(params[:building_name]) # return all Buildings based on URL param
I'm trying to store some additional data along with the standard error message in a custom validator in Rails 3.
For example, (ignoring built-in validators) suppose I wanted to check to see if a post is a duplicate before it's saved. I might write my custom validation method like this:
class Post < ActiveRecord::Base
# prevent duplicate posts
validate do |post|
duplicates = Post.find_all_by_body(body)
errors.add_to_base("Post is a duplicate!") if duplicates.length
# something like this is desired:
# errors.add_to_base("Post is a duplicate",
# :extra => { :duplicates => duplicates })
end
end
This will let the user know there are duplicates, but along with adding the error message I would also like to store the duplicates so they can be displayed to the user. How would I store the list of duplicate posts retrieved during validation such that it is associated with the record's errors for the body field, and available to my view?
A simpler example might be length validation: If a field exceeds its maximum length, how can I store the maximum length along with an error message without simply interpolating it into the message as Rails currently does?
I have not had to do this before, but my first thought is to create a new method on the object called duplicates.
attr_accessor :duplicates
Then in your custom validate method, you can set the duplicates on the object making them available to the view when you render the errors. Notice your current code doesn't change much:
validate do |post|
duplicates = Post.find_all_by_body(body)
errors.add_to_base("Post is a duplicate!") if duplicates.size > 0
end
You would then have to intercept that error in the view manually so that you can print out all the duplicates if the "Post is a duplicate!" error is encountered.
You can pass options to the error but they are only used as substitution in i18n templates. To make a long story short, no you can't store meta-data about your error in the errors hash. If you need such a functionality you'll need to look into the ActiveModel::Errors module in Rails core.
Update:
Another solution could be that instead of pushing a string into error hash, you stuff an instance of your own class, a class which quacks like a string but would be decorated with extra methods and state and such like.