NO LONGER SUPPORTED¶ ↑
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.¶ ↑
NOTE¶ ↑
These docks are for the old version of the gem. When the new version is realesed will udpate the docs. This gem has gone through a major overhaul to get ready for rails 4.
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.….
From version 2 onward Redis Caching is required because of the use of Redis Objects. This should make the gem faster, and more efficient, not to mention condense the code, and allow for more features to be add more easily.
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. You can even use it in a Russian Doll caching system. Guide coming later.
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.
You can also tell it to watch associated objects and clear their caches on update too, by declaring on model something like the following.
Model¶ ↑
cache_sitta :watch_assoc => [:jobs, :master]
This method though is not very efficient for large numbers of associated objects (Ie association that would be larger then available memory). It should be better in the next few versions though (This was written at V2.0.0). It needs to be set up to update in batch instead of all at once.
You also have the option if you index based on date to do the following.
Model¶ ↑
cache_sitta :time_column => "published_at"
View¶ ↑
- cache_sitta :section => "body", :index_only => true , :time_models => Blog, :time => {:year => @year, :month => @month} do - render :partial => "blogs/index/body"
:time_column of course being the column you base the time indexing on.
You can also pass the helper method :day and :hour to time. If you pass it hour you must pass it day, month year. If you pass it day you must pass it day and year. Etc etc. These can either be a string or a number. In the future it will be possible to just do :time => @datetime.
This works because index based on time have stable, not relative boundaries. This means you will have far fewer cache invalidations on update.
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.
Edit: The following is true only for the old version. The new versionw when realesed should be thread safe through the use of redis-multex.
If 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