Perspectives

Adminable

Just get ActsAsAdminable now!

Admin Interfaces

I was recently working on a site for a client, which was just six simple pages and a CMS in the back-end to administrate content. Our CMS let you add and edit the FAQs on the FAQ page, add/remove downloads on the "Resources" page, etc. I took it to the client and his first question was, "How do I edit the footer?". After discussing, it turned out the client needed the ability to edit any piece of text on the entire site, which was very rich in detail. Every element on the page had a different layout or position, and so on. It's a very graphically complex site!

I could have extended the admin interface to support this. But it wouldn't have fit well with the style of the rest of the admin interface. What would I call that page, "Scattered Bits of Miscellaneous Text"? Something simpler, cleaner and more intuitive was needed.

Enter the Plugin

I searched for a plugin that would solve my problem but didn't find anything that exactly fit my situation, and I didn't look TOO hard because I was already excited at the prospect of writing my own. Long story short, I did, and it's called acts_as_adminable.

Using it couldn't be simpler. After you install it (or run script/runner vendor/plugins/acts_as_adminable/install.rb if you got it via git clone), you just go into your view, find the bit of text you want to replace, and turn it into a content_tag with a :key attribute — and that's it!

An Example

Let's say you have code like this:

<div class="header">
  <h1>At Company Inc., our mission is to meet your needs</h1>
</div>

To administrate this with ActsAsAdminable, first rewrite it like so:

<div class="header">
  <%= content_tag :h1, 'At Company Inc., our mission is to meet your needs', :key => 'mission_statement' %>
</div>

At this point your page should look exactly the same as it did a minute ago; ActsAsAdminable is designed to be easily removed or disabled. Now you just need to tell your controller to make this page adminable:

class PagesController < ApplicationController
  acts_as_adminable :if=>Proc.new{ session[:current_user].is_admin? }
  ...
end

The if parameter is a code snippet that you customize to match whatever you use for authentication. After all, you don't want just anybody to be able to edit your page!

Results

Now when you visit the page, you get a visual clue on mousing over any admin-enabled element. And a single click pops up an Ajax control for editing its contents! Any changes you make will be reflected instantly and for all subsequent visitors. There are only a few caveats:

  1. The :key argument must be a globally unique string.
  2. The content_tag should also have a unique id HTML attribute, although by default ActsAsAdminable will use the :key for that purpose if you didn't specify your own. So also make sure that you either specify a unique "id" HTML attribute, or that the ":key" does not collide with any other element's "id" attribute.
  3. The edit field uses markdown for formatting, so read up if you're not familiar with its syntax.

Now that you know what to do with it, head over to github and simplify your site!



Ball

Every once in a while we are asked to make a game for a client. I recently worked on a game that centered—as many games do—around a bouncing ball. I thought this would be a good opportunity to put together the first Killswitch multi-part flash tutorial. In this tutorial we'll build a simple but highly customizable billiards game. Credit goes to Tonypa's vector tutorial for some of the concepts and techniques on which this game is based. There is also a helpful article on Euclidean vectors on Wikipedia.

In this first installment we'll get things started with vectors—the basic building block of the game—and get a ball bouncing around inside a confined space.

So, What's a Vector?

This isn't drawing with vectors that we're talking about here. This is physics. A vector is essentially a point (x and y coordinates) and a direction. It can be used to describe the position, direction and velocity of an object, describe forces like wind or gravity, describe obstacles like walls, determining collisions and so on.

For this game I've created a Vector class which has a number of useful methods that are helpful for manipulating vectors. Ultimately they boil down to things like the pythagorean theorem, sine and cosine—trigonometry stuff.

The constructor takes 4 attributes: x and y coordinates, deltaX (horizontal movement component) and deltaY (vertical movement component).

var myVector:Vector = new Vector(1, 1, 4, 3);

Vector Playground

Let's try it out. Download a finished version of this movie for reference if you like.

You'll want to download the Vector.as file.

Create a new flash (AS3) project. Name it something like “vectorPlayground.fla”. Save the project in the same directory as Vector.as or be sure to set the classpath to the directory of Vector.as (in publish settings). Directions on how to do this can be found in Adobe's Flash documentation.

Create a Sprite called “canvas” and add it to the stage. We'll use the graphics package to draw the vectors.

var canvas:Sprite = new Sprite();
addChild(canvas);

Write a function for drawing vectors:

function drawVector(vector:Vector, color:uint):void{
	canvas.graphics.lineStyle(1, color);
	canvas.graphics.moveTo(vector.x, vector.y);
	canvas.graphics.lineTo(vector.x + vector.deltaX, vector.y + vector.deltaY);
}

This function takes a vector and a color for the lines it will draw.

Now create a vector and draw it on the canvas:

var myVector:Vector = new Vector(10, 10, 50, 300);
drawVector(myVector, 0x3F3F39);

try out some of the Vector methods:

// set the vector's angle in degrees
myVector.setAngle(45);
drawVector(myVector, 0xFF55BB); // a pink line

// set the vector's length
myVector.setLength(50); 
drawVector(myVector, 0x5555FF); // a short blue line drawn on top of the pink one

// resets the vectors length and angle respectively
myVector.setPolar(200, 10);
drawVector(myVector, 0x55FF55); // a green line

// the getIntersection() method is used to find the point where two vectors intersect. 
var vector2:Vector = new Vector(100, 10, -20, 200);
drawVector(vector2, 0xDD2200); // a dark red line
var intersection:Point = vector2.getIntersection(myVector);
// draw a circle at the intersection point (x, y, radius)
canvas.graphics.drawCircle(intersection.x, intersection.y, 2);

Ok, enough of that for now. It's time start building the pool game.

The Game

Download a finished version of this movie for reference if you like. Preview the finished movie.

Let's start with a clean FLA. Call it “poolParty.fla”. Create a folder named “classes” in the same directory as pollParty.as and put Vector.as in there. Make sure that you set the classpath to the classes directory. Set the size of the document to 250x380. Set the background color to something dark (pool table green perhaps) because we'll be playing with the (white) cue ball to start.

I've created a Ball class that is essentially a circle that has its own vector. In a later installment we'll spice it up a bit with some nicer graphics. This iteration of the Ball class has a method that draws a circle at the x and y coordinates of the Vector. The other method of note is moveAlongVector().

// Ball.as

public function moveAlongVector():void{
	drawMe();
	// put a cap on how fast the ball can move.
	if(vector.getLength() > radius){
		vector.setLength(radius);
	}
	vector.x += vector.deltaX;
	vector.y += vector.deltaY;
	x = vector.x;
	y = vector.y;
}

This method draws the ball at its current location and then sets the vectors coordinates to the endpoint of the vector and moves the sprite to that point. Calling this repeatedly animates the ball along a line defined by its vector.

Download Ball.as and save it in the classes folder.

Create a ball and add it to the stage. The constructor takes x and y coordinates, deltaX and deltaY, a radius and a color:

// a global radius for all balls
var radius:Number = 7;

var cueBall:Ball = new Ball(180, 50, 1, 4, radius, 0xFFFFFF); 
addChild(cueBall);

This places a ball on the stage and sets its vector pointing down and to the right. The Ball constructor function draws the ball for us with a call to the private method drawMe().

Let's make it move. In poolParty.fla set the frame rate to something high like 60fps. Write an event listener to move the ball along its vector:

addEventListener(Event.ENTER_FRAME, updateMovement);

function updateMovement(e:Event):void{
	cueBall.moveAlongVector();
}

Test this and you should see the ball glide off the table.

Create some borders for the table by making vectors that run along the edges of the stage. I set a margin variable (roughly the diameter of the ball) to set off the borders so there will be room to add in pockets later.

var margin:Number = 16;

// borders
var top:Vector = new Vector(margin, margin, 250 - margin*2, 0);
var right:Vector = new Vector(250 - margin, margin, 0, 380 - margin*2);
var bottom:Vector = new Vector(margin, 380 - margin, 250 - margin*2, 0);
var left:Vector = new Vector(margin, margin, 0, 380 - margin*2);
var walls:Array = [top, right, bottom, left];

You may find it reassuring to know where your vectors are. Feel free to draw them in using the drawVector() function that we used earlier. Don't forget to add the canvas first: 

var canvas:Sprite = new Sprite();
canvas.graphics.lineStyle(1, 0x000000);
addChild(canvas);

for each(var w in walls){
	drawVector(w, 0xFF00EE);
}

In our updateMovement() function we'll want to add something to check for collisions. Because a ball might hit more than one thing at a time (other balls for example) we'll call this function determineFinalBounce() and pass it the ball and walls as arguments:

function updateMovement(e:Event):void{
	cueBall.moveAlongVector();
	determineFinalBounce(cueBall, walls);
}

The determineFinalBounce() function itself looks like this:

function determineFinalBounce(ball:Ball, obstacles:Array):void{
	var vector = ball.getVector();
	var radius = ball.getRadius();
	var willBounce:Boolean = false; 

	// check walls for collisions and bounce accordingly
	for each(var o in obstacles){
		var vectorToObstacle:Vector = shortestDistance(vector, o);
		if(vectorToObstacle.getLength() <= radius){
			bounce(vector, vectorToObstacle, radius);
		}
	}
}

This function checks all of the obstacles (walls) to see if the ball is close enough to any wall such that it should bounce off of it.

You'll notice that there are two functions missing from the picture still: shortestDistance() and bounce(). Let's look at shortestDistance() first.

shortestDistance() is used to figure out the point of the wall that is closest to the ball.

function shortestDistance(v1:Vector, v2:Vector):Vector{
	// vector from start of v2 to start of v1
	var v2ToV1:Vector = new Vector(v2.x, v2.y, v1.x - v2.x, v1.y - v2.y);
	// create vector from ball to closest part of v2
	var shortest:Vector = v2ToV1.project(v2.normalLeft());
	// move start of shortest to start of v1.
	shortest.x = v1.x;
	shortest.y = v1.y;
	// rotate shortest 180°
	shortest.deltaX *= -1;
	shortest.deltaY *= -1;

	return shortest;
}

shortestDistance() uses two Vector methods that we haven't seen before:

normalLeft() returns a vector that runs perpendicular to the given vector. Think of it as the same vector rotated 90° counterclockwise.

project() returns a vector that is a “projection” of one vector onto another. Imagine one vector casting a shadow onto the other.

The whole function looks something like this if we drew all of the vectors involved line by line:

This diagram requires flash.

Get Adobe Flash player

if the length of the vector returned by shortestDistance() is less than the radius of the ball, then we know that it has come in contact with the wall. Once we have figured out which wall the ball has most immediately come into contact with, we need to make it bounce off of that wall.

bounce() takes the vector of the ball, shortest (the vector representing the shortest distance between the ball and the wall), and the ball's radius as arguments.

// given a vector and the shortest distance between vector's endpoint and an obstacle
// move the vector back to the point where it makes contact (assuming a radius), 
// and reflect its path.
function bounce(v1:Vector, v2:Vector, radius:Number):void{
	// the amount and direction to back up
	var reverse:Vector = new Vector(v2.x, v2.y, -v2.deltaX, -v2.deltaY);
	reverse.setLength( (radius+1) - v2.getLength() );
	var reverseEP:Point = reverse.getEndpoint();

	//surface off of  which reflection will occur
	var surface:Vector = v2.normalLeft();
	var proj1:Vector =  v1.project(v2);
	var proj2:Vector = v1.project(surface);
	proj1.deltaX *= -1;
	proj1.deltaY *= -1;
	var reflection:Vector = proj1.sum(proj2);
	reflection.x = reverseEP.x;
	reflection.y = reverseEP.y;

	if(proj1.dotProduct(v2) > 0){
		reflection.deltaX = v1.deltaX;
		reflection.deltaY = v1.deltaY;
	}
	v1.resetFromVector(reflection);
}

This function does the following:

It finds the point at which the ball makes contact with the wall. It does this by creating a vector reverse, starting at the center of the ball and at an angle of 180° from the balls current vector. The length is set to the difference between the radius and the shortest distance to the wall. The endpoint of reverse is the point where the ball is when it makes contact with the wall.

Then the ball's vector is reflected at an angle found by summing the projection of the ball's vector onto a line parallel to the wall, and the projection of the ball's vector onto a line perpendicular to the wall. sum() is a method of Vector that simply adds the deltaX and deltaY components of two vectors returning a vector with an angle and length somewhere between the two.

There are two other new methods here. The first is dotProduct() which returns a number. I won't go into the details of how the dot product is found. The important part is that if the dot product of two vectors is positive it means that they are pointing toward each other, if it is negative then they are pointing away. bounce() checks the dot product of v2 (shortest) and the reversed projection of v1 onto v2. If it is greater than 0 then we know that the ball was moving away from the wall to begin with, and that reflecting it would be a bad idea because it would turn the ball back toward the wall, which would result in unnatural behavior.

The function looks something like this:

This diagram requires flash.

Get Adobe Flash player

Add the shortestDistance() and bounce() functions to poolParty.fla and test it out. You should see your ball bouncing around the screen like an old-school screen saver.

Now, I know someone is asking “Hang on, why do we need to mess around with all of these vectors? Why not just check the ball's coordinates against the borders of the table and tell it to bounce back when it's gone too far? That would be a lot easier.” That is true, it would be easier and it would work. But the vector technique allows for borders that are not strictly horizontal or vertical. It allows the game designer to build a table with 3, 5, 6 or however many sides they want. Go ahead and try it. Maybe you want to rotate the table a bit or give it a three-dimensional feel with some pseudo-perspective. We will also be using the vectors to determine the correct angle of bouncing when two or more balls collide with another.

That does it for this stage of the the tutorial. Next time we'll add some more balls and smack 'em around.



Log_cabin

While optimizing our Rails apps, I used the standard Rails Logger to output performance information about specific trouble-spots of code. Having to constantly grep through the production log, however, was driving me nuts. What I really wanted to do was to write information about a specific problem area to a specific file, without adding a lot of extra code.

And now the LogCabin Rails plugin makes this very easy (Get it on GitHub).

The LogCabin plugin is designed to give you the flexibility to write information about blocks of code and their execution times to specific log files in your Rails project. LogCabin can also be used in Rake tasks, which can be incredibly helpful for debugging and monitoring long-running tasks.

Example Usage

This is especially useful when monitoring and debugging a specific set of code in a model or controller file. As a contrived example:

class UsersController < ApplicationController
  def index
    LogCabin.log_to :user_search do |log|
      log.debug "Finding all users with params = '#{params[:user_search].map{|k,v| "#{k}: #{v}"}.join(', ')}'"
      @users = User.search(params[:user_search])
      log.info "Found a total of #{@users.length} users"
      log.warn "ONLY FOUND #{@users.length} USERS" if @users.length < 50
    end
  end
end

LogCabin logs the time it took to run the block, along with any info, debug or warn messages that you ran on the log object. This code might print the following out to RAILS_ROOT/log/user_search.log:

DEBUG: Finding all users with params = 'last_name: Smith, city: Chicago'
INFO: Found a total of 32 users
WARN: ONLY FOUND 32 USERS
TIME: Tue Nov 18 12:34:52 -0600 2008: Operation took 3.2876 seconds

It is also possible to pass an :if or :unless option to LogCabin#log_to, ex:

LogCabin.log_to :users_query, :if => RAILS_ENV == 'development' do |log|
  log.info "This will not output anything unless we're in development"
  puts "But any other Ruby code in this block will always be run"
end

Leveraging Capistrano

I think one of the best parts about using LogCabin is how easy it is to check up on these logs across multiple production servers using Capistrano. Here's a cap task that will let you tail a specific log or monitor the live stream:

# Assumes the deploy_to variable has been set in your Capistrano config
namespace :deploy do
  namespace :log do
    desc "Tails a log "
    task :tail, :roles => [:app] do
      set(:log_name) do
        Capistrano::CLI.ui.ask "Which log do you want to fetch? "
      end
      set(:line_count) do
        Capistrano::CLI.ui.ask "How many lines should I fetch? (blank to tail log) "
      end
      if line_count.strip! =~ /^\d+$/
        run "tail -n #{line_count} #{deploy_to}/current/log/#{log_name.gsub(/\.log$/, '')}.log"
      else
        run "tail -f #{deploy_to}/current/log/#{log_name.gsub(/\.log$/, '')}.log"
      end
    end
  end
end
Download LogCabin from GitHub




RSS Feed


CATEGORIES


ARCHIVES


BOOKMARKED


Add to Technorati Favorites