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.
Emulating RJS with Merb/JQuery/Haml
DEC
21
2008
Ok, Merb is great. Rails is equally as great. RJS is great. JQuery and Haml are just outstanding. But I found that Merb not doing RJS-like behavior a little daunting in the current application that I'm working on. Of course, Merb does javascript, and you can send Ajax requests, but it's not quite as easy to accomplish RJS-type interactions with the current page. I did quite a bit of googling, and all I ever came up with was a very interesting presentation from Yehuda Katz about using JQuery with Merb. It didn't completely answer my question about getting RJS functionality in a Merb app. So, I set about trying to actually figure something out on my own!
Let's say I have an action called mark_as_complete that I can call for several items on a page, have it make a database call, then update the item without having to leave the page. The first step is to make the form for each item submit via AJAX instead of a regular HTTP POST request. Thanks to Ryan Bates' RailsCast on JQuery for help on that. All I have to do is give the form a class of remote, then this function will cause it to be submitted via AJAX (as well as any form on the site with the same class name):
// function to send the jQuery form object via AJAX
jQuery.fn.submitWithAjax = function() {
this.submit(function() {
// be sure to add the '.js' part so that it knows this is format:js
$.post(this.action + '.js', $(this).serialize(), null, "script");
return false;
})
return this;
};
$(document).ready(function() {
// once the page has completely loaded,
// make forms with the class "remote" submit via AJAX
$("form.remote").submitWithAjax();
});
Now that's done, I need to figure out how to get the RJS stuff to fire correctly. I've already set the format of the request as .js, so the controller knows it's an AJAX request. With Merb, you have to add the provides :js line to your action so that it knows to accept js requests.
def mark_as_complete(id)
provides :js
# do work, son
# set instance variable to indicate success/failure
if stuff_done
@complete = true
end
render
end
Now for the funky part. Getting my Haml template to update the page. Well, if you just name a view mark_as_complete.js.haml, then the application will use it. The hard part was figuring out what to do in the file. I could call javascript in the file and get that to work (alert(), for example), but the following bit of code did NOT work:
- if @complete
$("#div_name_#{@record.id}").html('complete');
$("#notes_#{@record.id}").replaceWith('#{@record.notes}
');
- else
alert("An error occurred processing your request.");
Nothing happened and I couldn't figure out why. The javascript was being run, but the ruby code inside it wasn't being evaluated. Turns out it's because I'm using Haml. I suspect that if I were using erb, then I could surround my ruby code with output blocks(<%= %>) and it would work(though I haven't tested it).
After experimenting with all sorts of variations on the above code, I finally decided to try and put the javascript code into Haml ruby output blocks(so the ruby code would be evaluated). Low and behold, the following actually worked, despite being ugly and less readable.
- if @complete
= "$('#div_name_#{@record.id}').html('complete');"
= "$('#notes_#{@record.id}').replaceWith('#{@record.notes}
');"
- else
alert("An error occurred processing your request.");
Notice that the alert() call isn't in an output block because it doesn't have any ruby code that needs to be evaluated.
This solution worked for me, but I'm sure there is a better way to accomplish this. If anyone has a better approach, please leave it in the comments.
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"
Stop! Haml Time!
NOV
23
2008
Ok, first of all I have to apologize for the title of this post, but I couldn't resist the pun.
Anyway, as the title suggests, I've recently converted all of my erb pages on this site to haml. Haml is a DSL templating language uses to generate well-formed XHTML. The documentation site claims that you can become familiar with the syntax in about 20 minutes. I think, as long as you already know XHTML, you can pick it up in about 10 minutes. The concept is pretty simple. Each tag should be on its own line, and everything contained in that tag should be indented below it. If a tag doesn't have any other tags contained in it, then the content can be on the same line as the tag. Also, div is the default tag, so you can just pass an id or class name to output a div.
%p This is a paragraph tag. %ul %li First list item %li Second list item #id_name Content in div .class_name Content in div
Will convert to (with beautiful formatting):
<p>This is a paragraph tag.</p> <ul> <li>First list item</li> <li>Second list item</li> </ul> <div id="id_name"> Content in div </div> <div class="class_name"> Content in div </div>
It's really easy to include rails evaluation and output blocks too.
- @posts.each do |post|
%p
Written by
= post.author
%p= post.date
Notice that there is no need for end when a block is done. When haml sees that the indentation has ended, it automatically ends the block.
You can also pass any attributes to an HTML tag via a ruby hash.
%img{:src => "/images/picture.png", :alt => "Picture", :title => "Picture"}
I think the coolest thing about Haml is how it can assign a class and unique id to an element simply by passing an ActiveRecord object within square brackets.
%div[@post]
Will output:
<div class="post" id="post_18"><div>
If you'd like to learn more, you can take a look at the online documentation, or you can look at the code for my site on Github.
Site Redesign
NOV
15
2008
My site has received a much-needed facelift. The original "design" was something I just slapped together when I was first building the site. It was really an afterthought because I was only concerned with the functionality of the site, and not so much with how it looked.
Of course, this design is not something that I'm capable of. Luckily Andrew, one of the designers at Plexus, needed someone to code his personal site. So we worked out a deal to trade code for design. I think he did a great job. The only drawback was that he wanted me to put a gaudy link to his site in my footer. Sorry.
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.