If you've ever needed to implement user-friendly upload, you know intimately what a pain it is to get right. The web just isn't built for uploading files from a browser. I mean, it kinda works, but even then only with a dozen or so limitations. Even the major photo and video sites have tried various solutions to make this easier for users. So when I built Dibs.net, I decided rather quickly to abandon all hope of getting it working flawlessly with plain ol' Javascript and HTML, and instead looked into using a fairly nonintrusive Flash uploader component. (Without Flash installed, it just falls back to a simple HTML-based file-upload form.)
That's not to say it was perfectly simple to get working with Rails. Because Dibs.net accepts uploads only from logged-in users, I ran into two limitations that would not allow me to use this solution:
Flash doesn't send the cookies from the browser (at least it doesn't in Firefox; it might in IE)
Rails doesn't support non-cookie sessions
Because Flash doesn't send the session cookie, Rails thinks the request is coming from a new, logged-out user and creates a new session for it. Adding a cookies feature to Flash was well out of my hands since I don't work for Adobe, so I looked into a way to restore the session from a session key passed as a URL parameter. After some experimentation, I found a solution that works great.
Assumptions
I use a modified version of the acts_as_authenticated plugin. Upon authentication, the plugin sets the :user session key to the authenticated user's id. You'll need to adapt for your own configuration.
Example Rails Code
In RAILS_ROOT/app/controllers/show_my_ip_controller.rb:
Then we include the session id as a parameter in the form's action URL in the view:
How it works
Under normal circumstances the acts_as_authenticated plugin sets the @current_user instance variable to the current logged-in user at the start of each request. Since we have no session data when a Flash app hits the controller, there's effectively no current_user. Our goal is to get current_user working, so we:
turn sessions off for the relevant action; otherwise Rails will create useless sessions any time Flash hits that action
prepend a before filter to set the @current_user instance variable
require login for most of the actions, including create
In the before_filter, we grab the session data from whatever session store we're using, decode and unmarshall it, and set the @current_user instance variable to the User we find with the id we get from the session hash.
I forgot to mention when I published this earlier that there's a reason parameterized sessions is discouraged: browsers will send the entire current link, including the session id, in referer headers to offsite hosts. This doesn't affect Dibs.net's Flash upload, but in other scenarios use the above with caution, or with SSL.
What a mess. You have undoubtedly run across these methods sprinkled throughout the Ruby and Rails world. If you've been working with Rails for even a short time, you've probably read a little about security and attr_accessible. But do you really understand what each of them do and when to use them?
I'm not a fan of whoever made these methods so closely named, especially because they serve very different purposes: two are specific to Rails (or more accurately, ActiveRecord), while the other three are Ruby core methods. When I have a need for any of them, I still have to really think about which one I actually want to use. Often I still have to glance at the rdocs to be reassured my choice is the right one. So let's dive in and figure out what the heck these are supposed to do, and when to use them.
Forget Rails, Let's Talk Ruby Attributes
I've noticed a fair number of folks try to write Rails apps without much knowledge of Ruby. That's a noble thing to try, but to be a truly effective Rails developer, you'll need to be intimately knowledgeable about the Ruby language.
For this reason, and to get our feet wet, we'll ignore the Rails methods for a moment and focus on the Ruby, since that's the source of three important attr_* methods: attr_accessor, attr_reader, and attr_writer. Put these three methods together in a bucket inside your head, as they are closely related.
Say we have a simple little Ruby class (remember we're ignoring Rails at the moment, so this is just a basic Ruby Object class, not an ActiveRecord model).
In address.rb:
Our class doesn't do much yet, but we can create an Address instance and print it as a string using the to_s method:
What if we want to be able to get each of those attributes separately from the full address, say to print out only the zip code? Trying to print the zip attribute with our current code won't work:
In order to call address.zip, we need an attribute getter to make the @zip instance variable visible outside the instance. If you're from the world of Java you're probably intimately familiar with these so-called "getter" methods. So, you'd go and write your "getter" method for @zip:
Okay, only four more methods to go and you can head over to the water cooler for a well-deserved break! (You get paid by the line, after all!) It feels kinda silly, though, since all these methods follow an identical pattern. Since this is such a common pattern, Ruby gives us a little shortcut: anytime you would add an attribute getter as above, you can and should use the handy attr_reader method instead, which effectively creates the attribute getter methods for us behind the scenes, without us having to explicitly define each method in detail. Our new class looks like this:
Now we can grab all of our attributes apart from the full address:
But what about setting the zip code?
Nope, doesn't work. Maybe that's what you want: if an attribute should not be settable after the object is instantiated, you wouldn't want the ability to set the value of an attribute like this. (If you ever hear the term "immutable", that is what we're talking about here: Address instances are currently immutable because they can not be "mutated," or changed, after instantiation.) But if you do want to set your attribute values, you'll need to create a way for code outside the instance to change the value of that somewhat elusive @zip instance variable. Again, if you are coming from Java programming, you'd attempt to write a "setter" method here:
But, if you're starting to hear harp music right now, you might be thinking that Ruby might give us an equally simple, declarative way to make attribute setter methods as it did for getters. Lo and behold, attr_writer comes to the rescue:
And now we can set or get any attribute value:
Now, you might think we're in good shape: we've consolidated what would have been ten different method definitions into a two lines of declarative, readable code. Not so fast! There's a bit of redundancy in those two lines, isn't there? Specifying all your fields in two places is clearly less maintainable than if we could tell Ruby "we want getters and setters for this list of attributes." It might not be a big deal for our little Address class, but when you have hundreds or thousands of classes that you haven't looked at in a year, every extraneous line matters.
Again, this is such a common scenario in programming that Ruby offers the third attribute method, attr_accessor, which does exactly what two separate calls to attr_reader and attr_writer would do.
So, let's get rid of that repetition now:
And this works exactly like the previous example:
Hopefully this clarifies what these three methods are for. Next time, we'll explore the similarly-named-but-entirely-different-purposed attr_protected and attr_accessible methods brought to us by Rails' ActiveRecord models.
Further Reading
The attr_accessor, attr_reader, and attr_writer methods are fairly well documented in the Ruby core rdocs.
Web applications can receive requests directly, via a CGI process, through proxy servers, relayed from front-end web servers, and so on. This can complicate how you might find out where the request originated if you, for example, wanted to limit an online poll to one vote per IP address. Luckily, Rails consolidates most of the ways to get this info into a single convenience method on the request object for us.
The Convenience Method: #remote_ip
Without the request.remote_ip method, you'd have to look for specific headers that are used to carry this data in the HTTP request beyond the server where the actual client's connection was terminated.
Rails' request.remote_ip method is pretty smart: it looks for and parses the headers HTTP_CLIENT_IP, HTTP_X_FORWARDED_FOR and REMOTE_ADDR and parse the value which are commonly used for this purpose.
Example Rails Code
In RAILS_ROOT/app/controllers/show_my_ip_controller.rb:
In RAILS_ROOT/app/views/show_my_ip/index.html.erb:
At the risk of being called a keyword Nazi and having to endure accusations that I care just a little too much about this stuff: Microsoft just managed surpass my experience with Yahoo.
As I mentioned, I've been advertising my online community classifieds site, Dibs.net, on Google for almost a month. Last night I decided to try out Microsoft's advertising network for my community classifieds site. I copied some of my better performing ads from my Classifieds Ad Group on Google into my Classifieds Ad Group on Microsoft.
Here's representative ad from the bunch:
I was in business fairly quickly -- or so I thought. Microsoft has a similar "editorial guidelines" process to Yahoo's. But at Microsoft, the process must go something like this:
Take break from huffing paint fumes and watching Scooby-Doo
Flip coin
Heads, you get to advertise with your keyword; tails, you don't
Microsoft rejected what I consider to the the most important keywords from my advertising, including "classifieds" and "community classifieds."
I've given them a chance to respond before I start calling them names here ;)
(PS - Google's success in advertising appears to be not without reason.)
Update: Microsoft took only a few hours to respond, and did so positively by reinstating the classifieds-related keywords. Good for them (and me). They blamed the problem on their automated editorial process, which raises the question: why would anyone automate such a thing? I think we can all agree computers are great at some things, but applying subjective and vaguely defined editorial guidelines isn't one of them. Either way, I take back my paint fumes and Scooby-Doo comment above, since it appears they aren't nuts after all.
I've been advertising Dibs.net on both the Google and Yahoo ad networks for three weeks. I'll have more to say later on, but I did want to share some notes about Yahoo's "editorial process" regarding what search terms they let you advertise against. This process is allegedly for quality purposes, so that more relevant advertising is displayed to searchers.
Google has taken a more free-market approach to their editorializing of search terms you can advertise against. I'm sure they have a quality-control staff to keep ads clean, but they tend to let you spend your money however you want. I'm no Google fan, but I think Google's approach is far superior, mainly because the market actually does self-correct mispositioned advertising: as people find their advertising for certain keywords isn't working, they pare back the selection of keywords to be more laser-focused. I did this very thing; it works.
Yahoo, on the other hand, voluntarily protects certain company names (though who knows which ones) and is awfully serious about the relevancy of keywords you choose. But it's all so inconsistent and confusing that you never know what they will allow. For example, they apparently allow some misspellings of brand names, but not others. And today I got notice they decided a term wasn't relevant enough to Dibs.net -- which surprised me, since I often describe Dibs.net using the exact term they rejected.
So, I just fired off an email to Yahoo support to see if they can make this situation clearer than the Mississippi mud they've given me till now.
Can you give me more detail as to why you believe the keyword "flea markets" isn't applicable to my company, which is itself an online flea market (dibs.net)?
Removing the keyword from my campaign strikes me as an overly aggressive editorial decision, especially after conducting this search on Yahoo and noting NOT A SINGLE AD relates even remotely to flea markets. In fact, mine is the MOST RELEVANT of any company there. Of the most absurd listings you apparently allow, BizRate, Shopping.com, PalmersUniforms.com and Restaurant.com are advertised on the "flea markets" keyword.
Yes, Restaurant.com advertises under "flea markets" but dibs.net cannot? Come on, Yahoo! I'm trying to like your ad service. I really am. But this sort of random, haphazard enforcement of "editorial rules" leaves me plain confounded.
I have paused my advertising until I receive a response explaining how Yahoo can possibly hope to apply such picky rules consistently.
Thanks,
Kevin Hunt
We'll see what they have to say about Restaurant.com's flea market. Mmmmm, makes you hungry just thinking about it, doesn't it?
Update (8/23): Yahoo responded this morning. They explained that because Dibs.net's content is not about flea markets, the keyword is not allowed. Ok, fair enough, if those are the actual rules -- but they're not. That would be stupid. I responded and asked if Amazon would be allowed to advertise with the keywork "book stores," since they have no information about actual book stores. Also, Yahoo's response did not address how a site about restaurants can use the keyword they are disallowing for me. I'm still confused.
Come on, Yahoo! I really want to like you. Granted, this isn't such a big deal in the grand scheme of things; but if I'm to commit an ad budget to you, you'd better clarify your rules and apply them consistently.