earth library
Simplify your admin dashboard - create new charts in seconds!
Works with Rails 3.1+ and most browsers (including IE 6)
A perfect companion to groupdate
Usage
Line chart
Loading...
<%= line_chart User.group_by_day(:created_at).count %>Pie chart
Loading...
<%= pie_chart Goal.group("name").count %>Column chart
Loading...
<%= column_chart Task.group_by_day_of_week(:created_at).count %>Multiple series (except pie chart)
Loading...
<%= line_chart @goals.map{|goal| {:name => goal.name, :data => goal.feats.group_by_week(:created_at).count } } %>Say Goodbye To Timeouts
Make your pages load super fast and stop worrying about timeouts. Give each chart its own endpoint.
<%= line_chart completed_tasks_charts_path %>And in your controller, pass the data as JSON.
class ChartsController < ApplicationController def completed_tasks render :json => Task.group_by_day(:completed_at).count end endNote: This feature requires jQuery at the moment.
Options
id and height
<%= line_chart User.group_by_day(:created_at).count, :id => "users-chart", :height => "500px" %>min and max values (except pie chart)
<%= line_chart User.group_by_day(:created_at).count, :min => 1000, :max => 5000 %>Data
Pass data as a Hash or Array
<%= pie_chart({"Football" => 10, "Basketball" => 5}) %> <%= pie_chart [["Football", 10], ["Basketball", 5]] %>For multiple series, use the format
<%= line_chart [ {:name => "Series A", :data => series_a}, {:name => "Series B", :data => series_b} ] %>Times can be a time, a timestamp, or a string (strings are parsed)
<%= line_chart({20.day.ago => 5, 1368174456 => 4, "2013-05-07 00:00:00 UTC" => 7}) %>Installation
Add this line to your application's Gemfile:
And add the javascript files to your views. chartkick.js runs as a Rails engine - no need to install it.
Note: These files must be included before the helper methods.
For Google Charts, use:
<%= javascript_include_tag "//www.google.com/jsapi", "chartkick" %>If you prefer Highcharts, use:
<%= javascript_include_tag "path/to/highcharts.js", "chartkick" %>History
View the changelog
Chartkick follows Semantic Versioning
Contributing
- Fork it
- Create your feature branch (git checkout -b my-new-feature)
- Commit your changes (git commit -am 'Add some feature')
- Push to the branch (git push origin my-new-feature)
- Create new Pull Request
Add MongodbLogger settings to database.yml for each environment in which you want to use the MongodbLogger. The MongodbLogger will also look for a separate mongodb_logger.yml or mongoid.yml (if you are using mongoid) before looking in database.yml. In the mongodb_logger.yml and mongoid.yml case, the settings should be defined without the 'mongodb_logger' subkey.
DivergenceSwitch branches as fast as you switch pages. Waiting for a deploy sucks. Allocating a staging server for each remote branch is costly. But nothing beats testing on a staging server with real production data. Divergence allows you to quickly test your remote branches simply by changing the subdomain.
How it works
Divergence allows you to easily view any branch from your repository by using the branch name as the subdomain. Replace invalid URL characters with dashes and Divergence will magically find the branch you're looking for. Hook into one or many callbacks to automatically restart Passenger or run a bundle install if needed.
http://branchname.yourdomain.com
Running Divergence is simple. Install the gem, initialize it into a folder, set your application paths in the config, and start it. You may have to make a quick DNS change as well for the best results. It works best when you give it its own domain, like layervault-divergence.com
Depending on your setup, some more configuration may be required. Divergence was built with an Apache-Passenger stack in mind. If you want to develop the project further, please see the Contributing section. Our team is eager to see where Divergence goes.
More in-depth instructions on running and configuration, as well as the Divergence source code, are available on GitHub.
Installation
Divergence lives on RubyGems. Simply run:
gem install divergenceAfter installation finishes, move to an empty directory and run:
divergence initConfiguration
Each project is going to be a bit different, that's why we've made Divergence highly configurable. First, you will need to point Divergence where to look. Here's an example config/config:
# config/config.rb require File.expand_path('../callbacks', __FILE__) Divergence::Application.configure do |config| # Change this to the git repository path config.git_path = "/var/www/web_app/repository" # this to your application's path config.app_path = "/var/www/web_app/current" # and this to a location for the cache config.cache_path = "/var/www/web_app/cache" config.forward_host = 'localhost' config.forward_port = 81 endThere are also a suite of callbacks at your disposal. Please see the GitHub project page for a list. Here's an example of our :after_swap callback:
# config/callbacks.rb Divergence::Application.configure do |config| config.callbacks :after_cache, :after_webhook do # This is a built-in helper method bundle_install end endContributing
Download .zip Download .tar.gz View on GitHubNext up
Divergence is a work in progress. This is a beta release. There are a few things that we could use a hand with, including:
- Increased language support
- More stacks supported, (e.g. nginx, Unicorn, etc.)
- HTTPS support built-in
If you'd like to tackle these things, fork it up and issue us a Pull Request.
Credit
Ryan LeFevre
Kelly Sutton
Allan Grinshtein
License
Copyright 2012 LayerVault Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Co-authored by Ben Willis (@bwillis) and Jim Jones (@aantix).
Version Cake is an unobtrusive way to version APIs in your Rails app.
- Easily version any view with their API version:
app/views/posts/ - index.v1.xml.builder - index.v3.xml.builder - index.v1.json.jbuilder - index.v4.json.jbuilder
- Gracefully degrade requests to the latest supported version
- Clients can request API versions through different strategies
- Dry your controller logic with exposed helpers
Check out https://github.com/bwillis/350-rest-api-versioning for a comparison of traditional versioning approaches and a versioncake implementation.
Install
gem install versioncakeExample
In this simple example we will outline the code that is introduced to support a change in a version.
config/application.rb
config.view_versions = (1...4) config.view_version_extraction_strategy = :http_parameter # for simplicityOften times with APIs, depending upon the version, different logic needs to be applied. With the following controller code, the initial value of @posts includes all Post entries. But if the requested API version is three or greater, we're going to eagerly load the associated comments as well.
Being able to control the logic based on the api version allow you to ensure forwards and backwards compatibility for future changes.
PostsController
class PostsController < ApplicationController def index # shared code for all versions @posts = Post.scoped # version 3 or greated supports embedding post comments if derived_version >= 3 @posts = @posts.includes(:comments) end end endSee the view samples below. The basic top level posts are referenced in views/posts/index.v1.json.jbuilder. But for views/posts/index.v4.json.jbuilder, we utilize the additional related comments.
Views
Notice the version numbers are denoted by the "v{version number}" extension within the file name.
views/posts/index.v1.json.jbuilder
json.array!(@posts) do |json, post| json.(post, :id, :title) endviews/posts/index.v4.json.jbuilder
json.array!(@posts) do |json, post| json.(post, :id, :title) json.comments post.comments, :id, :text endSample Output
When a version is specified for which a view doesn't exist, the request degrades and renders the next lowest version number to ensure the API's backwards compatibility. In the following case, since views/posts/index.v3.json.jbuilder doesn't exist, views/posts/index.v1.json.jbuilder is rendered instead.
http://localhost:3000/posts.json?api_version=3
[ { id: 1 title: "Version Cake v0.1.0 Released!" name: "Ben" updated_at: "2012-09-17T16:23:45Z" }, { id: 2 title: "Version Cake v0.2.0 Released!" name: "Jim" updated_at: "2012-09-17T16:23:32Z" } ]For a given request, if we specify the version number, and that version of the view exists, that version specific view version will be rendered. In the below case, views/posts/index.v1.json.jbuilder is rendered.
http://localhost:3000/posts.json?api_version=2 or http://localhost:3000/posts.json?api_version=1
[ { id: 1 title: "Version Cake v0.1.0 Released!" name: "Ben" updated_at: "2012-09-17T16:23:45Z" }, { id: 2 title: "Version Cake v0.2.0 Released!" name: "Jim" updated_at: "2012-09-17T16:23:32Z" } ]When no version is specified, the latest version of the view is rendered. In this case, views/posts/index.v4.json.jbuilder.
http://localhost:3000/posts.json
[ { id: 1 title: "Version Cake v0.1.0 Released!" name: "Ben" updated_at: "2012-09-17T16:23:45Z" comments: [ { id: 1 text: "Woah interesting approach on versioning" } ] }, { id: 2 title: "Version Cake v0.2.0 Released!" name: "Jim" updated_at: "2012-09-17T16:23:32Z" comments: [ { id: 4 text: "These new features are greeeeat!" } ] } ]How to use
Configuration
You need to define the supported versions in your Rails application.rb file as view_versions. Use this config to set the range of supported API versions that can be served:
config.view_versions = [1,3,4,5] # or (1...5)You can also define the way to extract the version. The view_version_extraction_strategy allows you to set one of the default strategies or provide a proc to set your own. You can also pass it a prioritized array of the strategies.
config.view_version_extraction_strategy = :query_parameter # [:http_header, :http_accept_parameter]These are the available strategies:
- query_parameter: version in the url query parameter, for testing or to override for special case i.e. http://localhost:3000/posts.json?api_version=1 (This is the default.)
- http_header: Api version HTTP header ie. X-API-Version: 1
- http_accept_parameter: HTTP Accept header ie. Accept: application/xml; version=1 why do this?
- custom: lambda {|request| request.headers["HTTP_X_MY_VERSION"].to_i } takes the request object and must return an integer
The strategies use a default string of api_version, but that can be changed:
config.view_version_string = "special_version_parameter_name"Version your views
When a client makes a request to your controller the latest version of the view will be rendered. The latest version is determined by naming the template or partial with a version number that you configured to support.
- app/views/posts - index.html.erb - edit.html.erb - show.html.erb - show.json.jbuilder - show.v1.json.jbuilder - show.v2.json.jbuilder - new.html.erb - _form.html.erbIf you start supporting a newer version, v3 for instance, you do not have to copy posts/show.v2 to posts/show.v3. By default, the request for v3 or higher will gracefully degrade to the view that is the newest, supported version, in this case posts/show.v2.
Controller
You don't need to do anything special in your controller, but if you find that you want to perform some tasks for a specific version you can use requested_version and latest_version. This may be updated in the near future.
def index # shared code for all versions @posts = Post.scoped # version 3 or greated supports embedding post comments if derived_version >= 3 @posts = @posts.includes(:comments) end endClient requests
When a client makes a request it will automatically receive the latest supported version of the view. The client can also request for a specific version by one of the strategies configured by view_version_extraction_strategy.
Related MaterialLibraries
Discussions
Questions?Create a bug/enhancement/question on github or contact aantix or bwillis through github.
LicenseVersion Cake is released under the MIT license: www.opensource.org/licenses/MIT
Here's a sample dashboard with 2 widgets. With a name of sample.erb, it becomes accessible at /sample.Each widget is represented by a div element needing data-id and data-view attributes. The wrapping <li>tags are used for layout.
data-id: Sets the widget ID which will be used when pushing data to the widget. Two widgets can have the same widget id, allowing you to have the same widget in multiple dashboards. Push data to that id, and each instance will be updated.
data-view: Specifies the type of widget that will be used.
Both these widgets are of the same type: Number. However, using different data- attributes lets you customize them. You can use any arbitrary attribute you want — each one will be available for you within the widget logic.
Anatomy of a widget
- an HTML file used for layout and bindings
- a SCSS file for styles
- a coffeescript file which allows you to handle incoming data & functionality
Number widget's HTML:
Widgets use batman bindings in order to update their contents. Whenever the data changes, the DOM will automatically reflect the changes.
You may notice the piping '|' characters in some of the data-bind's above. These are Batman Filters, and let you easily format the representation of data.
Number widget's Coffeescript:
By default, whenever a widget receives JSON data, it mixes in the received data onto itself. This means that if you pass in data with a title attribute, the widget's title variable will change, and so will all DOM elements bound to it. No need to handle that yourself.
Laying out the widgets
Dashing uses gridster.js to create a layout for your widgets. Each widget is part of an unordered list. The <li> element for a widget contains:
- data-row : What row the widget is on.
- data-col : What column the widget is on
- data-sizex : Integer size for the width of a widget
- data-sizey : Integer size for the height of a widget
For both data-row and data-col, you can leave them as 1, and gridster will automatically position your widgets.
If you want to customize the layout easily, simply navigate to your running dashboard, and drag the widgets with your mouse. You will be prompted to save the layout when you're finished. Follow the on-screen instructions.
For data-sizex and data-sizey, these are multiples of the widget dimensions you can configure in application.coffee
JuggernautJuggernaut has been deprecated! Read why here.
Juggernaut gives you a realtime connection between your servers and client browsers. You can literally push data to clients using your web application, which lets you do awesome things like multiplayer gaming, chat, group collaboration and more.
Juggernaut is built on top of Node.js and is super simple and easy to get going.
Features
- Node.js server
- Ruby client
- Supports the following protocols:
- WebSocket
- Adobe Flash Socket
- ActiveX HTMLFile (IE)
- Server-Sent Events (Opera)
- XHR with multipart encoding
- XHR with long-polling
- Horizontal scaling
- Reconnection support
- SSL support
As you can see, Juggernaut supports a variety of protocols. If one isn't supported by a client, Juggernaut will fallback to one that is.
Supported browsers are:
- Desktop
- Internet Explorer >= 5.5
- Safari >= 3
- Google Chrome >= 4
- Firefox >= 3
- Opera 10.61
- Mobile
- iPhone Safari
- iPad Safari
- Android WebKit
- WebOs WebKit
Requirements
- Node.js
- Redis
- Ruby (optional)
Setup
If you're using the Brew package management system, use that:
brew install nodeOr follow the Node build instructions
If you're using the Brew package, use that:
brew install redisOr follow the Redis build instructions
Install Juggernaut
Juggernaut is distributed by npm, you'll need to install that first if you haven't already.
npm install -g juggernautThis step is optional, but if you're planning on using Juggernaut with Ruby, you'll need the gem.
gem install juggernautRunning
Start Redis:
redis-serverStart Juggernaut:
juggernautThat's it! Now go to http://localhost:8080 to see Juggernaut in action.
Basic usage
Everything in Juggernaut is done within the context of a channel. JavaScript clients can subscribe to a channel which your server can publish to. First, we need to include Juggernaut's application.js file. By default, Juggernaut is hosted on port 8080 - so we can just link to the file there.
<script src="http://localhost:8080/application.js" type="text/javascript" charset="utf-8"></script>We then need to instantiate the Juggernaut object and subscribe to the channel. As you can see, subscribe takes two arguments, the channel name and a callback.
<script type="text/javascript" charset="utf-8"> var jug = new Juggernaut; jug.subscribe("channel1", function(data){ console.log("Got data: " + data); }); </script>That's it for the client side. Now, to publish to the channel we'll write some Ruby:
require "juggernaut" Juggernaut.publish("channel1", "Some data")You should see the data we sent appear instantly in the open browser window. As well as strings, we can even pass objects, like so:
Juggernaut.publish("channel1", {:some => "data"})The publish method also takes an array of channels, in case you want to send a message to multiple channels co-currently.
Juggernaut.publish(["channel1", "channel2"], ["foo", "bar"])That's pretty much the gist of it, the two methods - publish and subscribe. Couldn't be easier than that!
Flash
Adobe Flash is optional, but it's the default fallback for a lot of browsers until WebSockets are supported. However, Flash needs a XML policy file to be served from port 843, which is restricted. You'll need to run Juggernaut with root privileges in order to open that port.
sudo juggernautYou'll also need to specify the location of WebSocketMain.swf. Either copy this file (from Juggernaut's public directory) to the root public directory of your application, or specify it's location before instantiating Juggernaut:
window.WEB_SOCKET_SWF_LOCATION = "http://juggaddress:8080/WebSocketMain.swf"As I mentioned above, using Flash with Juggernaut is optional - you don't have to run the server with root privileges. If Flash isn't available, Juggernaut will use WebSockets (the default), Comet or polling.
SSL
Juggernaut has SSL support! To activate, just put create a folder called 'keys' in the 'juggernaut' directory, containing your privatekey.pem and certificate.pem files.
>> mkdir keys >> cd keys >> openssl genrsa -out privatekey.pem 1024 >> openssl req -new -key privatekey.pem -out certrequest.csr >> openssl x509 -req -in certrequest.csr -signkey privatekey.pem -out certificate.pemThen, pass the secure option when instantiating Juggernaut in JavaScript:
var juggernaut = new Juggernaut({secure: true})All Juggernaut's communication will now be encrypted by SSL.
Scaling
The only centralised (i.e. potential bottle neck) part to Juggernaut is Redis. Redis can support hundreds of thousands writes a second, so it's unlikely that will be an issue.
Scaling is just a case of starting up more Juggernaut Node servers, all sharing the same Redis instance. Put a TCP load balancer in front them, distribute clients with a Round Robin approach, and use sticky sessions.
It's worth noting that the latest WebSocket specification breaks support for a lot of HTTP load balancers, so it's safer just using a TCP one.
Client Events
Juggernaut's JavaScript client has a few events that you can bind to:
- connect
- disconnect
- reconnect
Juggernaut also triggers data events in the context of an channel. You can bind to that event by just passing a callback to the subscribe function. Here's an example of event binding. We're using jQuery UI to show a popup when the client loses their connection to our server.
var jug = new Juggernaut; var offline = $("<div></div>") .html("The connection has been disconnected! <br /> " + "Please go back online to use this service.") .dialog({ autoOpen: false, modal: true, width: 330, resizable: false, closeOnEscape: false, title: "Connection" }); jug.on("connect", function(){ offline.dialog("close"); }); jug.on("disconnect", function(){ offline.dialog("open"); }); // Once we call subscribe, Juggernaut tries to connnect. jug.subscribe("channel1", function(data){ console.log("Got data: " + data); });Excluding certain clients
It's a common use case to send messages to every client, except one. For example, this is a common chat scenario:
- User creates chat message
- User's client appends the message to the chat log, so the user sees it instantly
- User's client sends an AJAX request to the server, notifying it of the new chat message
- The server then publishes the chat message to all relevant clients
Now, the issue above is if the server publishes the chat message back to the original client. In which case, it would get duplicated in the chat logs (as it already been created). We can resolve this issue by recording the client's Juggernaut ID, and then passing it as an :except option when Juggernaut publishes.
You can pass the Juggernaut session ID along with any AJAX requests by hooking into beforeSend, which is triggered by jQuery before sending any AJAX requests. The callback is passed an XMLHttpRequest, which we can use to set a custom header specifying the session ID.
var jug = new Juggernaut; jQuery.beforeSend(function(xhr){ xhr.setRequestHeader("X-Session-ID", jug.sessionID); });Now, when we publish to a channel, we can pass the :except option, with the current client's session ID.
Juggernaut.publish( "/chat", params[:body], :except => request.headers["X-Session-ID"] )Now, the original client won't get the duplicated chat message, even if it's subscribed to the /chat channel.
Server Events
When a client connects & disconnects, Juggernaut triggers a callback. You can listen to these callbacks from the Ruby client,
Juggernaut.subscribe do |event, data| # Use event/data endThe event is either :subscribe or :unsubscribe. The data variable is just a hash of the client details:
{"channel" => "channel1", "session_id" => "1822913980577141", "meta" => "foo"}Metadata
You'll notice there's a meta attribute in the server event example above. Juggernaut lets you attach meta data to the client object, which gets passed along to any server events. For example, you could set User ID meta data - then you would know which user was subscribing/unsubscribing to channels. You could use this information to build a live Roster of online users.
var jug = new Juggernaut; jug.meta = {user_id: 1};Using Juggernaut from Python
You don't have to use Ruby to communicate with Juggernaut. In fact, all that is needed is a Redis adapter. Here we're using Python with redis-py.
import redis import json msg = { "channels": ["channel1"], "data": "foo" } r = redis.Redis() r.publish("juggernaut", json.dumps(msg))Using Juggernaut from Node.js
Similar to the Python example, we can use a Node.js Redis adapter to publish to Juggernaut.
var redis = require("redis"); var msg = { "channels": ["channel1"], "data": "foo" }; var client = redis.createClient(); client.publish("juggernaut", JSON.stringify(msg));Building a Roster
So, let's take all we've learnt about Juggernaut, and apply it to something practical - a live chat roster. Here's the basic class. We're using SuperModel with the Redis adapter. Any changes to the model will be saved to our Redis data store. We're also associating each Roster record with a user.
class Roster < SuperModel::Base include SuperModel::Redis::Model include SuperModel::Timestamp::Model belongs_to :user validates_presence_of :user_id indexes :user_id endNow let's integrate the Roster class with Juggernaut. We're going to listen to Juggernaut's server events - fetching the user_id out of the events meta data, and calling event_subscribe or event_unsubscribe, depending on the event type.
def self.subscribe Juggernaut.subscribe do |event, data| user_id = data["meta"] && data["meta"]["user_id"] next unless user_id case event when :subscribe event_subscribe(user_id) when :unsubscribe event_unsubscribe(user_id) end end endLet's implement those two methods event_subscribe & event_unsubscribe. We need to take into account they may be called multiple times for a particular user_id, if a User opens multiple browser windows co-currently.
def event_subscribe(user_id) record = find_by_user_id(user_id) || self.new(:user_id => user_id) record.increment! end def event_unsubscribe(user_id) record = find_by_user_id(user_id) record && record.decrement! endWe need to add a count attribute to the Roster class, so we can track if a client has completely disconnected from the system. Whenever clients subscribes to a channel, increment! will get called and the count attribute will be incremented, conversly whenever they disconnect from that channel decrement! will get called and count decremented.
attributes :count def count read_attribute(:count) || 0 end def increment! self.count += 1 save! end def decrement! self.count -= 1 self.count > 0 ? save! : destroy endWhen decrement! is called, we check to see if the count is zero, i.e. a client is no longer connected, and destroy the record if necessary. Now, at this point we have a live list of Roster records indicating who's online. We just need to call Roster.subscribe, say in a Rails script file, and Juggernaut events will be processed.
#!/usr/bin/env ruby require File.expand_path('../../config/environment', __FILE__) puts "Starting Roster" Roster.subscribeThere's no point, however, in having a live Roster unless we can show that to users - which is the subject of the next section, observing models.
Observing models
We can create an Juggernaut observer, which will observe some of the models, notifying clients when they're changed.
class JuggernautObserver < ActiveModel::Observer observe :roster def after_create(rec) publish(:create, rec) end def after_update(rec) publish(:update, rec) end def after_destroy(rec) publish(:destroy, rec) end protected def publish(type, rec) channels = Array(rec.observer_clients).map {|c| "/observer/#{c}" } Juggernaut.publish( channels, { :id => rec.id, :type => type, :klass => rec.class.name, :record => rec } ) end endSo, you can see we're calling the publish method whenever a record is created/updated/destroyed. You'll notice that we're calling observer_clients on the updated record. This is a method that application specific, and needs to be implemented on the Roster class. It needs to return an array of user_ids associated with the record.
So, as to the JavaScript side to the observer, we need to subscribe to a observer channel and set a callback. Now, whenever a Roster record is created/destroyed, the process function will be called. We can then update the UI accordingly.
var process = function(msg){ // msg.klass // msg.type // msg.id // msg.record }; var jug = new Juggernaut; jug.subscribe("/observer/" + user_id, process);Full examples
You can see the full examples inside Holla, specifically roster.rb, juggernaut_observer.rb and application.juggernaut.js.
lacquerRails drop in for Varnish support.
Install
This gem requires ruby 1.9
Basic installation
sudo gem install lacquer rails generate lacquer:installconfig/initializers/lacquer.rb
Lacquer.configure do |config| # Globally enable/disable cache config.enable_cache = true # Unless overridden in a controller or action, the default will be used config.default_ttl = 1.week # Can be :none, :delayed_job, :resque config.job_backend = :none # Array of Varnish servers to manage config.varnish_servers << { :host => "0.0.0.0", :port => 6082 # if you have authentication enabled, add :secret => "your secret" } # Number of retries config.retries = 5 # config handler (optional, if you use Hoptoad or another error tracking service) config.command_error_handler = lambda { |s| HoptoadNotifier.notify(s) } ### Varnish - 2.x / 3.x .. VCL-Changes ### https://www.varnish-cache.org/docs/trunk/installation/upgrade.html # => Purge Command ( "url.purge" for Varnish 2.x .. "ban.url" for Varnish 3.x ) # => purges are now called bans in Varnish 3.x .. purge() and purge_url() are now respectively ban() and ban_url() config.purge_command = "ban.url" # => VCL_Fetch Pass Command ( "pass" for Varnish 2.x .. "hit_for_pass" for Varnish 3.x ) # => pass in vcl_fetch renamed to hit_for_pass in Varnish 3.x config.pass_command = "pass" endapp/controllers/application_controller.rb
class ApplicationController < ActionController::Base include Lacquer::CacheUtils endconfig/varnishd.yml
development: listen: localhost:3001 telnet: localhost:6082 sbin_path: /usr/local/sbin storage: "file,#{Rails.root}/log/varnishd.#{Rails.env}.cache,100MB" test: listen: localhost:3002 telnet: localhost:6083 sbin_path: /usr/local/sbin storage: "file,#{Rails.root}/log/varnishd.#{Rails.env}.cache,100MB" production: listen: :80 telnet: localhost:6082 sbin_path: /usr/local/sbin storage: "file,#{Rails.root}/log/varnishd.#{Rails.env}.cache,100MB" params: overflow_max: 2000 # for Varnish 2.x ... use "queue_max: 2000" for Varnish 3.x thread_pool_add_delay: 2 thread_pools: 4 # <Number of cpu cores> thread_pool_min: 200 # <800/number of cpu cores> thread_pool_max: 4000If only some urls of the application should be cached by varnish, Lacquer::CacheControl will be helpful.
config/initializers/caches.rb
require "lacquer/cache_control" Lacquer.cache_control.configure do |config| config.register :static, :url => "^/images", :expires_in => "365d" config.register :static, :url => "^/stylesheets", :expires_in => "365d" config.register :static, :url => "^/javascripts", :expires_in => "365d" config.register :class_section, :url => "^(/[a-z]{2})?/(info_screens|class_sections)/%s.*$", :args => "[0-9]+", :expires_in => "1m" config.register :open_scoring, :url => "^(/[a-z]{2})?/class_sections/%s/open_scoring.*$", :args => "[0-9]+", :expires_in => "1m" endIn the sweeper we can do something like this
class_section = ClassSection.find(1) Lacquer.cache_control.purge(:open_scoring, class_section)This will purge “^(/[a-z]{2})?/class_sections/1/open_scoring.*$” (/sv/class_sections/1/open_scoring.js, /sv/class_sections/1/open_scoring.html)
The varnish.vcl is preprocssed when starting varnishd with the rake tasks
rake lacquer:varnishd:startconfig/varnish.vcl.erb
sub vcl_recv { # Lookup requests that we know should be cached if (<%= Lacquer.cache_control.to_vcl_conditions %>) { # Clear cookie and authorization headers, set grace time, lookup in the cache unset req.http.Cookie; unset req.http.Authorization; return(lookup); } # Generates # # if(req.url ~ "^/images" || # req.url ~ "^/stylesheets" || # req.url ~ "^/javascripts" || # req.url ~ "^(/[a-z]{2})?/(info_screens|class_sections)/[0-9]+.*$" || # req.url ~ "^(/[a-z]{2})?/class_sections/[0-9]+/open_scoring.*$") { # unset req.http.Cookie; # unset req.http.Authorization; # return(lookup); # } } sub vcl_fetch { <%= Lacquer.cache_control.to_vcl_override_ttl_urls %> # Generates # # if(req.url ~ "^/images" || req.url ~ "^/stylesheets" || req.url ~ "^/javascripts") { # unset beresp.http.Set-Cookie; # set beresp.ttl = 365d; # return(deliver); # } # # if(req.url ~ "^(/[a-z]{2})?/(info_screens|class_sections)/[0-9]+.*$" || # req.url ~ "^(/[a-z]{2})?/class_sections/[0-9]+/open_scoring.*$") { # unset beresp.http.Set-Cookie; # set beresp.ttl = 1m; # return(deliver); # } }This makes it much simpler to perform cacheing, it’s only setuped in one place, purge it or just let it expire.
Usage
To set a custom ttl for a controller:
before_filter { |controller| controller.set_cache_ttl(15.minutes) }Clearing the cache:
class Posts < ApplicationController after_filter :clear_cache, :only => [ :create, :update, :destroy ] private def clear_cache clear_cache_for( root_path, posts_path, post_path(@post)) end endControl varnishd with the following rake tasks
rake lacquer:varnishd:start rake lacquer:varnishd:stop rake lacquer:varnishd:restart rake lacquer:varnishd:status rake lacquer:varnishd:global_purgeGotchas
The default TTL for most actions is set to 0, since for most cases you’ll probably want to be fairly explicit about what pages do get cached by varnish. The default cache header is typically:
Cache-Control: max-age=0, no-cache, privateThis is good for normal controller actions, since you won’t want to cache them. If TTL for an action is set to 0, it won’t mess with the default header.
The key gotcha here is that cached pages strip cookies, so if your application relies on sessions and uses authenticity tokens, the user will need a session cookie set before form actions will work. Setting default TTL to 0 here will make sure these session cookies won’t break.
As a result, all you have to do to set a cacheable action is the before filter above.
Note on Patches/Pull Requests
Fork the project.
Make your feature addition or bug fix.
Add tests for it. This is important so I don’t break it in a future version unintentionally.
Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
Send me a pull request. Bonus points for topic branches.
Copyright
Copyright © 2010 Russ Smith. See LICENSE for details.
READMERack::Cache =========== Rack::Cache is suitable as a quick drop-in component to enable HTTP caching for Rack-based applications that produce freshness (Expires, Cache-Control) and/or validation (Last-Modified, ETag) information: * Standards-based (RFC 2616) * Freshness/expiration based caching * Validation (If-Modified-Since / If-None-Match) * Vary support * Cache-Control: public, private, max-age, s-maxage, must-revalidate, and proxy-revalidate. * Portable: 100% Ruby / works with any Rack-enabled framework * Disk, memcached, and heap memory storage backends For more information about Rack::Cache features and usage, see: http://tomayko.com/src/rack-cache/ Rack::Cache is not overly optimized for performance. The main goal of the project is to provide a portable, easy-to-configure, and standards-based caching solution for small to medium sized deployments. More sophisticated / high-performance caching systems (e.g., Varnish, Squid, httpd/mod-cache) may be more appropriate for large deployments with significant throughput requirements. Installation ------------ From Gem: $ sudo gem install rack-cache With a local working copy: $ git clone git://github.com/rtomayko/rack-cache.git $ rake package && sudo rake install Basic Usage ----------- Rack::Cache is implemented as a piece of Rack middleware and can be used with any Rack-based application. If your application includes a rackup (`.ru`) file or uses Rack::Builder to construct the application pipeline, simply require and use as follows: require 'rack/cache' use Rack::Cache, :metastore => 'file:/var/cache/rack/meta', :entitystore => 'file:/var/cache/rack/body', :verbose => true run app Assuming you've designed your backend application to take advantage of HTTP's caching features, no further code or configuration is required for basic caching. Using with Rails ---------------- Add this to your `config/environment.rb`: config.middleware.use Rack::Cache, :verbose => true, :metastore => 'file:/var/cache/rack/meta', :entitystore => 'file:/var/cache/rack/body' You should now see `Rack::Cache` listed in the middleware pipeline: rake middleware See the following for more information: http://snippets.aktagon.com/snippets/302 Using with Dalli ---------------- Dalli is a high performance memcached client for Ruby. More information at: https://github.com/mperham/dalli require 'dalli' require 'rack/cache' use Rack::Cache, :verbose => true, :metastore => "memcached://localhost:11211/meta", :entitystore => "memcached://localhost:11211/body" run app Links ----- Documentation: http://tomayko.com/src/rack-cache/ Mailing List: http://groups.google.com/group/rack-cache GitHub: http://github.com/rtomayko/rack-cache/ License ------- Copyright (c) 2008 Ryan Tomayko <http://tomayko.com/about> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
SeriousSerious is a blog engine inspired by other filesystem-based engines like jekyll (jekyllrb.com/) and toto (cloudhead.io/toto) and is based upon Sinatra and rack, thus can be hosted very easily (and for free) on heroku (heroku.com).
The articles are stored in plain text files with an opinionated naming scheme which is used for getting the date and permalink of your article: articles/2010-02-14-will-you-be-my-valentine.txt
The actual content of the article is lazy-loaded only when accessed, so things don’t get messy when a lot of articles has to be maintained. Articles consist of a YAML front, an optional summary and the body, so a basic article looks something like this:
title: My shiny article author: Christoph Olszowka Some nice summary. ~ ## Some content. You can use markdown in your articles, and also <%= "erb" %> <% highlight do %> puts "it will also syntax-highlight your codes" <% end %>There are quite a few assumptions made by this format: You have to specify your title in yaml format upfront. You can also specify an author for this article. If you don’t, it will fall-back to the default one (see configuration). Then two newlines must follow to separate the yaml from the actual content. After this, you can type your blog post. If you want a summary, add in the summary/body delimiter “~”, so Serious knows what you want.
Serious makes use of StupidFormatter (github.com/colszowka/stupid_formatter) for formatting your articles, so you get ERb, Markdown and Coderay syntax highlighting for free and can customize the processing chain to your liking, add custom ERb helpers and so on. See the documentation of stupid_formatter (rdoc.info/projects/colszowka/stupid_formatter) to learn how to customize the formatting.
Articles with a date in the future will not appear on the site, thus allowing you to draft or schedule posts. Note that you can also create a folder to store your drafts and once they are ready to be published just move it to the articles folder (this folder name can be changed in your configuration).
Getting started
Install the gem:
sudo gem install seriousYou can use the supplied generator using the serious executable provided with the gem. Type serious in your shell to see the available options.
By default the generator will create the app with gem-based public and views directories and initialize a git repository. To host your app on heroku instantly, supply the –heroku option, which will use the heroku gem to create your app and push it to heroku via git. Type:
serious my-fancy-blog --herokuAnd you can go to my-fancy-blog.heroku.com to see your new blog!
The setup
The directory basic directory structure of your Serious site would be something like this:
serious_blog/ - articles - 2010-02-14-will-you-be-my-valentine.txt - pages - about.txt - config.ru - Gemfile - RakefileThe config.ru is pretty straight-forward if you want to stick to the defaults:
require 'rubygems' require 'bundler' Bundler.require Serious.set :title, "My Sweet Little Blog" Serious.set :author, "Christoph Olszowka" Serious.set :url, 'http://mysweetlittleblog.heroku.com' run SeriousThe Gemfile to resolve dependencies (i.e. when hosting on heroku)
source :rubygems gem "serious"The Rakefile, which is obviously totally optional but highly recommended looks like this:
require 'serious' require 'serious/tasks'Supported Rubies
The gem tests are run against 1.8.7, REE, 1.9.1 and 1.9.2, so Serious should run fine at least on those interpreters - it is highly recommended to go with 1.9.2 though.
Creating heroku app manually
Assuming you’ve got the heroku gem installed and set up and you’ve set up git for your blog with git init or sticked with the generator, which created your git repo, you can now do:
heroku create mysweetlittleblog git push heroku masterPoint your browser to the url, and bang, you’re ready!
Running locally
You might also want to test your blog locally. Use the rake server command inside your site’s directory.
You can also use thin (sudo gem install thin) with:
thin -R config.ru startGo to localhost:3000 and enjoy.
Archives
The whole archives can be accessed at /archives. Archives by year, month and date are available at /2009 (all of 2009), /2009/05 (May 2009), /2009/05/15 (May 15th 2009).
Static pages
Static pages are quite similar to blog articles, in that their formatting and processing is the same. They have a yaml front matter, content that gets piped through the StupidFormatter and so on. The filename sans the extension serves as the permalink, pages can be reached via /pages/PERMALINK, so the content in pages/about.txt will be served at /pages/about
Rake tasks
If you’ve set up the Rakefile in your site’s main directory like mentioned above (this happens automatically when generating with the serious executable), you have the following tasks available:
rake article:create # Creates a new article rake article:validate # Validates all articles, making sure they can be processed correctly rake server # Runs a server hosting your site on localhost:3000 using rackupThe default is article:create, so to create a new article, just type rake, specify your title and optionally a date and you’re ready to go!
It’s highly recommended that you run the article:validate task before publishing, since it will make sure everything gets processed correctly.
Please be aware that you have to run the Rake tasks from the top level directory (where your config.ru file resides) since the config.ru file will be loaded to reflect your settings.
Creating routes by extending Serious
If you’d like to create your own routes or use other features from Sinatra you can do that by extending Serious. Create a ruby file in the root of your Serious blog. We’ll create our own class that extends Serious. Name the file and class however you like. For the following example we’ll go with a filename of app.rb and a class named MyApp.
require 'serious' class MyApp < Serious # define your custom routes end endFrom there all that is left is to update the config.ru file with the following:
require 'serious' require './app' # Serious config overrides here if any... run MyAppComments with disqus
You can activate comments for articles very easily with disqus (disqus.com) by setting the disqus property to your disqus-id (e.g. ‘myfancysite’). Disqus developer mode will be automatically activated for requests served by localhost so you can preview your settings and layout properly.
Serious.set :disqus, 'yourid'Google Analytics
You can activate Google Analytics by setting the google_analytics property to your tracker id (something like ‘UA-123123-5’). The required code will then be included to your site. For requests served from localhost, the inclusion is skipped.
Serious.set :google_analytics, 'UA-123123-5'Configuration options
Inside your config.ru, you can customize the settings for your Serious site.
Custom view templates or public folder
Changing the path to the public folder
Say you want to stick with the default view templates, but are willing to customize the css to make things prettier. You can do so. Get the provided css from lib/serious/site/public and point Serious to your new public folder, which assumingly lies in the current working directory (which is where your config.ru file is)
Serious.set :public, File.join(Dir.getwd, 'public')Serious will now serve the public directory from your custom location, but still get the views provided with the gem.
Changing the path to the views
Accordingly, if you want to stick with the default css, but want to customize the templates (would anyone want to do this?), specify the views path and get the provided ones from the gem as a starting point.
Serious.set :views, File.join(Dir.getwd, 'views')Setting the root
The most likely case though will surely be that you want to move both public and views into your site. Again, just copy over the provided assets from the gems lib/serious/site/ folder into your own site and modify them to your liking. You’ll have to specify a new root for your site, set to the current working directory, where your config.ru resides:
Serious.set :root, Dir.getwdNote that you do not have to specify the views and public folders separately, they’ll be hosted from the roots views and public subdirectory.
Setting the articles path
You want your articles hosted from your home directory or fancy a different folder name? Use the :articles property, which defaults to the articles subdirectory of the current working directory (a.k.a. where your config.ru sits)
Serious.set :articles, '/home/youruser/myblogposts'Setting the pages path
Similarly to the articles path, the pages will be served from your sites working directory’s subdirectory pages. Customize this with:
Serious.set :pages, '/home/youruser/mystaticpages'The title
The title is used for your atom feed, the site name and so on. It defaults to ‘Serious’ and you can specify it with:
Serious.set :title, "My Sweet Little Blog"The author
If you don’t want to specify the author for each article separately in the YAML front matter, you can define the blog author, which will be used as a fall-back when no custom article author is given in the YAML. It defaults to ‘unknown’
Serious.set :author, "Christoph Olszowka"The url
Well, your site has to know where it lives to provide proper links in your atom feed. Configure this with the url setting, which defaults to ‘localhost:3000’
Serious.set :url, 'http://localhost:3000'The date format
There is a helper in the Date class for formatting dates according to the configuration specified in Serious.date_format and which is used in the front-end. It defaults to “%B %o %Y”, which expands to (i.e.) ‘December 24th, 2009’. Notice that %o is a custom flag that is not built-in in the default strftime method of Date. It returns the ordinal name of the day. Customize with:
Serious.set :date_format, "%Y-%m-%d"Disqus comments
To enable disqus comments, specify your disqus id with the disqus property. Disqus is disabled by default. See section above for more information on disqus and comments.
Serious.set :disqus, 'yourdisqusid'Google Analytics
To enable google analytics, specify your tracker id at the property google_analytics. See section above for more information on Google Analytics.
Serious.set :google_analytics, 'UA-123123-5'Custom feed url in layout
If you want to specify a different feed url for the head link tag, for example to point your readers to Feedburner, you can do so by specifying the feed_url option and setting up your feed to be burned by feedburner based upon yoururl.com/atom.xml.
Serious.set :feed_url, 'http://feeds.feedburner.com/myfeedurl'Displayed items
You can specify how many items you want displayed across your site:
Amount of feed items
To customize the amount of items in your atom feed (living under /atom.xml), set the items_in_feed property to an integer. This defaults to 25.
Serious.set :items_in_feed, 50Amount of items with summary on index
On your index page, the most recent items will be displayed including the summary (or the whole post if you did not use the summary/body delimiter). This defaults to 3 items, but can be customized:
Serious.set :items_on_index, 5Amount of archive items on index
Below the items with summaries on your main page, there’s also a list of ‘archived’ items, which only includes the title and date. This defaults to 10 items, but can be customized as well:
Serious.set :archived_on_index, 10Cache timeout
All pages served are automatically getting a Cache-Control header, so they get cached in your visitor’s browsers as well as in Varnish on Heroku (docs.heroku.com/http-caching) (or some similar tool when you host yourself). The timeout is set to 300 seconds by default, but can be customized with:
Serious.set :cache_timeout, 300Article formatting
You can define the formatting chain for StupidFormatter with:
StupidFormatter.chain = [StupidFormatter::RDiscount]You’ll surely want to read the documentation of StupidFormatter (github.com/colszowka/stupid_formatter) to learn how to add your own formatters or erb helpers.
TODO
unescaped special chars in yaml front matter can lead to errors
text not readable in android
valid xhtml in demo setup
make summary delimiter configurable
improve caching
make it possible to host in subdirectories
allow for choice between erb/haml templates
Note on Patches/Pull Requests
Fork the project.
Make your feature addition or bug fix.
Add tests for it. This is important so I don’t break it in a future version unintentionally.
Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
Send me a pull request. Bonus points for topic branches.
Thanks
Alexis Sellier for toto, the main inspiration for this gem Ryan Bates for the great coderay css
Copyright
Copyright © 2010 Christoph Olszowka. See LICENSE for details.
BaseDESCRIPTION:
People love Base classes! They have tons of methods waiting to be used. Just check out ActiveRecord::Base's method list:
>> ActiveRecord::Base.methods.length => 572But why stop there? Why not have even more methods? In fact, let's put every method on one Base class!
So I did. It's called Base. Just subclass it and feel free to directly reference any class method, instance method, or constant defined on any module or class in the system. Like this:
class Cantaloupe < Base def embiggen encode64(deflate(SEPARATOR)) end end >> Cantaloupe.new.embiggen => "eJzTBwAAMAAw\n"See that embiggen method calling encode64 and deflate methods? Those come from the Base64 and Zlib modules. And the SEPARATOR constant is defined in File. Base don't care where it's defined! Base calls what it wants!
By the way, remember those 572 ActiveRecord methods? That's amateur stuff. Check out Base loaded inside a Rails app:
>> Base.new.methods.count => 5794It's so badass that it takes five seconds just to answer that question!
Base is just craaazzy! It's the most fearless class in all of Ruby. Base doesn't afraid of anything!
LICENSE:
Distributed under the union of the terms specified by all current OSI-approved licenses. In the event of a conflict, a die is to be rolled.
PRAISE FOR BASE
@garybernhardt @kantrn ... Can't tell if joke or just Ruby.
- @shazow
@garybernhardt y u troll soooo good? ;-)
- @amerine
@garybernhardt Imagine all the things you could have done doing not that
- @mrb_bk
@garybernhardt I hate you.
- @jmazzi
SHOULD I USE THIS IN MY SYSTEM?
Yes. I am being completely serious. You should.
Definitely.
Letters is a little alphabetical library that makes sophisticated debugging easy & fun. For many of us, troubleshooting begins and ends with the print statement. Others recruit the debugger, too. (Maybe you use print statements to look at changes over time but the debugger to focus on a small bit of code.)
World FlagsThis engine/gem can be used with Rails 3+. WorldFlags includes css files for flags of the following pixel sizes:
The sprites contains all the main country flags in the world.
You can use the sprite generator to generate sprites for other icons sets and follow the pattern for this gem.
You can also see the pictos-icons
or social_icons gems for other examples using this model.Also check out the world-flag-packs repo, that contains multiple flag packs ready for use with `world-flags.
Customizing Flag sprite size
Resize images:
Use ImageMagick to resize, using 32px version as base
convert flags32.png -resize 75% flags24.png
convert flags32_semi.png -resize 75% flags24_semi.png
Generate adsjusted css files:
The CSS files must have positioning adjusted to fit the new sprites: Use the resizing tool in lib/world-flags/tools/resize_css.rb Edit the last part where ResizeCss is initialized
resizer = ResizeCss.new 32, 24
The last argument (here 24) is the flag size used for calculating new positioning in the css files generated.
The ResizeCSS initializer takes many additional options, including :auto-exec to auto convert the sprite using ImageMagick.
resizer = ResizeCss.new 32, 24, auto-exec: true
See the code for more details on how to adjust to fit your needs.
Run the resizer
$ ruby resize_css.rb
TODO: refactor resize css tool into rake task or similar that takes arguments!
Configuration
In you asset application.css manifest file:
/* *= require_self *= require_tree . *= require flags/basic *= require flags/flags16 *= require flags/flags32 */The flags/basic stylesheet sets up a basic css for use with borders around the flag images (to mark selected language). Use this css as inspiration and customize by overriding styles as needed.
There is also support for semi-transparent flags. This can be used to fade certain flags while having the selected flag (or hovered over flag) in full brightness.
Simply add or remove the "semi" class for the flag to adjust the brightness level (fx for selection/mouse over).
Rendering
Flags will be rendered in HTML as:
<pre> <ul class="f32"> <li class="flag ar selected" data-cc="ar" data-country_name="Argentina" data-language_name="Spanish" data-locale="ar"> </li> ... </ul> </pre>The countries corresponding to the codes can be found at ISO 3166-1 alpha-2
Use
WorldFlags supports flag sizes in 16, 24, 32, 48 and 64 pixels (size).
You can also use built in helper methods:
= flag_list 16 do = flags :ar, :gb, :selected => :gbAlternatively
= flag_list 32 do = flag(:ar, 'Argentina') = flag(:gb, 'England', :selected => true)Or using the locale mappings for the label...
= flag_list 32 do = flag(:ar) = flag(:gb, :selected => true)Or using the #flag_code helper
= flag(:ar, 'Argentina') + flag(:gb, 'England', :selected => flag_code(I18n.locale)You can also include the :with_semi => true option in order to have flags not selected displayed with the 'semi' class (semi-bright image)
For use with tooltips or similar js plguins, it can be useful to set the
title attribute:= flag_list 32 do = flags :ar, :br, :gb, :title => trueThe flag_title will render the following list item:
<li class="flag ar" lang="ar" title="Argentina"> </li>Note: The ` is needed in order for the background (flag icon) to have something to be displayed against.
The :title and :content can also be set to a string which is then displayed
= flag :ar, 'Argentina', :title => 'Argentina country', :content => 'Argh!'or using the locale mapping...
= flag :ar, :title => 'Argentina country'To get content rendered for the
= flags :ar, :br, :gb, :content => trueNote: There is also a #flag_selected? helper, which is (and/or can be) used to determine if the flag to be drawn should have the "selected" class set)
Configuration
To disable use of country- and language name data attributes in output:
WorldFlags.country_name_disable! WorldFlags.language_name_disable!Customizing output
You can customize the output by the flag view helper methods:
WorldFlags.flag_list_tag = :div WorldFlags.flag_tag = :span WorldFlags.flag_text = ''To do more customization, look at the world_flags/helper/view/util.rb file.
Using localization
You can specify whether to look up labels for the flags for either language or country and for which locale to look up the labels (see Configuring localization)
Use danish (da) country labels
= flag_list 32 do = flags :ar, :br, :gb, :country => :daUse language labels for current locale. Note: Also use border class (see CSS below)
= flag_list 32, :class => 'border' do = flags :ar, :br, :gb, :language => I18n.localeIn the /config folder of this gem/engine there is a json file with all the english ISO-3166-2 code translations ready for use. You can make similar locale files for other locales/languages. You can use either of the following:
To generate i18n data for your particular locale(s).
Configuration and usage tips
See the wiki
Geo IP
Include the 'geoip' gem if you want to detect locale based on browser IP location.
Extract and put latest GeoIP.dat file in db/GeoIP.dat of your Rails app. Alternatively set location via WorldFlags.geo_ip_db_path = path.
The world-flags engine comes pre-packaged with a recent version of GeoIP.dat in the db folder of the engine itself.
GeoIP generator
Copy WorldFlags GeoIP.dat to Rails app '/db/GeoIP.dat'
TODO: Should optimally fetch the one from maxmind.com
rails g world_flags:geoip
Localhosts filtering
Also set WorldFlags.localhost_list if you have localhosts other than the default 127.0.0.1.
You can override the default ip passed to the Geo module by overriding thebrowser_ip` method in your controller. Here the default implementation is shown:
Fine control of the IP used
def browser_ip request.remote_ip endCode extract of the locale source logic for browser IP country code detection:
when :ip country_code_from_ip browser_ipCSS
The basic.css file in the vendor/assets/stylesheets/flags folder of this repo defines the basic styling of flags. Override these styles to fit your needs.
.flags { list-style-type: none; padding: 0; margin-left: 0; } .flags .l { float: left; } .border .flag { border: 1px solid; } .border .selected { border: 1px solid black; } .f16.flag { width: 16px; height: 16px; } .f24.flag { width: 24px; height: 24px; } /* and so on ... */TODO for version 1.0
Suggestions welcome ;)
Enjoy
Copyright (2012) Kristian Mandrup
mongoid_followablePlease refer to mongo_followable for latest version.
Installation
In console:
gem install mongoid_followableor in Gemfile:
gem 'mongoid_followable', "~> 0.1.9"Usage
To make model followable you need to include Mongoid::Followable into your document;Meanwhile, you also need to include Mongoid::Follower in your follower model:
class User include Mongoid::Document include Mongoid::Followable include Mongoid::Follower end class Group include Mongoid::Document include Mongoid::Followable endNow you can set authorization:
current_user.set_authorization('user', 'game') # now current_user cannot follow User and Game model current_user.unset_authorization('User', 'Game')And then you can follow a model:
@group = Group.new @group.save current_user.follow(@group) current_user.unfollow(@group)You can also judge whether a model is a follower of another model or a model is a followee of another model:
current_user.follower_of?(@group) current_user.followee_of?(@group)Moreover, it’s easy to get a model’s follower/followee count:
current_user.followers_count current_user.followees_countOf course, you can get a list of followers/followees:
User.followers_of(@group) User.followees_of(@group) @group.all_followers @user.all_followeesGetting a model’s followers/followees by type is also possible:
@group.followers_by_type("user") @user.followees_by_type("group")And their count:
@group.followers_by_type("user") @group.followers_count_by_type("user") @user.followees_by_type("group") @user.followees_count_by_type("group")You can also get a model’s follow/followed history:
@user.ever_follow @group.ever_followedAnother feature is to get a list of models which has the most followers/followees:
User.with_max_followees User.with_min_followees User.with_max_followees_by_type('group') User.with_min_followees_by_type('group') Group.with_max_followers Group.with_min_followers Group.with_max_followers_by_type('user') Group.with_min_followers_by_type('user')Now you can tell if two models have some common followers/followees by following methods:
@user.common_followees?(@another_user) @user.common_followers?(@group)And see what the common followers/followees are:
@user.common_followees_with(@another_user) @user.common_followers_with(@group)
Any bug or issue, please send me an email: ustc.flyingfox@gmail.com
TODO
inter-models followable #FINISHED#
divide into two parts: followable(being followed) and follower(following others) #FINISHED#
following history/followed history #FINISHED#
most/least followed/following #FINISHED
add authorization to followable models #FINISHED#
common followers/followees #FINISHED#
add support for mongo_mapper in next version
!!If you have any advice, plese do not hesitate to tell me!!
Thanks
Thanks the author(s) of acts_as_followable, you can find this gem here
Copyright
Copyright © Jie Fan. See LICENSE.txt for further details.
Voteable Mongovoteable_mongo allows you to make your Mongoid::Document or MongoMapper::Document objects voteable and tabulate votes count and votes point for you. For instance, in a forum, a user can vote up (or down) on a post or a comment. It’s optimized for speed by using only ONE database request per collection to validate, update, and retrieve updated data.
Initial idea based on cookbook.mongodb.org/patterns/votes.
Sample app at github.com/vinova/simple_qa.
Wonder how fast voteable_mongo is compare to other SQL & MongoDB solutions? Visit benchmarks at github.com/vinova/voteable_benchmarks
Why voteable_mongo?
There are various solutions for up / down voting problem (1, 2, 3, 4, …). Most of them using additional votes table (SQL) or votes collection (MongoDB) to store votes and do data tabulation on that votes table or votes collection.
voteable_mongo is different. It takes advantage of document-oriented database to store all related votes data inside voteable document. That has following benefits:
Don’t have to maintain additional votes table or votes collection.
When voteable document is loaded, all votes data related to it also be loaded, no more additional database requests to see how many votes this document got, who give up votes who give down vote, total vote points, votes count …
When vote up, vote down, revote, unvote, voteable_mongo validates vote data, updates voteable document and retrieves updated data using only ONE database request thanks to atomic findAndModify operation.
Atomic operations on single document warranty data integrity that makes sure if votes created / changed / deleted their associated counters and points will be updated.
So use voteable_mongo for less maintain cost, data integrity and save database requests for other tasks.
Sites using voteable_mongo
Installation
Rails 3.x
To install the gem, add this to your Gemfile
gem 'mongoid' gem 'voteable_mongo'After that, remember to run “bundle install”
Usage
Make Post and Comment voteable, User become the voter
Mongoid
post.rb
class Post include Mongoid::Document include Mongo::Voteable # set points for each vote voteable self, :up => +1, :down => -1 has_many :comments endcomment.rb
require 'post' class Comment include Mongoid::Document include Mongo::Voteable belongs_to :post voteable self, :up => +1, :down => -3 # each vote on a comment can affect votes count and point of the related post as well voteable Post, :up => +2, :down => -1 enduser.rb
class User include Mongoid::Document include Mongo::Voter endMongoMapper
post.rb
class Post include MongoMapper::Document include Mongo::Voteable # set points for each vote voteable self, :up => +1, :down => -1 many :comments endcomment.rb
require 'post' class Comment include MongoMapper::Document include Mongo::Voteable belongs_to :post voteable self, :up => +1, :down => -3 voteable Post, :up => +2, :down => -1 enduser.rb
class User include MongoMapper::Document include Mongo::Voter endMake a vote
@user.vote(@post, :up)Is equivalent to
@user.vote(:votee => @post, :value => :up) @post.vote(:voter => @user, :value => :up)In case you don’t need to init voter and / or votee objects you can
@user.vote(:votee_class => Post, :votee_id => post_id, :value => :down) @post.vote(:voter_id => user_id, :value => :up) Post.vote(:voter_id => user_id, :votee_id => post_id, :value => :up)Undo a vote
@user.unvote(@comment)If have voter_id, votee_id and vote value you don’t need to init voter and votee objects (suitable for API calls)
New vote
Post.vote(:voter_id => user_id, :votee_id => post_id, :value => :up)Re-vote
Post.vote(:voter_id => user_id, :votee_id => post_id, :value => :up, :revote => true)Un-vote
Post.vote(:voter_id => user_id, :votee_id => post_id, :value => :up, :unvote => true)Note: vote function always return updated votee object
Get vote_value
@user.vote_value(@post) @user.vote_value(:votee_class => Post, :votee_id => post_id) @post.vote_value(@user) @post.vote_value(user_id)Check if voted?
@user.voted?(@post) @user.voted?(:votee_class => Post, :votee_id => post_id) @post.voted_by?(@user) @post.voted_by?(user_id)Get votes counts and points
puts @post.votes_point puts @post.votes_count puts @post.up_votes_count puts @post.down_votes_countGet voters given voted object and voter class
@post.up_voters(User) @post.down_voters(User) @post.voters(User) - or - User.up_voted_for(@post) User.down_voted_for(@post) User.voted_for(@post)Get the list of voted objects of a class
Post.voted_by(@user) Post.up_voted_by(@user) Post.down_voted_by(@user)Utilities
Set counters and point to 0 for uninitialized voteable objects in order sort and query
Rails
rake mongo:voteable:init_statsRuby
Mongo::Voteable::Tasks::init_statsRe-generate counters and vote points in case you change :up / :down vote points
Rails
rake mongo:voteable:remake_statsRuby
Mongo::Voteable::Tasks.remake_statsMigrate from voteable_mongoid version < 0.7.0
Rails
rake mongo:voteable:migrate_old_votesRuby
Mongo::Voteable::Tasks.migrate_old_votesCredits
Copyright © 2010-2011 Vinova Pte Ltd
Licensed under the MIT license.
Getting StartedMiddleman is a command-line tool for creating static websites using all the shortcuts and tools of the modern web development environment.
Middleman assumes familiarity with the command-line. The Ruby language and the Sinatra web framework form the base of the tool. Familiarity with both will go a long way in helping users understand why Middleman works the way it does.
Installation
Middleman is distributed using the RubyGems package manager. This means you will need both the Ruby language runtime installed and RubyGems to begin using Middleman.
Mac OS X comes prepackaged with both Ruby and Rubygems, however, some of the Middleman's dependencies need to be compiled during installation and on OS X that requires Xcode. code can be installed via the Mac App Store. Alternately, if you have a free Apple Developer account, you can just install Command Line Tools for Xcode from their downloads page.
Once you have Ruby and RubyGems up and running, execute the following from the command line:
gem install middlemanThis will install Middleman, its dependencies and the command-line tools for using Middleman.
The installation process will add one new command to your environment, with 3 useful features:
- middleman init
- middleman server
- middleman build
The uses of each of these commands will be covered below.
Starting a New Site: middleman init
To get started we will need to create a project folder for Middleman to work out of. You can do this using an existing folder or have Middleman create one for you using the middleman init command.
Simply point the command at the folder for your new site and Middleman will build a skeleton project in that folder (or create the folder for you).
middleman init my_new_projectThe Skeleton
Every new project creates a basic web development skeleton for you. This automates the construction of a hierarchy of folders and files that you can use in all of your projects.
A brand-new project will contain a source folder and a config.rb file. The source folder is where you will build your website. The skeleton project contains folders for javascript, css and images, but you can change these to match your own personal preferences.
The config.rb file contains settings for Middleman and commented documentation on how to enable complex features such as compile-time compression and "blog mode."
Gemfile
Middleman will respect a Bundler Gemfile for locking down your gem dependencies. When creating a new project, Middleman will generate a Gemfile for you which specifies the same version of Middleman you are using. This will lock Middleman to this specific release series (the 2.x series, for example).
config.ru
A config.ru file describes how the site should be loaded by a Rack-enabled webserver. This file is provided as a convenience for users wishing to host their Middleman site in development mode on a Rack-based host such as Heroku.
To include a boilerplate config.ru file in your project, add the --rack flag to the init command:
middleman init my_new_project --rackProject Templates
In addition to the default basic skeleton, Middleman comes with an optional project template based on the HTML5 Boilerplate project. Alternative templates can be accessed using the -t or --template command-line flags. For example, to start a new project based on HTML5 Boilerplate, run this command:
middleman init my_new_boilerplate_project --template=html5Finally, you can create your own custom template skeletons by creating folders in the ~/.middleman/ folder. For example, I can create a folder at ~/.middleman/mobile/ and fill it with files I intend to use on mobile projects.
If you run middleman init with the help flag, you will see a list of all the possible templates it has detected:
middleman init --helpThis will list my custom mobile framework and I can create new projects based on it as before:
middleman init my_new_mobile_project --template=mobileThere are also a number of community-developed project templates.
The Development Cycle (middleman server)
The Middleman separates your development and production code from the start. This allows you to utilize a bevy of tools (such as HAML, SASS, etc) during development that are unnecessary or undesirable in production. We refer to these environments as The Development Cycle and the Static Site.
The vast majority of time spent using Middleman will be in the Development Cycle.
From the command-line, start the preview web-server from inside your project folder:
cd my_project bundle exec middleman serverThis will start a local web server running at: http://localhost:4567/
You can create and edit files in the source folder and see the changes reflected on the preview web-server.
You can stop the preview server from the command-line using CTRL-C.
Unadorned middleman command
Running middleman without any commands is the same as starting a server.
bundle exec middlemanThis will do exactly the same thing as middleman server.
When something goes wrong
Under some circumstances(one known case is under Windows, see here), middleman might not work as expected, try using a full command instead:
middleman server -p 4567 -e developmentUnder some circumstances(say if your config file has gone wild), middleman server might not be able to boot itself, and no error output can be seen on the console, don't panic, just try middleman build to see the full trace of the problem and fix it.
Exporting the Static Site (middleman build)
Finally, when you are ready to deliver static code or, in the case of "blog mode", host a static blog, you will need to build the site. Using the command-line, from the project folder, run middleman build:
cd my_project bundle exec middleman buildThis will create a static file for each file located in your source folder. Template files will be compiled, static files will be copied and any enabled build-time features (such as compression) will be executed. You can pass a --clean flag to middleman build to have it clean out any files from the build directory that are left over from earlier builds but would no longer be produced.
radiant/radiant, gma/nesta, comfy/comfortable-mexican-sofa, zena/zena, cantierecreativo/railsyardcms, magiclabs/alchemy_cms, svenfuchs/adva-cms2, DigitPaint/skyline, quickleft/regulate, spoiledmilk/casein3, gnuine/ubiquo, fabiokr/manageable_content, puffer/puffer_pages, hulihanapplications/Opal, zen-cms/Zen-Core, resolve/refinerycms, mindfire-solutions/mcms
This is a Rails e-commerce platform. Other e-commerce projects that use rails, don't use rails in a standard way. They use engines or are a separate framework altogether.
ROR ecommerce is a Rails 3 application with the intent to allow developers to create an ecommerce solution easily. This solution includes, an Admin for Purchase Orders, Product creation, Shipments, Fulfillment and creating Orders. There is a minimal customer facing shopping cart understanding that this will be customized. The cart allows you to track your customers cart history and includes a double entry accounting system.
The project has solr searching, compass and blueprint for CSS and uses jQuery. The gem list is quite large and the project still has a large wish list but it is the most complete solution for Rails today and it will only get better.
Vanity is an Experiment Driven Development framework for Rails.
A/B Testing With Rails (In 5 Easy Steps)
Step 1: Start using Vanity in your Rails application:
Rails 2.x configuration
Rails::Initializer.run do |config| gem.config "vanity" config.after_initialize do require "vanity" end endRails 3 configuration
Add to your Gemfile:
gem "vanity"If using a relational database, run the generator and migrations to create the database schema:
$ rails generate vanity $ rake db:migrateAdd to your application controller:
class ApplicationController < ActionController::Base use_vanity :current_user endStep 2: Define your first A/B test. This experiment goes in the file experiments/price_options.rb:
ab_test "Price options" do description "Mirror, mirror on the wall, who's the better price of all?" alternatives 19, 25, 29 metrics :signups end NOTE: If using a metric as above ("signups"), there needs to be a corresponding ruby file for that metric. Inside the "experiments" directory create a "metrics" directory with a file called "signups.rb". The contents of the file can describe the signup metric, refer to the "Metrics" Vanity documentation page for an example.Step 3: Present the different options to your users:
<h2>Get started for only $<%= ab_test :price_options %> a month!</h2>Step 4: Measure conversion:
class SignupController < ApplicationController def signup @account = Account.new(params[:account]) if @account.save track! :signups redirect_to @acccount else render action: :offer end end endStep 5: Check the report:
vanity report --output vanity.htmlRails 3
There is currently an issue with report generation. The vanity-talk Google Group has a couple posts that outline the issue for now. This is one of the posts: groups.google.com/group/vanity-talk/browse_thread/thread/343081a72a0cefb6
If you are collecting data (in development you need to opt-in to this by setting Vanity.playground.collecting = true in environments/development.rb) you can view experiment results with the vanity dashboard instead of the report.
The vanity dashboard setup instructions with Vanity work for Rails 3.x except the route is different. A Rails 3.x-style route would look like this:
`match '/vanity(/:action(/:id(.:format)))', :controller=>:vanity`Registering participants with Javascript
If robots or spiders make up a significant portion of your sites traffic they can affect your conversion rate. Vanity can optionally add participants to the experiments using asynchronous javascript callbacks, which will keep almost all robots out. To set this up simply do the following:
Add Vanity.playground.use_js!
Set Vanity.playground.add_participant_path = ‘/path/to/vanity/action’, this should point to the add_participant path that is added with Vanity::Rails::Dashboard, make sure that this action is available to all users
Add <%= vanity_js %> to any page that needs uses an ab_test. vanity_js needs to be included after your call to ab_test so that it knows which version of the experiment the participant is a member of. The helper will render nothing if the there are no ab_tests running on the current page, so adding vanity_js to the bottom of your layouts is a good option. Keep in mind that if you call use_js! and don’t include vanity_js in your view no participants will be recorded.
Contributing
Fork the project
Please use a topic branch to make your changes, it’s easier to test them that way
To set up the test suite run bundler, then run `rake appraisal:install` to prepare the test suite to run against multiple versions of Rails
Fix, patch, enhance, document, improve, sprinkle pixie dust
At minimum run `rake appraisal test`, if possible, please run rake test:all
Tests. Please. Run rake test, of if you can, rake test:all
Send a pull request on GitHub
Credits/License
Original code, copyright of Assaf Arkin, released under the MIT license.
Documentation available under the Creative Commons Attribution license.
For full list of credits and licenses: vanity.labnotes.org/credits.html.
Garbhttp://github.com/vigetlabs/garb
Important ChangesIt has now been nearly 6 months, I have removed the deprecated features listed below in master. I will release 0.9.2 shortly, with these features removed.
With The release of version 0.9.0 I have officially deprecated Garb::Report, Garb::Resource, Garb::Profile, and Garb::Account. Garb::Report and Garb::Resource should be replaced by Garb::Model. Garb::Profile and Garb::Account are supplanted by their Garb::Management::* counterparts.
I'll be working hard to update the documentation over the next day or so to highlight all of the old features in the new classes, as well as any new features brought by the new classes. If you are looking for something in particular, please open an issue and I will try to prioritize these requests.
Please read CHANGELOG
Description
Provides a Ruby API to the Google Analytics API.
http://code.google.com/apis/analytics/docs/gdata/gdataDeveloperGuide.html
Basic UsageSingle User Login
> Garb::Session.login(username, password)OAuth Access Token
> Garb::Session.access_token = access_token # assign from oauth gemAccounts, WebProperties, Profiles, and Goals
> Garb::Management::Account.all > Garb::Management::WebProperty.all > Garb::Management::Profile.all > Garb::Management::Goal.allProfiles for a UA- Number (a WebProperty)
> profile = Garb::Management::Profile.all.detect {|p| p.web_property_id == 'UA-XXXXXXX-X'}Define a Report Class
class Exits extend Garb::Model metrics :exits, :pageviews dimensions :page_path endGet the Results
> Exits.results(profile, :filters => {:page_path.eql => '/'})OR shorthand
> profile.exits(:filters => {:page_path.eql => '/'})Be forewarned, these numbers are for the last 30 days and may be slightly different from the numbers displayed in Google Analytics' dashboard for 1 month.
Other Parameters
- start_date: The date of the period you would like this report to start
- end_date: The date to end, inclusive
- limit: The maximum number of results to be returned
- offset: The starting index
Metrics & Dimensions
Metrics and Dimensions are very complex because of the ways in which they can and cannot be combined.
I suggest reading the google documentation to familiarize yourself with this.
http://code.google.com/apis/analytics/docs/gdata/gdataReferenceDimensionsMetrics.html#bounceRate
When you've returned, you can pass the appropriate combinations to Garb, as symbols.
Filtering
Google Analytics supports a significant number of filtering options.
http://code.google.com/apis/analytics/docs/gdata/gdataReference.html#filtering
Here is what we can do currently: (the operator is a method on a symbol for the appropriate metric or dimension)
Operators on metrics:
eql => '==', not_eql => '!=', gt => '>', gte => '>=', lt => '<', lte => '<='Operators on dimensions:
matches => '==', does_not_match => '!=', contains => '=~', does_not_contain => '!~', substring => '=@', not_substring => '!@'Given the previous Exits example report in shorthand, we can add an option for filter:
profile.exits(:filters => {:page_path.eql => '/extend/effectively-using-git-with-subversion/')SSL
Version 0.2.3 includes support for real ssl encryption for SINGLE USER authentication. First do:
Garb::Session.login(username, password, :secure => true)Next, be sure to download http://curl.haxx.se/ca/cacert.pem into your application somewhere. Then, define a constant CA_CERT_FILE and point to that file.
For whatever reason, simply creating a new certificate store and setting the defaults would not validate the google ssl certificate as authentic.
TODOS
- rebuild AND/OR filtering in Garb::Model
Requirements
- crack >= 0.1.6
- active_support >= 2.2.0
Requirements for Testing
Install
gem install garb OR with bundler: gem 'garb' and `bundle install`Contributors
Many Thanks, for all their help, goes to:
- Patrick Reagan
- Justin Marney
- Nick Plante
- James Cook
License
(The MIT License)
Copyright (c) 2011 Viget Labs
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
GoogleVisualrThis Ruby gem, GoogleVisualr, is a wrapper around the Google Chart Tools that allows anyone to create the same beautiful charts with just plain Ruby.
You don’t have to write any JavaScript at all!
Good for any Ruby on Rails setup whereby you prefer to work your logic in models or controllers.
Please refer to the GoogleVisualr API Reference site for a complete list of Google charts that you can create with GoogleVisualr.
The Gist
In your model or controller, write Ruby code to create your chart (e.g. Area Chart, Bar Chart, even Spark Lines etc).
Configure your chart with any of the options as listed in Google Chart Tools’ API Docs. E.g. Area Chart’s Configuration Options.
In your view, invoke a chart.to_js(div_id) method and that will magically generate and insert JavaScript into the final HTML output.
You get your awesome Google chart, and you didn’t write any JavaScript!
Limitations
GoogleVisualr is created solely for the aim of simplifying the display of a chart, and not the interactions.
Hence, do note that GoogleVisualr is not a 100% complete wrapper for Google Chart Tools.
For example, Methods and Events as described in Google Chart Tools’ API Docs, for use after a chart has been rendered, are not implemented because they felt more native being written as JavaScript functions, within views or .js files.
Differences Between Gem and Plugin
Gem
is Rails 3 compatible.
implements many of the newer versions of Google Charts E.g. Core Charts.
enjoyed a major refactoring, and also now has Tests!
Plugin
has not been tested for Rails 3 compatibility
implements the older versions of Google Charts.
has deprecated methods.
This gem, however, is not a drop-in replacement for the plugin, as there are numerous changes in its usage.
Notably, you will have to interact mostly with GoogleVisualr::DataTable which was absent in the plugin.
InstallAssuming you are on Rails 3, include the gem in your Gemfile.
gem "google_visualr", ">= 2.1"BasicsThis section describes a basic implementation of the GoogleVisualr::DataTable and GoogleVisualr::AreaChart classes.
For detailed documentation and advanced implementations, please refer to the GoogleVisualr API Reference site or read the source.
In your Rails layout, load Google Ajax API in the head tag, at the very top.
<script src='http://www.google.com/jsapi'></script>In your Rails controller, initialize a GoogleVisualr::DataTable object with an empty constructor.
data_table = GoogleVisualr::DataTable.newPopulate data_table with column headers, and row values.
# Add Column Headers data_table.new_column('string', 'Year' ) data_table.new_column('number', 'Sales') data_table.new_column('number', 'Expenses') # Add Rows and Values data_table.add_rows([ ['2004', 1000, 400], ['2005', 1170, 460], ['2006', 660, 1120], ['2007', 1030, 540] ])Create a GoogleVisualr::AreaChart with data_table and configuration options.
option = { width: 400, height: 240, title: 'Company Performance' } @chart = GoogleVisualr::Interactive::AreaChart.new(data_table, option)In your Rails view, render the Google chart.
<div id='chart'></div> <%= render_chart(@chart, 'chart') %>SupportPlease feel free to fork the project, make improvements or bug fixes and submit pull requests. Ideally, all pull requests should also come with tests!
Please submit all feedback, bugs and feature-requests to GitHub Issues Tracker.
Change LogVersion 2.1.3
Added support for Bubble Chart.
Pull Request 37 Added support for Stepped Area Chart.
Version 2.1.2
Pull Request 28 Removed InstanceMethods as it’s deprecated in Rails 3.2.x.
Pull Request 26 Added 3 more image charts and #uri method to all image charts.
Version 2.1.1
Added support for role and pattern attributes in #new_column and #new_columns methods.
Version 2.1.0
Author
Added #render_chart as a helper method in Rails views.
GoogleVisualr is maintained by Winston Teo.
Who is Winston Teo? You should follow Winston on Twitter, or find out more on WinstonYW and LinkedIn.
LicenseCopyright © 2011 Winston Teo Yong Wei. Free software, released under the MIT license.
AnalyticalGem for managing multiple analytics services in your rails app.
Service implementations include:
Usage
Add the following to your controllers:
analyticalAdd a configuration file (config/analytical.yml) to declare your API keys, like so:
production: google: key: UA-5555555-5 clicky: key: 55555 development: test:You can add different configurations for different environments.
By default, all the declared analytics modules are loaded. You can override this behavior in the controller.
analytical :modules=>[:console, :google, :clicky]Then, in your template files, you’ll want to add the analytical helper methods for initializing the tracking javascript:
<!DOCTYPE html> <html> <head> <%= raw analytical.head_prepend_javascript %> <title>Example</title> <%= stylesheet_link_tag :all %> <%= javascript_include_tag :defaults %> <%= csrf_meta_tag %> <% analytical.identify '5', :email=>'josh@transfs.com' %> <%= raw analytical.head_append_javascript %> </head> <body> <%= raw analytical.body_prepend_javascript %> <%= yield %> <%= raw analytical.body_append_javascript %> </body> </html>Note the example above also includes an identify() command that will apply to every page. More likely, you’ll want to make this identify() command conditional so that it only applies when you have a logged-in user:
<% analytical.identify(current_user.id, :email=>current_user.email) if current_user %>You can sprinkle the track() and event() tracking methods throughout your views & controllers as needed.
analytical.track '/a/really/nice/url' analytical.event 'Some Awesome Event', :with=>:dataIt’s possible to conditionally disable analytics by specifying a `disable_if` method.
analytical :disable_if => lambda{ |controller| controller.i_can_haz_tracking? }Analytical also provides the ability to filter your active modules dynamically:
analytical :filter_modules => lambda{ |controller, modules| controller.use_google_analytics? ? modules : modules - [:google] }This can be useful for enabling the console logger optionally in your app, based on a request parameter:
:filter_modules => lambda { |controller, modules| controller.use_console_logger? ? [:console] : modules }Adding new modules
New modules should be fairly easy to add. Follow the structure that I’ve used in the Clicky, Google, and KISSMetrics modules… and you should be fine. All modules should include the Analytical::Base::Api module, so that they have some default behavior for methods that they don’t support or need.
Session-based command queues
By default, any Analytical commands that are queued in a controller that subsequently redirects won’t be emitted to the client. This is because the redirect triggers a new request, and everything is cleared out at the beginning of each request.
However, if you would like to be able to queue commands between requests… there’s a new option that supports this behavior:
analytical :modules=>[:console, :google], :use_session_store=>trueThis will store the queued commands in the user session, clearing them out when they are emitted to the client, but allowing you to make calls like:
analytical.track 'something'… in your controller. After a redirect, the corresponding track() call will be emitted in the next request made by the client. NOTE: This is new and somewhat experimental, and could cause problems if you store large amounts of data in the session. (there is a 4k limit to session data)
Javascript tracking/event commands
Sometimes you want to trigger an analytics track/event via javascript. Analytical now provides a solution for this by default. Appended to your <head> is a simple javascript object that will contain “instantaneous” versions of the tracking commands for each of your modules.
You call these analytical commands like this:
<script type="text/javascript">Analytical.track('/some/url');</script> <script type="text/javascript">Analytical.event('A javascript event', {with: 'data'});</script>When you call these commands, it will pass the track/event name & data on to each of the modules. (Take a look at the simple javascript helpers in your <head> for more information.)
To disable this functionality, use
analytical :javascript_helpers => falseNote on Patches/Pull Requests
I would be extremely happy to accept contributions to this project! If you think something should work differently, send me a message and I’d love to discuss your ideas. Even better:
Fork the project.
Make your feature addition or bug fix.
Add specs for it. This is important so I don’t break it in a future version unintentionally.
Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
Send me a pull request. Bonus points for topic branches.
Current Contributors
These fine folks have contributed patches and new features to this project:
Thanks guys!
Copyright
Copyright © 2010 Joshua Krall. See LICENSE for details.
ThumbsUpNote: Version 0.5.x is a breaking change for #plusminus_tally and #tally, with > 50% speedups.
A ridiculously straightforward and simple package 'o' code to enable voting in your application, a la stackoverflow.com, etc. Allows an arbitrary number of entities (users, etc.) to vote on models.
Mixins
This plugin introduces three mixins to your recipe book:
- acts_as_voteable : Intended for content objects like Posts, Comments, etc.
- acts_as_voter : Intended for voting entities, like Users.
- has_karma : Adds some helpers to acts_as_voter models for calculating karma.
Inspiration
This plugin started as an adaptation / update of vote_fu for use with Rails 3. It adds some speed, removes some cruft, and is adapted for use with ActiveRecord / Arel in Rails 3. It maintains the awesomeness of the original vote_fu.
InstallationRequire the gem:
gem 'thumbs_up'Create and run the ThumbsUp migration:
rails generate thumbs_up rake db:migrateUsageGetting Started
Turn your AR models into something that can be voted upon.
class SomeModel < ActiveRecord::Base acts_as_voteable end class Question < ActiveRecord::Base acts_as_voteable endTurn your Users (or any other model) into voters.
class User < ActiveRecord::Base acts_as_voter # The following line is optional, and tracks karma (up votes) for questions this user has submitted. # Each question has a submitter_id column that tracks the user who submitted it. # The option :weight value will be multiplied to any karma from that voteable model (defaults to 1). # You can track any voteable model. has_karma(:questions, :as => :submitter, :weight => 0.5) end class Robot < ActiveRecord::Base acts_as_voter endTo cast a vote for a Model you can do the following:
Shorthand syntax
voter.vote_for(voteable) # Adds a +1 vote voter.vote_against(voteable) # Adds a -1 vote voter.vote(voteable, vote) # Adds either a +1 or -1 vote: vote => true (+1), vote => false (-1) voter.vote_exclusively_for(voteable) # Removes any previous votes by that particular voter, and votes for. voter.vote_exclusively_against(voteable) # Removes any previous votes by that particular voter, and votes against. vote.unvote_for(voteable) # Clears all votes for that userQuerying votes
Did the first user vote for the Car with id = 2 already?
u = User.first u.vote_for(Car.find(2)) u.voted_on?(Car.find(2)) #=> trueDid the first user vote for or against the Car with id = 2?
u = User.first u.vote_for(Car.find(2)) u.voted_for?(Car.find(2)) #=> true u.voted_against?(Car.find(2)) #=> falseTallying Votes
You can easily retrieve voteable object collections based on the properties of their votes:
@items = Item.tally.limit(10).where('created_at > ?', 2.days.ago).having('COUNT(votes.id) < 10')Or for MySQL:
@items = Item.tally.limit(10).where('created_at > ?', 2.days.ago).having('vote_count < 10')This will select the Items with less than 10 votes, the votes having been cast within the last two days, with a limit of 10 items. This tallies all votes, regardless of whether they are +1 (up) or -1 (down). The #tally method returns an ActiveRecord Relation, so you can chain the normal method calls on to it.
Tallying Rank ("Plusminus")
You most likely want to use this over the normal tally
This is similar to tallying votes, but this will return voteable object collections based on the sum of the differences between up and down votes (ups are +1, downs are -1). For Instance, a voteable with 3 upvotes and 2 downvotes will have a plusminus_tally of 1.
@items = Item.plusminus_tally.limit(10).where('created_at > ?', 2.days.ago).having('plusminus_tally > 10')Lower level queries
positiveVoteCount = voteable.votes_for negativeVoteCount = voteable.votes_against # Votes for minus votes against. If you want more than a few model instances' worth, use `plusminus_tally` instead. plusminus = voteable.plusminus voter.voted_for?(voteable) # True if the voter voted for this object. voter.vote_count(:up | :down | :all) # returns the count of +1, -1, or all votes voteable.voted_by?(voter) # True if the voter voted for this object. @voters = voteable.voters_who_votedOne vote per user!
ThumbsUp by default only allows one vote per user. This can be changed by removing:
In vote.rb:
validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id]In the migration, the unique index:
add_index :votes, ["voter_id", "voter_type", "voteable_id", "voteable_type"], :unique => true, :name => "uniq_one_vote_only"You can also use --unique-voting false when running the generator command:
rails generate thumbs_up --unique-voting falseTesting ThumbsUp
Testing is a bit more than trivial now as our #tally and #plusminus_tally queries don't function properly under SQLite. To set up for testing:
$ mysql -uroot # You may have set a password locally. Change as needed. > GRANT ALL PRIVILEGES ON 'thumbs_up_test' to 'test'@'localhost' IDENTIFIED BY 'test'; > CREATE DATABASE 'thumbs_up_test'; > exit; $ rake # Runs the test suite.CreditsBasic scaffold is from Peter Jackson's work on VoteFu / ActsAsVoteable. All code updated for Rails 3, cleaned up for speed and clarity, karma calculation fixed, and (hopefully) zero introduced bugs.
Letter OpenerPreview email in the browser instead of sending it. This means you do not need to set up email delivery in your development environment, and you no longer need to worry about accidentally sending a test email to someone else’s address.
Rails Setup
First add the gem to your development environment and run the bundle command to install it.
gem "letter_opener", :group => :developmentThen set the delivery method in config/environments/development.rb
config.action_mailer.delivery_method = :letter_openerNow any email will pop up in your browser instead of being sent. The messages are stored in tmp/letter_opener.
Non Rails Setup
If you aren’t using Rails, this can be easily set up with the Mail gem. Just set the delivery method when configuring Mail and specify a location.
require "letter_opener" Mail.defaults do delivery_method LetterOpener::DeliveryMethod, :location => "tmp/letter_opener" endProject Status
Unfortunately I have not had time to actively work on this project recently. If you find a critical issue where it does not work as documented please ping me on Twitter and I’ll take a look.
Development & Feedback
Questions or comments? Please use the issue tracker. If you would like to contribute to this project, clone this repository and run bundle and rake to run the tests.
Special thanks to the mail_view gem for inspiring this project and for their mail template.
premailer-rails3This gem is a no config solution for the wonderful Premailer gem to be used with Rails 3. It uses interceptors which were introduced in Rails 3 and tweaks all mails which are delivered and adds a plain text part to them and inlines all CSS rules into the HTML part.
By default it inlines all the CSS files that are linked to in the HTML:
<link type='text/css' ... />Don't worry about the host in the CSS URL since this will be ignored.
If no CSS file is linked to in the HTML and no inline <style type="text/css"> is presnet, it will try to load a default CSS file email.css.
Every CSS file (including the default email.css) is loaded from within the app. The retrieval of the file depends on your assets configuration:
Rails 3.1 asset pipeline: It will load the compiled version of the CSS asset which is normally located in app/assets/stylesheets/. If the asset can't be found (e.g. it is only available on a CDN and not locally), it will be HTTP requested.
Classic static assets: It will try to load the CSS file located in public/stylesheets/
Hassle: It will try to load the compiled CSS file located in the default Hassle location tmp/hassle/stylesheets/
Installation
Simply add the gem to your Gemfile in your Rails project:
gem 'premailer-rails3'premailer-rails3 requires either nokogiri or hpricot. It doesn't list them as a dependency so you can choose which one to use.
gem 'nokogiri' # or gem 'hpricot'If both are loaded for some reason, premailer chooses hpricot.
That's it!
Configuration
Premailer itself accepts a number of options. In order for premailer-rails3 to pass these options on to the underlying premailer instance, specify them in an initializer:
PremailerRails.config.merge!(:preserve_styles => true, :remove_ids => true)For a list of options, refer to the Premailer documentation
The default configs are:
{ :input_encoding => 'UTF-8', :inputencoding => 'UTF-8', :generate_text_part => true }The input encoding option changed at some point. To make sure this option works regardless of the premailer version, the old and new setting is specified. If you want to use another encoding make sure to specify the right one or both.
If you don't want to generate a text part from the html part, set the config :generate_text_part to false.
Note that the options :with_html_string and :css_string are used to make this gem work and will thus be overridden.