Saturday, 26 May 2012

observers


node.js + Socket.io + Bash. A collaborative terminal for your browser

//
// This server will start a bash shell and expose it
// over socket.io to a browser. See ./term.html for the
// client side.
//
// You should probably:
//
// npm install socket.io
// curl -O https://github.com/LearnBoost/Socket.IO/raw/master/socket.io.min.js
//
// To get socket.io in the node_modules directory and
// the socket.io.min.js file needed for the client.
//
// To start the server:
//
// node server.js
//
// And then load up your term!
//
// open http://`hostname`:8080/term.html
//
// You can even share the url with a friend on your
// local network. Be sure they're a friend though :-)
//
var http = require('http'),
url = require('url'),
path = require('path'),
fs = require('fs'),
io = require('./node_modules/socket.io'),
sys = require('sys'),
util = require('util'),
spawn = require('child_process').spawn;
var sh = spawn('bash');
sh.stdout.on('data', function(data) {
listener.broadcast(data);
});
sh.stderr.on('data', function(data) {
listener.broadcast(data);
});
sh.on('exit', function (code) {
listener.broadcast('** Shell exited: '+code+' **');
});
server = http.createServer(function(request, response){
var uri = url.parse(request.url).pathname;
var filename = path.join(process.cwd(), uri);
path.exists(filename, function(exists) {
if (!exists) {
response.writeHead(404, {'Content-Type':'text/plain'});
response.end("Can''t find it...");
}
fs.readFile(filename, 'binary',function(err, file){
if (err) {
response.writeHead(500, {'Content-Type':'text/plain'});
response.end(err + "\n");
return;
}
response.writeHead(200);
response.write(file, 'binary');
response.end();
});
});
}
);
server.listen(8080);
var listener = io.listen(server);
listener.on('connection', function(client){
client.on('message', function(data){
sh.stdin.write(data+"\n");
listener.broadcast(new Buffer("> "+data));
});
});
view raw server.js hosted with ❤ by GitHub
<!doctype html>
<html>
<head>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script src="socket.io.min.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" charset="utf-8">
function add_content(str){
console.log(str);
$('div').append('<p>' + $('<div/>').text(str).html() + '</p>');
$(window).scrollTop($('body').height() - $(window).height() + 80);
}
var socket = new io.Socket(null, {rememberTransport: false, port: 8080});
socket.connect();
socket.addEvent('message', function(data) {
add_content($.map(data, function(e,i) {
return String.fromCharCode(e);
}).join(''));
});
$(function(){
$('form').submit(function(e){
var input = $('input');
socket.send(input.val());
input.val('');
return false;
});
$('input').focus();
});
</script>
<style type="text/css">
body, p, input {
font-family: fixed;
font-size: 13px;
border: none;
}
p { white-space: pre; }
p, form, input { margin:0; padding:0; }
input:focus { outline: none; border: none; }
</style>
</head>
<body>
<div></div>
<form>
&gt; <input>
</form>
</body>
</html>
view raw term.html hosted with ❤ by GitHub
https://gist.github.com/947512

Thursday, 24 May 2012

Tuesday, 22 May 2012

Unobtrusive Javascript

<!DOCTYPE html>
<html>
<head>
<title>UJS Example</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" charset="utf-8">
$(function() {
$("#alert").click(function() {
alert(this.getAttribute("data-message"));
return false;
})
})
</script>
</head>
<body>
<h1><a href="#" id="alert" data-message="Hello UJS!">Click Here</a></h1>
</body>
</html>
view raw gistfile1.html hosted with ❤ by GitHub

Monday, 21 May 2012

workng with meteor

Try it yourself In about 3 minutes, you'll make your own copy of Leaderboard and deploy it live on the Internet for you and your friends to use. No programming knowledge required! Pick a name for your new app. I'll call it... .meteor.com new suggestion Install Meteor (if you haven't already). In your Terminal window, run: $ curl install.meteor.com | sh Make a copy of the example. $ meteor create --example leaderboard Get it running on the cloud. $ cd leaderboard $ meteor deploy testing-app.meteor.com Now, open testing-app.meteor.com in a new tab in your web browser. There's your app!
➜ Development cd JavaScripts
➜ JavaScripts curl install.meteor.com | sh
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 5238 0 5238 0 0 4497 0 --:--:-- 0:00:01 --:--:-- 7580
Installing Meteor to /usr/local/meteor
... downloading
######################################################################## 100.0%
Meteor installed! To get started fast:
$ meteor create ~/my_cool_app
$ cd ~/my_cool_app
$ meteor
Or see the docs at:
docs.meteor.com
➜ JavaScripts meteor create --example leaderboard
leaderboard: created.
To run your new app:
cd leaderboard
meteor
➜ JavaScripts cd leaderboard
➜ leaderboard meteor deploy testing-app.meteor.com
zsh: correct 'meteor' to '.meteor' [nyae]? n
Deploying to testing-app.meteor.com. Bundling ... uploading ... done.
Now serving at testing-app.meteor.com
➜ leaderboard
view raw gistfile1.txt hosted with ❤ by GitHub
http://meteor.com/examples/leaderboard

Push Service


Push Example
=========================================

create new server and client for push/sub notifications
----------------
client: http://stream-channel.herokuapp.com/
server: http://stream-server.herokuapp.com/



code:https://github.com/msroot/push-sub-example-ror

Sunday, 20 May 2012

High performance Publish/Subscribe solution for Ruby On Rails or Juggernaut vs. Faye


In this post I want to share some experience in pub/sub for RoR. First, I'll briefly explain what kind of application we have, why we've chosen Faye and then I'll say how to run many instances of Faye without any Load Balancers and without Redis. 

One of RoR apps I've developed is handling more than 30 000 RMP (requests per minute) while i'm writing this post (and it is ready to handle more). About 90% of requests do pushes to Faye. About 6 000 users are connected to Faye right now. Everything is working very stable and smooth. And we are going to grow :)

But before Faye we used Juggernaut. These technologies are pretty same. And both are easy to use and integrate with Rails app. We've choosen Juggernaut because it was... more popular I think. So, we've patched it a little bit for our needs and quickly integrated to our application. Everything was good untill first deployment to production with less than hundred online users. It loaded servers - but we were ready to accepted it. But then we found another issue - it was very unstable. If somebody pushes broken data to it - process dies. Totally dies. So we've added monit to monitor Juggernaut. Not really cool, right? We've been patching it a lot. Still, it was not stable and sometimes it was using so much CPU that we've decided to look on another solution. And here we've found out Faye.

Before describing Faye I want to say that Juggernaut has 1 great benefit which Faye doesn't have - you can push events directly to Redis and Juggernaut will catch it and process. You can't easily do the same with Faye (maybe it will be done in future). Instead of it you have to send a HTTP request, which is slower and loads Faye's server.

So, we've decided to switch to Faye. We've choosen node.js instead of Thin and after first deployment we found the difference - Faye is stable and doesn't load system at all.

And now, finally, about high-performance and scalability.

Both Node.js and Thin are very-very slow if you run it with SSL - check my next post to see how to solve this problem

First of all, you need many instances of Faye to support a lot of concurrent users. Faye supports Redis as a  shared storage (in experimental mode, but it seems to be stable). It gives you a possibility to run many instances on many servers but it's slow - it needs to communicate with Redis. So, we've decided to create our own simple mechanism of sharding instead of using Redis.And we didn't want to use one more Load Balancer for it.

Note: this mechanism is designed to work when user subscribes to his own channel. To push data to global channel you need to perform requests to each shard.

Let's say we have a domain name app.com pointing to Load Balancer for Rails app. All our servers have sub-domains like server1.app.com, server2.app.com etc.

Configuration files:

We've created a YAML config where we listed all our instances. It looks like:
  production:
    shards:
      -
        node_port: 42000
        node_host: server1.app.com
        node_local_host: 10.x.x.1 #local IP of server
        run_on: server1_hostname

      -
        node_port: 42001
        node_host: server1.app.com
        node_local_host: 10.x.x.1
        run_on: server1_hostname
      -
        node_port: 42000
        node_host: server2.app.com
        node_local_host: 10.x.x.2
        run_on: server2_hostname
  .....

run_on option is used by our own Rake task to detect what shard to start on specific server during deployment. 
node_host is a public domain name or IP - we are using it to generate URL for users.
node_local_host is a local IP of server, cause we want to push data through local interfaces.

Sharding:

We assign shard for user by very simple formula:
   shard = @shards[user.id % @shards.size]

If you have 3 shards, users with ids 0, 3, 6 are connected to 1st shard; users with id's 1, 4, 7 - to 2nd...

Client side code:
client = new Faye.Client(<%= raw Faye.shard_for(user).url.inspect %>);
client.subscribe(<%= raw Faye.shard_for(user).channel.inspect %>, function(data){...});

Method .url returns URL like http://server1.app.com:42000/faye

Channel for user is just a "/#{user.id}", eg. '/123'.
So, now we need to push events to the needed shard:
...
  uri = URI.parse(Faye.shard_for(user).local_url)
  http = Net::HTTP.new(uri.host, uri.port)
  req = Net::HTTP::Post.new uri.path
  body = {'channel' => Faye.shard_for(user).channel, 'data' => data.to_json, 'ext' => {:auth_token => FAYE_TOKEN}}
  req.set_form_data('message' => body.to_json)
  http.request req
...

Method .local_url returns http://10.x.x.1:42000/faye.


https://github.com/ntenisOT/Faye-Example-Application
http://rails-alex.blogspot.com.au/2011/10/high-performance-publishsubscribe.html
http://railscasts.com/episodes/316-private-pub

Friday, 18 May 2012

setInterval vs setTimeout

var data ="testing or not to testing?";
function test(data) { console.log(data);}
var is_a_tune=self.setInterval("test(data)",1000);
// Stop
is_a_tune=window.clearInterval(is_a_tune)
var is_a_tune=self.setTimeout("test(data)",1000);
is_a_tune=window.clearTimeout(is_a_tune);
//Console:
var id ="testing or not to testing?";
undefined
function test(id) { console.log(id);}
undefined
var data ="testing or not to testing?";
undefined
function test(data) { console.log(data);}
undefined
var is_a_tune=self.setInterval("test(data)",1000);
undefined
6testing or not to testing?
is_a_tune=window.clearInterval(is_a_tune)
undefined
var is_a_tune=self.setTimeout("test(data)",1000);
undefined
testing or not to testing?
var is_a_tune=self.setTimeout("test(data)",100);
undefined
testing or not to testing?
var is_a_tune=self.setTimeout("test(data)",10000);
undefined
is_a_tune=window.clearTimeout(is_a_tune);
undefined
view raw gistfile1.js hosted with ❤ by GitHub
setInterval = Runs EVERY x secs setTimeout = Runs After x secs

Tuesday, 15 May 2012

rake stats

➜ video_app git:(master) ✗ bundle exec rake -T
rake about # List versions of all Rails frameworks and the environment
rake bourbon:install[sass_path] # Move files to the Rails assets directory.
rake db:create # Create the database from config/database.yml for the current Rails.env (use db:create:all to create all dbs in the config)
rake db:drop # Drops the database for the current Rails.env (use db:drop:all to drop all databases)
rake db:fixtures:load # Load fixtures into the current environment's database.
rake db:migrate # Migrate the database (options: VERSION=x, VERBOSE=false).
rake db:migrate:status # Display status of migrations
rake db:rollback # Rolls the schema back to the previous version (specify steps w/ STEP=n).
rake db:schema:dump # Create a db/schema.rb file that can be portably used against any DB supported by AR
rake db:schema:load # Load a schema.rb file into the database
rake db:seed # Load the seed data from db/seeds.rb
rake db:setup # Create the database, load the schema, and initialize with the seed data (use db:reset to also drop the db first)
rake db:structure:dump # Dump the database structure to an SQL file
rake db:version # Retrieves the current schema version number
rake doc:app # Generate docs for the app -- also availble doc:rails, doc:guides, doc:plugins (options: TEMPLATE=/rdoc-template.rb, TITLE="Custom Title")
rake log:clear # Truncates all *.log files in log/ to zero bytes
rake middleware # Prints out your Rack middleware stack
rake notes # Enumerate all annotations (use notes:optimize, :fixme, :todo for focus)
rake notes:custom # Enumerate a custom annotation, specify with ANNOTATION=CUSTOM
rake rails:template # Applies the template supplied by LOCATION=/path/to/template
rake rails:update # Update both configs and public/javascripts from Rails (or use just update:javascripts or update:configs)
rake routes # Print out all defined routes in match order, with names.
rake secret # Generate a cryptographically secure secret key (this is typically used to generate a secret for cookie sessions).
rake stats # Report code statistics (KLOCs, etc) from the application
rake test # Runs test:units, test:functionals, test:integration together (also available: test:benchmark, test:profile, test:plugins)
rake test:recent # Run tests for {:recent=>"test:prepare"} / Test recent changes
rake test:uncommitted # Run tests for {:uncommitted=>"test:prepare"} / Test changes since last checkin (only Subversion and Git)
rake time:zones:all # Displays all time zones, also available: time:zones:us, time:zones:local -- filter with OFFSET parameter, e.g., OFFSET=-6
rake tmp:clear # Clear session, cache, and socket files from tmp/ (narrow w/ tmp:sessions:clear, tmp:cache:clear, tmp:sockets:clear)
rake tmp:create # Creates tmp directories for sessions, cache, sockets, and pids
➜ video_app git:(master) ✗
➜ video_app git:(master) ✗ rake stats
WARNING: 'require 'rake/rdoctask'' is deprecated. Please use 'require 'rdoc/task' (in RDoc 2.4.2+)' instead.
at /Users/ioannis/.rvm/gems/ruby-1.9.3-p125/gems/rake-0.9.2.2/lib/rake/rdoctask.rb
WARNING: Global access to Rake DSL methods is deprecated. Please include
... Rake::DSL into classes and modules which use the Rake DSL methods.
WARNING: DSL method VideoApp::Application#task called at /Users/ioannis/.rvm/gems/ruby-1.9.3-p125/gems/railties-3.0.3/lib/rails/application.rb:214:in `initialize_tasks'
+----------------------+-------+-------+---------+---------+-----+-------+
| Name | Lines | LOC | Classes | Methods | M/C | LOC/M |
+----------------------+-------+-------+---------+---------+-----+-------+
| Controllers | 1009 | 463 | 10 | 52 | 5 | 6 |
| Helpers | 45 | 24 | 0 | 2 | 0 | 10 |
| Models | 145 | 60 | 7 | 1 | 0 | 58 |
| Libraries | 0 | 0 | 0 | 0 | 0 | 0 |
| Functional tests | 121 | 93 | 10 | 0 | 0 | 0 |
| Unit tests | 92 | 69 | 16 | 0 | 0 | 0 |
+----------------------+-------+-------+---------+---------+-----+-------+
| Total | 1412 | 709 | 43 | 55 | 1 | 10 |
+----------------------+-------+-------+---------+---------+-----+-------+
Code LOC: 547 Test LOC: 162 Code to Test Ratio: 1:0.3
➜ video_app git:(master) ✗
http://erik.debill.org/2011/12/04/rake-for-rails-developers
view raw gistfile1.bat hosted with ❤ by GitHub

Facebook Token Expiry



Token Expiry

Since Facebook deprecated the offline_access permission, this has become more complex. The expiration time of the access token you obtain will depend on which flow you are using. See below for more details.

Client-Side Flow

If you use the client-side flow, Facebook will give you back a short lived access token (~ 2 hours).

Signed and Permanent cookies in Rails 3


Signed and Permanent cookies in Rails 3
Published over 2 years ago
David added a very cool feature to Rails recently – Signed cookies and permanent cookies This lets you set permanent and/or signed cookies very easily.
Before this, you’d have to write :
cookies[:user_preference] = {
  :value => @current_user.preferences,
  :expires => 20.years.from_now.utc
}
Now just becomes :
cookies.permanent[:user_preference] = @current_user.preferences
In case you happen to have seen my Railssummit presentation I had talked about using ActiveSupport::MessageVerifier for implementing “Remember me” functionality. The above commit makes that a whole lot easier.
In your model User.rb :
# User.rb
def self.authenticated_with_token(id, stored_salt)
  u = find_by_id(user_id)
  u && u.salt == stored_salt ? u : nil
end
And when the user checks “Remember me” box, make sure the following gets run :
cookies.permanent.signed[:remember_me] = [current_user.id, current_user.salt]
This will set a permanent and signed cookie using the secret specified inActionController::Base.cookie_verifier_secret. If you don’t have the cookie_verifier_secret defined, you might want to do that in one of the initializers.
Now when you want to login using the cookie :
user = User.authenticated_with_token(*cookies.signed[:remember_me])
In this specific case, it’s very important to use the salt in the cookie value. That makes sure the cookie gets invalidated if the user changes his password.


http://m.onkey.org/signed-and-permanent-cookies-in-rails-3

Thursday, 10 May 2012

Gaskit


Gaskit

Gaskit a git-backed issue tracker. It uses a branch in your local git database to store the tickets.
Screenshot of Gaskit

Current Status

Gaskit was created as a proof of concept for using Git as a database for an application. It currently only runs against itself.
There is a lot more work to do before it is really useful. If you'd like to help out, clone the repo and run the app to see what needs done.

Running the app

$ bundle install
$ bundle exec rackup
https://github.com/bkeepers/gaskit

I am the smallest rails app!


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
%w(action_controller/railtie coderay).map &method(:require)

run TheSmallestRailsApp ||= Class.new(Rails::Application) {
  config.secret_token = routes.append { root to: 'hello#world' }.inspect
  initialize!
}

class HelloController < ActionController::Base
  def world
    render inline: "
      <!DOCTYPE html>
      <title>The Smallest Rails App</title>
      <h3>I am the smallest rails app!</h3>
      <p>Here is my source code:</p>
      #{CodeRay.scan_file(__FILE__, :ruby).div(line_numbers: :table)}
      <a href='https://github.com/artemave/thesmallestrailsapp.com'>Make me smaller</a>
    "
  end
end


http://thesmallestrailsapp.com/

brew install ack && brew install tmux

tmux From Wikipedia, the free encyclopedia Thi tmux session, with two horizontal and one vertical pane. Developer(s) Nicholas Marriott Initial release 2009-09-20 Stable release 1.6 / January 23, 2012; 3 months ago Written in C Operating system Unix-like Available in English Type Command line interface License BSD Website http://tmux.sourceforge.net/ tmux is a software application that can be used to multiplex several virtual consoles, allowing a user to access multiple separate terminal sessions inside a single terminal window or remote terminal session. It is useful for dealing with multiple programs from a command line interface, and for separating programs from the Unix shell that started the program.[1]

HTML presentation and slideshow

http://lab.hakim.se/reveal-js/?transition=cube#/7



https://github.com/hakimel/reveal.js

zoom.js

http://lab.hakim.se/zoom-js/

stroll.js - CSS3 scroll effects

http://lab.hakim.se/scroll-effects/

screenfull.js


screenfull.js

Simple wrapper for cross-browser usage of the JavaScriptFullscreen API, which lets you bring the page or any element into fullscreen. Smoothens out the browser implementation differences, so you don't have too.


http://sindresorhus.com/screenfull.js/

Sunday, 6 May 2012

Sortable List in Ruby on Rails 3 – Unobtrusive jQuery

Sortable List in Ruby on Rails 3 – Unobtrusive jQuery

Step 1 – create app

$ rails new books -d mysql

Step 2 – replace contents in rails.js with following (switch from prototype to jquery)

http://github.com/rails/jquery-ujs/blob/master/src/rails.js

Step 3 – bundler

Go to gemfile
gem "rails"
gem "mysql2"
gem "acts_as_list"

# go to console:
$ bundle install

Step 4 – to be quick will scaffold a resource

$ cd books
$ rails g scaffold books name:string
# make sure your config/database.yml file is filled in correctly
$ rake db:create
$ rake db:migrate
Add acts_as_list to model
# book.rb
class Book < ActiveRecord::Base
  acts_as_list
end

Step 5 – add jquery to view and setup javascript content_for

Go to app/views/layouts/books.html.erb
Change this:
<%= javascript_include_tag :defaults %>
To this:
<%= javascript_include_tag "https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js", "https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/jquery-ui.min.js", "rails.js" %>
Also before /body add:
<%= yield :javascript %>

Step 6 – seed the db

Go to db/seeds.rb
#db/seeds.rb
book = Book.create([{ :name => 'Harry Potter' }, { :name => 'Twilight' }, { :name => 'Bible' }])

# back to console
$ rake db:seed
# doesn't work - need to add the position column to books in database
$ rails g migration add_position_to_books position:integer
$ rake db:migrate
$ rake db:seed

Step 7 – edit index for li – tables don’t work

<h1>Listing books</h1>
<ul id="books"> <% @books.each do |book| %>
  <li id="book_<%= book.id %>"><span class="handle">[drag]</span><%= book.name %></li>
<% end %></ul>
<%= link_to 'New book', new_book_path %>

Step 8 – add javascript in view

# index.html.erb
<% content_for :javascript do %>
<%= javascript_tag do %>
// Sorting the list

$(document).ready(function(){
$('#books').sortable({
axis: 'y',
dropOnEmpty: false,
handle: '.handle',
cursor: 'crosshair',
items: 'li',
opacity: 0.4,
scroll: true,
update: function(){
$.ajax({
type: 'post',
data: $('#books').sortable('serialize'),
dataType: 'script',
complete: function(request){
$('#books').effect('highlight');
},
url: '/books/sort'})
}
});
});
<% end %>
<% end %>

Step 9 – controller

#books_controller.rb
def index
@books = Book.order('books.position ASC')
end

def sort
@books = Book.all
@books.each do |book|
book.position = params['book'].index(book.id.to_s) + 1
book.save
end

render :nothing => true
end

Cursor

#style.css
.handle:hover{cursor: move;}

Final step – routes

root to: "books#index"
resources :books do
post :sort, on: :collection
# ruby 1.8 would go :on => :collection
end
PS: if you want an all-in-one resource to become an expert in ruby on rails 3, I wouldn’t bother with textbooks – just buy this video tutorial series (and if you get it through this link I get some commission): Ruby on Rails Tutorial


http://webtempest.com/sortable-list-in-ruby-on-rails-3-almost-unobtrusive-jquery/