A wonderful way to list your project files 9

Posted by labria on September 10, 2009

This article is published as a reply to the last few articles on the thoughtbot blog.

Some time or another during the project development you may wonder: «What files does my project include?» or «Why am I a Star Wars fan anyway?». This article will focus on the first question, leaving the second to your own reflection.

Any shell you might be using comes with a lot of useful tools. The one of interest today is ls

Here's the most simple example of its usage in a Rails project

 
sample_project $ ls
README		app		db		lib		public		test		vendor
Rakefile	config		doc		log		script		tmp
 

As a little more complex example, you might want to get the list of your model files:

 
sample_project $ ls app/models/
comment.rb	post.rb
 

Or, it even can do crazy stuff, like list all your model files AND controller files AND helper files! Now thats some useful magic, isn't it?

 
sample_project $ ls app/*
app/controllers:
application_controller.rb	comments_controller.rb		posts_controller.rb
 
app/helpers:
application_helper.rb	comments_helper.rb	posts_helper.rb
 
app/models:
comment.rb	post.rb
 
app/views:
comments	layouts		posts
 

ls can do much. much more. Go check out the manual page here, you'll be amazed.

Note to Windows users: you also have a similar command, named dir.

Freckle API. 1

Posted by labria on July 11, 2009

UPD: Freckle updated the API and docs. The new ones are here.

I'm a Freckle user, and I love the service. The only thing I really miss is a simple client to store my time entries without visiting the site.

So, I sat down to write that simple client app, knowing Freckle has an API.

Alas, things aren't that simple. The only thing you can in fact do using the API description is to get the list of your projects. But after a bit of digging and trial and error i figured out some of the real API, and wanted to share it with anyone who might want to use it.

In all the examples yourfreckle is your Freckle domain name, and refet7vjpj01elltewf2fbqd9znkbh9 is your API key. All examples use JSON, but should work with XML too, just replace .json with .xml

Getting the list of projects. This is done by a simple GET request to http://yourfreckle.letsfreckle.com/api/projects.json?token=refet7vjpj01elltewf2fbqd9znkbh9, which will return a JSON array of projects in the form of

[
{"project": {"name": "Bibla", "updated_at": "2009-06-30T14:52:51Z", "account_id": 3279, "id": 5120,
"enabled": true, "user_id": null, "stepping": 15, "budget": null, "created_at": "2009-06-30T14:52:51Z"}},
{"project": {"name": "Inventure", "updated_at": "2009-06-30T14:02:15Z", "account_id": 3279, "id": 5114,
"enabled": true, "user_id": null, "stepping": 15, "budget": null, "created_at": "2009-06-30T14:02:15Z"}},
{"project": {"name": "OMD", "updated_at": "2009-06-30T14:27:20Z", "account_id": 3279, "id": 5118,
"enabled": true, "user_id": null, "stepping": 15, "budget": null, "created_at": "2009-06-30T14:27:20Z"}}
]
 

The significant bits here are the "name" and "id" fields. You will surely need both of them later on.

Getting the list of entries. This one is not quite what we want it to be. You can get only the last 100 entries, and no filters. To obtain this you need to do a GET request to http://yourfreckle.letsfreckle.com/api/entries.json?token=refet7vjpj01elltewf2fbqd9znkbh9, but you will only get the latest entries for all users on all projects. I found no way to filter them yet, the RESTful approach didn't work, so you have to filter them yourself in your app, if needed. The entries come back in the following form:

 
[{"entry": {"updated_at": "2009-07-10T18:34:16Z", "project_id": 5151, "billable": true, "minutes": 300, "date": "2009-07-10", "url": null, "id": 51177,
"time_to": null, "time_from": null, "description": "A sample entry description", "formatted_description": "A sample entry description", "user_id": 3635, "created_at": "2009-07-10T18:26:09Z"}},
{"entry": {"updated_at": "2009-07-11T02:03:37Z", "project_id": 5120, "billable": true, "minutes": 30, "date": "2009-07-10", "url": null, "id": 51253,
"time_to": null, "time_from": null, "description": "A second sample entry description", "formatted_description": "", "user_id": 3574, "created_at": "2009-07-11T02:03:37Z"}},
{"entry": {"updated_at": "2009-07-10T18:25:29Z", "project_id": 5151, "billable": true, "minutes": 120, "date": "2009-07-10", "url": null, "id": 51176,
"time_to": null, "time_from": null, "description": "A third sample entry description", "formatted_description": "A third sample entry description", "user_id": 3635, "created_at": "2009-07-10T18:25:29Z"}},
{"entry": {"updated_at": "2009-07-10T18:24:14Z", "project_id": 5151, "billable": true, "minutes": 120, "date": "2009-07-10", "url": null, "id": 51175,
"time_to": null, "time_from": null, "description": "And one more sample entry description", "formatted_description": "And one more sample entry description", "user_id": 3635, "created_at": "2009-07-10T18:24:14Z"}},
{"entry": {"updated_at": "2009-07-10T19:27:29Z", "project_id": 5112, "billable": true, "minutes": 60, "date": "2009-07-10", "url": null, "id": 51195,
"time_to": null, "time_from": null, "description": "haha haha", "formatted_description": "", "user_id": 3572, "created_at": "2009-07-10T19:27:29Z"}}...]
 

As you can see, entries don't mention project or user names, only reference them by id, so if you really wanna know who did what you need to do the user and project mappings yourself.

Getting the list of account users. GET to http://yourfreckle.letsfreckle.com/api/users.json?token=refet7vjpj01elltewf2fbqd9znkbh9, resulting in something like that:

 
[{"user": {"id": 3570, "first_name": "Alexey", "time_format": "hours_minutes", "login": "wolfson", "last_name": "Wolfson", "email": "wolfson@gmail.com"}},
{"user": {"id": 3572, "first_name": "Dmitry", "time_format": "fraction", "login": "labria", "last_name": "Krassovski", "email": "labria@startika.com"}},
{"user": {"id": 3571, "first_name": "Maxim", "time_format": "hours_minutes", "login": "boork", "last_name": "Boork", "email": "boork@gmail.com"}}
]

This one was simple, right? :)

Creating stuff. To create things you do POST requests with parameters in the request body. The parameters, contrary to the API description, are not JSON, but simple string params, in the form of object_name[property]=something&object_name[other_property]=something_else. This is the usual Rails way of sending parameters. Just don't forget to URL encode them before sending =)

Creating a project. POST to http://yourfreckle.letsfreckle.com/api/projects.json?token=refet7vjpj01elltewf2fbqd9znkbh9 with the body set to "project[name]=SomeProjectName". The reply body will be empty, just watch the HTTP code, 201 means you've created the project, anything else is probably an error (but don't expect to get any meaningful error description)

Creating an entry. This is where you need the project ids that you obtained earlier. POST to http://yourfreckle.letsfreckle.com/api/entries.json?token=refet7vjpj01elltewf2fbqd9znkbh9 with the body set to "entry[minutes]=15&entry[project_id]=5151&entry[user]=labria&entry[description]=foobar&entry[date]=2009-07-10". Here, project_id is the id of the project you want to post to, and user is the username of the user you want to post it to, everything else is quite obvious. Funny thing: using your own API key you can post entries for any user on your team.

Well, thats about it. There may be more stuff i don't yet know about, but the things described here should be enough to create a simple client application. You can't do much about reporting, as 100 entries is surely not enough, but I hope this improves in some way over time.

PS: Thanks to the Freckle team for the nice and easy to use service.

PPS: No, I didn't write any client app that does more than getting the list of your projects yet. I'll post a follow up when I do =)

PPPS: This by no means isn't a good technical description of the API, but it should get you going. Maybe I'll write a better one later.

Heroku pricing.

Posted by labria on April 24, 2009

Heroku announced their pricing today. As with all rails-centric hosting solutions, the price is sky high. You can get a LOT more if you just go and get Amazon EC2 and set everything up yourself. Yes, they do provide you with automatic and painless scaling, but for the price difference I would prefer the pain of scaling, once i get to the point of needing it.

Anybody needs Amazon deployment? Feel free to contact me =)

Ajaxy rails docs.

Posted by labria on May 10, 2008

I don’t know if i’m the only one with the problem, but i just couldn’t get the “doc:rails” rake task to work with the jaxdoc RDoc template. I finished by running rdoc by hand to create the docs, here’s the code Tell me if I forgot to exclude something.

Sharing sessions between Rails apps.

Posted by labria on March 27, 2008

Sometimes (in my case 2 times) you may have more than one application running off the same database, partially sharing model code. In the first case I had 4 apps (one main and four satellites) running this way, but the userbase was not shared between them (most users didn’t actually know of the other apps), so common sessions were not needed. In the second case (a distrbuted file sharing network) users floated between the main site and satellites and I wanted to include flash[:notices] while redirecting them. As you know, flash messages are kept in the session, so i needed all the apps to share the session data for the user.

There are 2 things you need to do to share sessions (this applies when using subdomains, i’m not sure if it’s doable with totally different domains).

First: make all the apps use the same session key and secret by editing the environment.rb file:

 
  config.action_controller.session = {
    :session_key => '_your_session',
    :secret      => 'some_long_string_of_letters_and_numbers'
  }
 

This is done so all your apps recognize each others session data. Second, you need to alter the session_domain option of ActionController (in one of your initializers files):

 
  ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS.update(:session_domain => ".yourdomain.com")
 

This one is to make the subdomains recognize the main domain’s cookies.

My problem, however, was that this setup worked only one way. I could set session variables in the main app and read from the satellite, but not the other way. As found out later, the problem is that the rails2 default session store is CookieStore. And cookies written by the top level domain can’t be altered by subdomains. To fix this i had to migrate to the ActiveRecord session store.

After a few hours of setting all of this up and testing, I decided that all of this was too much pain and security issues to be used in production, so I’ll just have another way of sending messages between the apps. But I also thought that someone may find this info useful (the CookieStore problem wasn’t evident to me), so I wanted to share it =)

Generating a fake id for a model.

Posted by labria on December 06, 2007

Sometimes you don’t want to reveal the real id of the object in the system to the user for some reason. For example, you don’t want to reveal the number of items in the system, or let the user view all of them by simply changing the id in the address.

The most obvious and easy way to do it is to generate a fake id for the object and use it instead. Lets say we have a model named Foo and you want it to have a fake id. Here’s the code for it:

 
class Foo < ActiveRecord::Base
  before_validation_on_create :generate_fake_id
 
  def to_param
    fake_id
  end
 
protected
  def generate_fake_id
    string = random_string
    while Foo.find_by_fake_id(string)
      string = random_string
    end
    self.fake_id = string
  end
 
  def random_string(size = 8)
    chars = (('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a)
    (1..size).collect{|a| chars[rand(chars.size)] }.join
  end
end
 

Now, when you create a Foo with for example “Foo.create(:name => ‘first foo’)”, the model generates a random string of 8 characters and stores it in fake_id. The fake id is only generated once, on creation, so it’s not changed later on. Now, for the helpers. Suppose you have map.resources :foos in your routes.rb. The helper you user before, foo_path(@foo) now generates a url with the fake id (foos/Hd45jdg3), because of the to_param method. All you have to do, is to change your foos_controller this way:

 
class FoosController < ApplicationController
  def show
    @foo = Foo.find_by_fake_id(params[:id])
  end
end
 

The uniqueness of the fake_id is guarantied by the Foo.find_by_fake_id call in the generate_fake_id method. Yes, it does add an extra query to the database, but it’s not really impaction performance much unless you are generating a lot of objects all of the time. You can change the way the fake id looks like by modifying the array used in random_string. For example if you want to eliminate symbols that look much alike you can have it this way:

 
def random_string(size = 8)
   chars = (('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a) - %w(i o 0 1 l O)
   (1..size).collect{|a| chars[rand(chars.size)] }.join
end
 

And also: don’t forget to add the fake_id field to the table in the database.