Posts Tagged with "rails"
Changing Paperclip File Storage Location
JAN
11
2009
For a while at Plexus, we've been using FileColumn for all our image/file upload attachment needs. It's worked out really well, but when we saw Paperclip we thought it might be a better choice.
Paperclip is super easy to setup and use, but we found ourselves wanting to slightly change the default way it stored attachments. We were used to the way that FileColumn created its folder structure. It would make a folder named for the model (singular) that the attachment(s) were part of and a folder for each attachment (singular). Say we had a BlogPost model with an image attachment and a file attachment. FileColumn would make the following two folders: public/blog_post/image and public/blog_post/file.
Paperclip operates a little differently. It creates a folder (plural) in public for each attachment. If you had the same setup as before, Paperclip would create the following two folders: public/images and public/files. Can you see the immediate problem with this? We already have a public/images folder in our default Rails file structure, so this might get a little confusing. Another problem arises if we have several models with an image attachment. Paperclip would store them all in the same folder. This would be ok 99% of the time. See, Paperclip gets images by their id and name, so even if two models have image attachments and the same id, as long as the name is different, we're ok. But, if you somehow add a different image with the same name, to a model with the same id, then it would get overwritten. Not very likely, but still possible.
The great thing about Paperclip is that you can change the default way it stores its attachments. You can just past a few extra options to the has_attached_file call in your model. As of the current version of Paperclip, they have fixed the file structure problem by tweaking the default storage path. They added a system subfolder in the public folder. This has a two-fold benefit. First, we don't have the problem before of having an attachment named image. Second, this works great for Capistrano because the system folder is already symlinked from the public folder, so you don't have to worry about adding symlinks to your deploy file.
Anyway, back to my point of changing the default file storage path. You can just add the url (which tells where to retrieve the files) and path (which tells where to save the file) options to your has_attached_file call in the model. We add the :class option to include the model_name as a folder.
has_attached_file :image,
:styles => {:thumb => '120x120>', :large => '640x480>' },
:default_style => :thumb,
:url => "/system/:class/:attachment/:id/:style/:basename.:extension",
:path => ":rails_root/public/system/:class/:attachment/:id/:style/:basename.:extension"
So, now our folder structure for the original example would be public/system/blog_posts/images and public/system/blog_posts/files. Much better!
Just make sure you put the whole path in the :path option using the :rails_root variable.
Helpful Helpers
NOV
30
2008
Here is a list of helper methods that we use a lot at Plexus to make our jobs easier. A few have been referenced in previous posts, but I thought I'd list them all here in one place.
Strip text for pretty URLs
You can use this method to replace all non-alphanumeric characters to dashes in a title or name when you're including them in the URL.
This is the route I use for my posts on this blog:
m.post_details ':year/:month/:day/:title', :action => "by_date",
:requirements => { :year => /(19|20)\d\d/, :month => /[01]?\d/, :day => /[0-3]?\d/},
:day => nil, :month => nil, :title => nil
And here is the helper to strip out characters in the title when creating the URL:
# Replace all non-alphanumeric characters with dashes def strip_chars(string='') string.gsub(/\s+/,'-').gsub(/[^a-z0-9\-]+/i, '') end
You can call the helper when you generate the route.
post_details_path(:year => @post.date.year, :month => @post.date.month, :day => @post.date.day, :title => strip_chars(@post.title))
Generate a random password of n length
I've actually got a whole other post for just this helper, but decided to include it in this list.
def generate_password(length=6)
chars = 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ23456789'
password = ''
length.times { |i| password << chars[rand(chars.length)] }
password
end
It will generate a 6 digit password by default, but you can specify the desired length.
generate_password => "Q6Kfze" generate_password(10) => "ZSJcmtRH5q"
Built-in rails helpers
These following helpers are built into rails, but you may not know about them.
["this", "that", "the other"].to_sentence => "this, that, and the other"
number_to_currency(123456789) => "$123,456,789.00" number_to_human_size(123456789) => "117.7 MB" number_to_phone(1234567890, :area_code => true) => "(123) 456-7890"
There are a ton of string inflector helpers
"under_scored".dasherize => "under-scored" "names_and_titles".humanize => "Names and titles" "lower case title".titleize => "Lower Case Title"
RESTful Routes Demystified
SEP
14
2008
RESTful application design is a rails-standard way to structure your CRUD actions. It simplifies your controllers into 7 actions: index, show, new, create, edit, update, and destroy. Here are some basics for working with RESTful routes.
The simple way to get the default routes:
ActionController::Routing::Routes.draw do |map| map.resources :products end
Route Options
Several options can be passed to the route to customize it. You can use the path_prefix option to customize the appearance of your URLs. The name_prefix option is used to change the way the routes are called. The controller option is used to specify a custom controller.
# start all of your URLs with /admin # '/admin/projects/:id/edit' map.resources :products, :path_prefix => '/admin' # start all of your routes with 'mng_' # mng_products_path or mng_product_path(:id) map.resources :products, :name_prefix => 'mng_' # if you want your URLs to say '/products/:id' # but your controller is called 'store_products' map.resources :products, :controller => 'store_products'
Adding Routes for Custom Actions
The resources method will generate the routes for the default REST actions, but what if you want to add your own actions to your controller? There are two options for adding custom routes: the member option is for routes that require an id to be passed, and the collection option is for actions that don't require an id. With each option, you must pass a hash with the action name and the HTTP method.
# return_policy_products_path => '/products/return_policy'
# submit_question_products_path => '/products/submit_question'
map.resources :products, :collection => {:return_policy => :get, :submit_question => :post}
# warranty_product_path(:id) => '/products/:id/warranty'
map.resources :products, :member => {:warranty => :get}
Nested Routes
You can nest routes for objects that are related. Say the products have customer reviews with a has_many relationship. You can nest the resources call within the products route definition.
# product_reviews_path(:product_id) => '/products/:product_id/reviews' # product_review_path(:product_id, :id) => '/products/:product_id/reviews/:id' # edit_product_review_path(:product_id, :id) => '/products/:product_id/reviews/:id/edit' map.resources :products do |products| products.resources :reviews, :controller => 'customer_reviews' end
Those are the basics, but there are a LOT more possibilities. If you're interested, there is a really great guide to all things routes.
Working with the flash hash
AUG
17
2008
The flash hash is what Rails uses to display messages (both notices and errors). Since it's a Hash, you can assign any key/value pair that you want, but I tend to stick with flash[:notice] for a success message, and flash[:error] for an error message.
In my opinion, using the flash hash is a little confusing. I haven't come across a book or tutorial that fully explains how to control it properly. So, here are the options for using the flash hash in Rails.
flash[:key] and flash.now[:key]
The following method shows how to best use the flash hash:
def create
@user = User.new(params[:user])
respond_to do |format|
if @user.save
flash[:notice] = 'User was successfully created.'
format.html { redirect_to admin_users_path }
else
flash.now[:error] = 'The user could not be created'
format.html { render :action => 'new' }
end
end
rescue Exception => ex
logger.warn('ERROR: ' + ex.message)
flash.now[:error] = 'There was an error creating the user.'
render :action => 'new'
end
Notice that I use the flash hash in two different ways: flash[:key] and flash.now[:key]. The way I use it depends on when I want it displayed. The flash[:key] usage should only be used before redirection, because it makes the object available for the current action and the next action. The flash.now[:key] usage should be used when you only want the flash object to be available to the current action.
Here's an example why you shouldn't use flash[:key] without redirection. Let's say this is your controller:
class MainController < ApplicationController
def index
flash[:notice] = 'Welcome to the site!'
end
def profile
end
end
When you visit the index page, you'll see the message 'Welcome to the site!.' If you then click a link from the index page that takes you to the profile page, you'll still see the message 'Welcome to the site!' because the flash[:key] is available to the current action and the next action.
Displaying the flash
Here is a really helpful method to display the contents of the flash hash that I modified from one of Ryan Bates' awesome Railscasts:
<%- flash.each do |key, msg| -%>
<div id="<%= key %>">
<p style="float:right;"><%= link_to_function 'X', "Effect.Fade('#{key}')" %></p>
<p><%= msg %></p>
<div class="clear"></div>
</div>
<%- end -%>
This method will loop through each key in your flash hash and create a div with the name of the key, then put the contents inside with a link to close the message div.
I put this method in a partial called _notice_div.html.erb and include it at the top of my application layout. Here are the styles I use for notices and errors:
#notice { background-color: #A4E7A0; border: 1px solid #26722D; }
#error { background-color: #F0A8A8; border: 1px solid #900; }
#notice, #error { width: 90%; margin: 0 auto 10px auto; padding: 5px; }
#notice p, #error p { margin-left: 20px; padding: 0; font-size: .75em; color: #000; }
#notice a, #error a { text-decoration: none; padding: 0 3px; }
#notice a { border: 1px solid #26722D; color: #26722D; }
#error a { border: 1px solid #900; color: #900; }
#notice a:hover, #error a:hover { color: #333; border: 1px solid #333; }
This is a notice div.
This is an error div.
Routes Demystified
AUG
02
2008
It took me a while to discover the full potential of Rails' routes. I slowly learned more and more neat stuff that made me like them more and more. They are extremely powerful and are very useful for URL re-writing. Following is all of my route knowledge. If you notice something that I didn't cover, please leave it in the comments.
A few basic routes:
# this will route domain.com/ to the index action of your main controller map.connect '', :controller => 'main' # this will create a custom URL for your about-us page map.connect 'custom-url/about-us', :controller => 'about_us', :action => 'index'
Pay attention to the order of your routes in your routes.rb file. They are evaluated in order from top to bottom. Routes at the top will be used before routes at the bottom if they are too similar.
Named Routes
Instead of saying map.connect, let's get crazy and name our routes. I'll use the same routes as above and add a few.
# you can now call this route with home_path or home_url map.home '', :controller => 'main' # call this route with about_us_path or about_us_url map.about_us 'custom-url/about-us', :controller => 'about_us', :action => 'index' # symbols in the routes define options you pass when calling the route # call product_details_path(:id => @product) to access this route map.product_details 'products/detail-view/:id', :controller => 'products', :action => 'show' # all-purpose route for the products controller : products_path(:action => 'search') or products_path(:action => 'buy', :id => product) map.products 'products/:action/:id', :controller => 'products'
Route Blocks Using with_options
If we have several routes for the same controller, it makes sense to use with_options to simplify the definitions.
map.with_options :controller => 'news' do |m| m.news 'news-releases/list', :action => 'index' # news_path m.news_details 'news-releases/details/:id', :action => 'show' # news_details_path(:id => news_release) m.news_author 'news-releases/by-author/:name', :action => 'author' # news_author_path(:name => 'Bill') end
Better URLs with Routes
Routes are great for creating search engine friendly URLs. Supposed you'd like to add the title of your blog post to the URL.
map.blog_details 'blog/details/:id/:title', :controller => 'blog', :action => 'show', :title => nil
Now you can call the blog_details path and give it the :id and :title as options for the URL. Also notice that I set :title => nil at the end of the route. This marks that option as optional for the route call.
# this call will generate the following URL: http://domain.com/blog/details/1/My-First-Blog-Post
# I'm using a helper method in this call entitled 'strip_chars', it is below
blog_details_path(:id => post, :title => strip_chars(post.title)) %>
# you can also omit the title when invoking the route because it is optional.
# since your 'details' method will be using the :id parameter to find the record
# we don't really need the title, it's just for search engines
blog_details_path(:id => post) %>
# this helper method takes a string, replaces all spaces with dashes, then strips out all non-letter, non-number, non-dashes
# it's good for generating URL-friendly titles
def strip_chars(string='')
return '' if string.blank?
string.gsub(' ','-').gsub(/[^a-z0-9\-]+/i, '')
end
Caching and Pagination
If we cache our site, and certain actions show a paginated list, then we need to add the :page attribute to the route to ensure that the cache is recorded properly. After all, to our server /blog/list looks the exact same as /blog/list?page=2 when retrieving cached pages.
map.blog 'blog/list/:page',
:controller => 'blog',
:action => 'list',
:requirements => {:page => /\d+/},
:page => nil
# blog_path
# blog_path(:page => 1)
Notice that we added something new, the :requirements option. For :page, it's a regular expression telling us the supplied value must be a number. I've also set :page => nil so that we don't always have to specify it when invoking the route.
One thing to watch out for when calling a paged route is to be careful when you're on another paged section of your site. Say I have the route above for blogs and a similar route for news releases. If I'm on the page /news/list/5 and I call blog_path it will take the :page parameter from the current URL and you'll end up on page 5 of your blog post list. Confused? Just make sure when you are calling a paged route to specify it as blog_path(:page => 1) to explicitly go to /blog/list/1 or call blog_path(:page => nil) to go to /blog/list. Both will work.
Route Conditions
Similar to RESTful routes, you can specify the method of the request with the :requirements option. While this is useful, it makes much more sense to just use map.resources to get real RESTful routes.
map.connect 'blog-post/:id', :controller => 'blog', :action => 'show', :requirements => { :method => :get }
map.connect 'blog-post/:id', :controller => 'blog', :action => 'update', :requirements => { :method => :post }
# a GET method to /blog-post/1 will route to the 'show' action
# a POST method to /blog-post/1 will route to the 'update' action
I think that's enough for normal routes. Hopefully soon I can write a post on RESTful routes, which are a whole different beast.
Popular Posts
Search
Tags
actionmailer activerecord ajax apache apple barcamp caching capistrano centos code golf css db delete eager loading ebay email attachment erb flash ftp fun generators get haml helpers ie sucks javascript jquery lightbox lost merb net ftp paperclip passenger php plexus post presentation rails rails machine railsconf redesign rest rjs routes rss ruby ruby on rails safari script sinatra symfony text replacement tips tutorial twitter xhtml
Projects
© 2010 Travis Roberts. All rights reserved.