Simon Harris
Yet another plea: Please don't add foreign-key migrations, schema validations or for that matter acts_as_taggable or any significant number of the myriad plugins that are now available. Leave RoR as lean and as mean as possible. By all means change your assumptions and your opinions but don't allow Rails to become the Micro$oft Word of the Ruby world—bloated and with features that < 1% of the community ever use.
I'm pretty opinionated. DHH is obviously pretty opinionated. That doesn't mean I necessarily agree with his opinions—I clearly think foreign-keys are important—but that doesn't prevent me from using RoR. In fact, quite the contrary. Precisely because DHH is so reticent to adding every new feature under the sun into RoR, the current feature set appeals to most of the developers who use it. This is not to say that Rails is in anyway fully-featured but what is there, most people use. So what about all that neat stuff that we all think is great and absolutely necessary but with which DHH and, no doubt, a non-trivial number of other developers in the community disagrees?
I think my favourite feature of RoR is the very sophisticated plugin model. With a little thought and imagination, it's pretty easy to implement just about any extension imaginable. And this is where the power lies in being lean, mean and opinionated. It's much easier to add features than to take them away—actually it's pretty easy to take them away too but it can get pretty ugly and besides, who wants to spend their time writing plugins to disable functionality? In fact I like plugins so much, I've started thinking about my applications as collections of plugins. Plugins work, there are lots of them, they allow you to add features that no one ever dreamed of and then, with very little effort you can, if you're a good sort, give something back to the development community.
You probably don't want to use much (if any) of the stuff that I think is useful and I sure as hell don't want your manky ideas cluttering up what continues to be my favourite development environment. So, please stop inundating the RoR Trac with every little thing that you believe to be 100% necessary and start building and publishing plugins.
Dion Almaer
"Hmm, this action is taking 45 seconds"
Subscriber.count: entire action == 1 second
Dion Almaer
"I am not getting the output that I expected"
<%=
render :partial => "most_recent_registrations", :locals => load_most_recent_registrations
render :partial => "summary_totals", :locals => load_summary_totals
%>
Dion Almaer
I often talk about how much I like clustered caching for a certain set of large scale systems (Give your DB a break).
I recently had the pleasure to add some simple caching to a Rails app. This time it didn't make sense to add memcached and friends (some interesting work there with ActiveRecord support) because the problem was simple:
One page in a stats package that would be hit by ~4 users, a couple of times a day was very slow (30 secs) to dynamically generate. The data is NOT time sensitive.
Since this piece didn't need to scale to thousands of concurrent users, and stale data was fine, we thought it would be enough to add some HTML caching.
One of our requirements was that we didn't want one poor sucker to have to wait 30 secs to fill the cache. We wanted the cache to fill itself. Again, we ended up with a simple solution to this rather than some killer cache work.
Here are some steps though:
1. Rails Page Caching
At first it seemed simple to just add page caching.
All you need to do here is:
Add "caches_page :action1, :action2" to your controller
Have some way to expire the page. You can create a Cache Sweeper, or have an action that expire_page's things, or simply have something that nukes the html file.
Sounds great! However, all that is happening here is the caches_page will make sure that an after_filter is applied that saved the response content out to the file system. By default, a .html file is created in the public directory so apache can just slurp it right up and serve it. Very fast.
The sweeping is where you have to write some code, but it is simple to do, so you can't totally complain (although it could actually be even simpler: I would love to see a system where you have a cache config so you put in expiration info in there and rails auto handles expiry).
The problem is that this full page caching only works if you CAN actually cache the entire page. Erm, surely that is most of the time right? Well, not really. If you have any user-specific work on the page it will not work. This could be as simple as showing a login/register area for non-logged in people, to a 'Welcome Dion' link to the users account if they are logged in. If you have special UI changes for admin users it will not work (e.g. add 'Edit' links next to content to give your admin users a simple interface).
This all means that you often can NOT use full page caching (and this was the case for us).
2. Action Caching
Another option here is to cache an action rather than the full page. The only difference is that: "unlike page caching, every request still goes through the Action Pack. The key benefit of this is that filters are run before the cache is served, which allows for authentication and other restrictions on whether someone is allowed to see the cache."
So, it lets you still do filters (which we would need to do as we wouldn't want the stats to be viewed by anyone), but still has the full page limitations.
3. Fragment Caching
The first two items sit on top of Fragment Caching.
Fragment caching is used for caching various blocks within templates without caching the entire action as a whole. This is useful when certain elements of an action change frequently or depend on complicated state while other parts rarely change or can be shared amongst multiple parties.
So, we could simply wrap the content that took a long time to load with:
<% cache do %>
... all of the long running pieces ...
<% end %>
And we can tweak keys via:
<% cache(:action => "list", :action_suffix => "all_topics") do %>
Now we finally have just a piece of content cached, so the full UI is still dynamic (get the logged in features) and all of the filters will of course run.
By why out of the box does this version still take a long time to load?
If you are testing in development, caching will not happen for you because config/environments/development.rb will probably have:
config.action_controller.perform_caching = false
You are used to thinking of this in the form of "in dev mode Rails reloads files that I have changed". In this case it is the HTML caching too. To test, change this setting to true in dev mode, and remember to change it back later ;)
Where are you DOING the long running task? Just because you have a cache() wrapping the VIEW doesn't mean that the controller isn't running the action. If your action has @data = long_running_method that the view then uses, the caching will do nothing for you. Make sure that the action doesn't do the long running thing, but that is kicked off from within the cache block itself. E.g. literally by calling the long_running_method, or by doing a render passing in the long running piece (which is inside the cache)
Ok, done right? Not quite. This has just created the cache file. Unless you want this to happen once and have that same version used for the rest of time you need to clean up.
We simply put something in cron to expire that fragment every X minutes (as it was simple). When working on an app that does more of this caching, then we create a cache config object that knows more about expiration and does the deed for you.
Also, remember the requirement not to screw the first user after expiry? The cron script does this in a hacky way. After it nukes the fragment it accesses the url of the page immediately to kick off a new cache page. This isn't totally trivial as in our case the page is behind authentication and some filters that can trip the script.
To solve this we have a cookie that the process uses to get through the auth piece (security worry) and we turn off one of the filters (cookies_required) for this beast.
Conclusion
Rails has a bunch of built-in page caching mechanisms, but they aren't THAT useful out of the box. You need to tweak and play around to get what you need, and most of the time you will NOT use the simple solutions.
For our large scale sites we still love the event-driven memcached approaches.
What have you done?
Simon Harris
After listening to Prag Dave's Keynote Speech this afternoon, I was motivated to implement some of the things he'd been asking for. Here's my first cut at it.
As the doco says, the plugin reads some—ok only one at the moment but we'll see how many others I get done before the beer runs out—database column constraints and tries to apply the closest corresponding rails validation. The first one I implemented reads the NOT NULL constraints against columns and generates a corresponding validates_presence_of.
I literally just whipped it up with no tests or what-not and I've only played with it against PostgreSQL, so if it has bugs or behaves oddly for whatever reason, please let me know, send me as much info as possible and I'll make it work. Nothing better than having real people testing it for me ;-)
Update: Ok, so far the beer has lasted long enough to implement validation of numbers (including specific support for integers) and lengths of strings.
Update: Now calls validates_presence_of anytime you declare a belongs_to association for a NOT NULL foreign-key column.
Update: Single-column unique indexes are now converted to validates_uniqueness_of.
Paul R. Brown
Last year, I wrote a rudimentary sidebar to display Feedburner feed links in Typo, but I didn't really get it to the point I wanted at the time. So, I took another fifteen minutes to rewrite the sidebar to work with the enhanced API, ditch the auto-subscribe chiclets, add links for category feeds, and muck with routes.rb. In routes.rb, I mapped a new set of feed URLs for Feedburner onto the controller that currently serves feeds, switched the existing mappings to a two-line controller that 301's to the Feedburner equivalents, and left holes so that people can subscribe directly to article-specific or tag-specific feeds if they wish. (The bonus in this approach is that autodiscovery gets taken care of for free, because the autodiscovery feed is one that gets 301'd.)
Just for grins, here's the two-line controller implementation:
class FbController < ContentController
def redirect
headers["Status"] = "301 Moved Permanently"
redirect_to "http://feeds.feedburner.com/Multifarious" +
params[:type].to_s.capitalize + params[:id].to_s.capitalize
end
end
Sometimes I think that the cornucopia of methods on some of the Ruby core classes (like capitalize on String) is overkill, and sometimes,
I hope that the enhanced setup is useful to any readers (since Feedburner should ensure QoS), but mostly I hope that it's transparent. (FWIW, NetNewsWire did the Right Thing and changed the feed URL for my self-subscription to the new one in response to the 301.) If for some reason you can't see this, let me know...
Lance Ball
Rails keeps getting better and better every day. There’s some really exciting stuff going on with edge rails – especially the growing support for REST based application development. Not only do some of the new features make creating REST apis incredibly straightforward, there’s domain model support as well.
Check out Ryan Daigle’s overview.
ActiveResource provides a large piece of the REST puzzle by basically implementing the client side of a RESTful system – the parts of a decoupled system that consume RESTful services. At its essence, ActiveResource provides a way to utilize model objects as REST-based client proxies to remote services. Ok, sounds impressive, but what does this mean?
Let’s look at an example of a typical find call on a Person model object:
Person.find(1).name #=> "Ryan"
Doesn’t appear to be anything out of the ordinary going on here – except that under the covers it’s not a database SELECT that’s occuring. Instead, if the Person model is an Active Resource, this find call is actually sending an HTTP GET request across the wire to a RESTful service.