FlagpoleSitta
I had visions, I was in them,
I was looking into the mirror
To see a little bit clearer
The rottenness and evil in me.
♫~♫~♫~♫~♫~♫~♫~♫~♫~♫~♫~♫~♫~♫~♫~♫~♫~♫~♫~♫~♫~♫~♫
Preface
This gem should be considered in beta.
Before You Use read This.
github.com/rovermicrover/FlagpoleSitta/wiki/Before-You-Use-Read-This
This gem was inspired in part by the song Flagpole Sitta by Harvey Danger. So if you like the gem and are wanting to help out please either donate your time and submit some patches, or donate to the band who wrote the song. They put there last two albums out there all open source like, only asking that those that could donate after downloading. While your donating, if you choose to do so, don’t be afraid to download their albums, its good stuff!
www.harveydanger.com/contribute.php
My grammar ability’s are fail. So if something doesn’t make sense in the docs don’t be afraid to point it out and/or submit a patch. Because to me it all looks right.….
File System caching and memory caching are not recommended. Redis/Memcache are defiantly suggested and strongly encouraged for this gem. If your using a cloud solution redis more then memecached for reasons stated in the “Before you use read this.” seciton of the wiki.
Inspiration
My insperation for this project came from the following post on github
stackoverflow.com/a/1035832/1436131
On a project I was handling things like the OP was, which of course has a chance of failure if between the controller and the view the cache expires or is deleted.
That post also lead me to the following depericated ruby gem.
I looked at it and decided it didn’t meet my needs even if I where to get it updated. Which lead me to decided to create my own.
PS feel free to vote up the above answer on stackoverflow, I know I have!
Special Thanks
Taha Mukaddam github.com/tmkdam For his feedback and help on this gem.
Installation
Very simple, in your gemfile
gem 'flagpole_sitta'
OR
gem 'flagpole_sitta', :git => "git://github.com/rovermicrover/FlagpoleSitta.git"
Then bundle install.
cache_sitta
Flagpole Sitta is a gem thats main purpose is to make it easier to effectively fragment cache in dynamic fashions in Rails.
When ever a cache is created it is associated with any model and/or record you tell it to be from the view helper method. When that model and/or record is updated all it’s associated caches are cleared.
Flagpole also expects you to put all your database calls into Procs/Lamdbas. This makes it so that your database calls wont have to happen unless your cache hasn’t been created. Thus speeding up response time and reducing database traffic.
For a simple application you could do something like this.
PageModel
class Page < ActiveRecord::Base
cache_sitta :route_id => "url"
end
PagesController
class PagesController < ApplicationController
def show
calls_sitta :name => "page", :section => "body" do
if params[:url]
@page = Page.find_by_url params[:url]
else
@page = Page.find_by_url 'home'
end
end
end
end
show.haml for Pages
- cache_sitta :model => Page, :route_id => params[:url], :section => "body" do
= @page.content.try(:html_safe)
First off lets look at calls_sitta. The view helper method cache_sitta will look for its calls at @#:section_calls. The method calls_sitta in the controller will append its provided block or proc (:block => yourproc) to @#:section_calls.
Second off @#:section_calls is an array of arrays. This is because you can have multiple calls, and each call must be given a target instance variable ie name (‘page’ in this instance) and a call object (the provide proc or block). They must also be ordered thus the array and not a hash.
You can also pass your calls options by providing :calls_args to the helper. You must though set up your calls to expect an options hash.
:route_id and :model must be provide so that the cache can be associated with the correct object, and the cache clear when its supposed to.
:route_id must also be a unique field on the model, other wise the cache won’t connect properly to the object.
For an index page you could do something like the following for a simple app.
BlogModel
class Blog < ActiveRecord::Base
cache_sitta
end
BlogsController
class BlogsController < ApplicationController
def index
calls_sitta :name => "blogs", :section => "body" do
@blogs = Blog.all
end
end
end
index.haml for Blogs
- cache_sitta :models_in_index => Blog, :index_only => true, :section => "body" do
- @blogs.each do |blog|
= blog.title
etc
etc
First notice you don’t have to pass :route_id and :model if you pass :index_only => true and :models_in_index.
This also means if your just showing objects in an index you don’t even have to worry about declaring a route_id(It just defaults to id anyway.)
:models_in_index tells the helper which Model to associated with the index, so if any objects in that model update the index cache gets nuked.
:index_only => true, tells the helper to not bother trying to associated this cache with anyone item in particular.
You could also pass it :scope which will add a ‘scope’ to a :models_in_index cache, which will cause the cache to only be destroyed if an object with in its ‘scope’ is create, updated or destroyed. Like :model and :route_id for each model there must be a corresponding route_id. If you don’t want a scope on every model then just do something like the following
- cache_sitta :models_in_index => [Blog, Setting], :scope => [@scope, nil] :index_only => true, :section => "body" do
The ‘scope’ can only be arguments for a where call. Which means it will either be a hash, record object, string, or an array.
Scopes should be used sparling because in order to verify them on save they require a call to the database, and while it boils down to a call by id, they can still add up if you don’t pay attention.
existence_hash
If you might have already figured out the overall strategy of this gem has a weakness. Namely how do you deal with instances where, the object being routed to doesn’t exist. See if your not querying the database until your already in the view then you can’t exactly redirect with out some crazy stuff going on. You could put some if statements in your view to show a 404 if the objects are nil, but the issue with that is that you would still end up with a bunch of pointless caches. This can all be avoided by creating a ‘hash’ in the cache which you can use to check for the existence of an object. This ‘hash’ too is updated on save/destory. Also the hash really isn’t hash but rather a bunch of cache keys held together by a flag key. Lets use the page example from above.
PageModel
class Page < ActiveRecord::Base
cache_sitta :route_id => "url"
has_existence_hash :route_id => "url"
end
A quick note you only actually have to pass :route_id once if your using both cache_sitta and has_existence_hash. It uses the same class instance variable.
PagesController
class PagesController < ApplicationController
def show
if Page.get_existence_hash(params[:url])
calls_sitta :name => "page", :section => "body" do
if params[:url]
@page = Page.find_by_url params[:url]
else
@page = Page.find_by_url 'home'
end
end
else
redirect_to :action => 'home'
flash[:notice] = "The Page you are looking for doesn't exist"
end
end
end
This will keep your cache safe from being filled with junk. Though this isn’t the only way you can use the ‘existence hash’ to solve this problem. Another note is that the hash is created the first time that you call for it. If you have a enough objects this can take a while. So its suggest that you warm all ‘existence hash’es just in case.
has_brackets_retrieval
This is best used for settings or html fragments stored in the database. It will look for the object in cache, and then if not there query the database and then create the object in cache. These caches too gets cleared on save/destory of their related objects. If the object isn’t in the database, it will create it for you with a default value of an empty string. You can pass it what ever value you want for a default with :default_value. Also it defaults to assuming the content isn’t html safe. You can make it return as an html_safe buffer by passing it :safe_content? => true.
Setting Model
class Setting < ActiveRecord::Base
has_brackets_retrieval :key => :name, :value => :content
end
Key is what you must pass to the brackets on the model. Value is the field it will return.
View Example
%meta{:name => "description", :content => Setting['meta_description']}
This amounts to a very small specific cache_sitta call. For now its suggested to not use this feature for user generated content.
Footer
More examples to come in the wiki, and in the coming example app.
Also if for what ever reason you have made your app multi threaded this gem will break. If your interested in possible making it multithread safe the following link has some good info.
stackoverflow.com/a/1390978/1436131
This gem should be considered in beta.
MIT License