Install nokogiri and libxml2 on Snow Leopard
This gets me everytime.
You have installed libxml2 through MacPorts but Nokogiri won't compile, even with complex command line arguments.
Solution:
sudo port upgrade --enforce-variants libxml2 +universal sudo port upgrade --enforce-variants libxslt +universal gem install nokogiri -- --with-xml2-include=/opt/local/include/libxml2 --with-xml2-lib=/opt/local/lib --with-xslt-dir=/opt/local
Now I can install capybara or webrat:
gem install capybaraRails users < membership > groups? Enter Workflow
I bet there are a fair numbers of plugins that does the user/groups membership relations, however I'm writing a quick method here because I would like to introduce you a better state machine: Workflow plugin for Ruby on Rails.
All this is tested against Rails 2.3-stable (git)
Do not fear join models; they may feel dirty at start, because we like the idea of never touching them by doing group.users, but here's an example where touching a join model isn't a bad idea.
I wrote this code in 10 minutes, so excuse me if it's not highly optimized, the point really is to just illustrate Workflow.
Let's start with a basic User and Group model:
class User < ActiveRecord::Base has_many :group_users, :dependent => :destroy has_many :groups, :through => :group_users end
class Group < ActiveRecord::Base has_many :group_users, :dependent => :destroy has_many :users, :through => :group_users # Use pure AR. has_many :founders, :class_name => 'User', :conditions => ['group_users.role = ?', 'founder'], :through => :group_users, :foreign_key => 'user_id', :source => :user has_many :moderators, :class_name => 'User', :conditions => ['group_users.role = ?', 'moderator'], :through => :group_users, :foreign_key => 'user_id', :source => :user has_many :members, :class_name => 'User', :conditions => ['group_users.role = ?', 'member'], :through => :group_users, :foreign_key => 'user_id', :source => :user has_many :waiting_users, :class_name => 'User', :conditions => ['group_users.role = ?', 'waiting'], :through => :group_users, :foreign_key => 'user_id', :source => :user has_many :banned_users, :class_name => 'User', :conditions => ['group_users.role = ?', 'banned'], :through => :group_users, :foreign_key => 'user_id', :source => :user has_many :active_users, :class_name => 'User', :conditions => ['group_users.role IN (?)', %w(founder moderator member)], :through => :group_users, :foreign_key => 'user_id', :source => :user end
Very basic. Watch out the :dependent => :destroy code in User model because you may have special logic when deleting a User. For example you may want to force the User to disband a Group first if he's the only founder left.
Also note that I went with GroupUser to have less problems with inflections and class names, however you can use what you prefer.
But let's take a look at the join model, shall we.
# id :integer(4) not null, primary key # group_id :integer(4) # user_id :integer(4) # role :string(255) # created_at :datetime # updated_at :datetime class GroupUser < ActiveRecord::Base include Workflow ROLES = %w(founder moderator member waiting banned) belongs_to :user belongs_to :group validates_presence_of :role, :user_id, :group_id validates_inclusion_of :role, :in => ROLES named_scope :founders, :conditions => { :role => 'founder' } workflow_column :role workflow do state :waiting do event :accept, :transitions_to => :member end state :member do event :ban, :transitions_to => :banned event :promote_to_moderator, :transitions_to => :moderator end state :banned do event :accept, :transitions_to => :member end state :moderator do event :demote_to_member, :transitions_to => :member event :promote_to_founder, :transitions_to => :founder end state :founder end def ban(committer = nil) # an event can accept optional parameters # We can't ban anyone except members. halt! "Cannot ban this user, has role #{current_state}" unless member? end end
A state machine like Workflow is composed of many parts, the most important being states and events.
Workflow is not ActiveRecord dependent and also works with CouchDb, however it has a nice integration and saved states will be immediately accessible in our object instance.
Workflow like other state machines enforces the state of an object, so when you try to change state on an object where an event isn't defined it will throw an exception. We can then smartly intercept this exception and reduce clutter in our code by using rescue.
# group.rb # Returns the membership object or a user towards this group. def membership_of(user) group_users.find(:first, :conditions => { :user_id => user.id }) end # Quick helper to check if the user have the passed role in group. # @group.role_is?(current_user, 'member') def role_is?(user, str) (membership_of(user).current_state.to_s == str) rescue false end # Ban a user from a group. # The entry will be preserved in GroupUser table. def ban(usr) begin usr = User.find_by_username!(usr) if usr.is_a?(String) # this is entirely another exception, add other rescue based on your app. membership_of(usr).ban! true # weak point, a state operation will return nil. rescue Workflow::NoTransitionAllowed => e false end end
Then in controller in your restful actions you can easily do if @group.ban!(user) ; else ; end
Workflow provides a lot of hooks to enter the lifecycle of the object.
I placed the state machine right into the join model, resulting in a lot of flexibility.
You can perform code when entering one or all events, right before a transition and so on.
For further informations consult the plugin's github page, it is very informative and helpful.
Use Amazon Cloudfront when appropriate.
Every project that starts growing needs to track down performance issues and bottlenecks, audiobox.fm is no exception.
However there are cases when the cure is worst than the disease, this is one.
We do streaming, a lot of it, the entire concept of audiobox.fm revolves around streaming your media over the Internet and it should be fast as possible.
A solution was needed to store and stream this content privately, after some evaluations we eventually settled on Amazon S3 and things shaped up pretty nicely from then, instant streaming even from Europe by fetching data contained in an US bucket.
I then received an email from Amazon, in which they explained they opened up CloudFront, a content delivery network working over HTTP and RTMP.
The advantages over the traditional fetch-from-bucket system is that the content is served from the nearest cloud to the requesting user, thus greatly reducing latency.
It's all fun and games until I noticed that the streaming was actually slower then fetching from the US bucket. It may be that the servers are suffering a heavy load, but I think there's more.
I started digging around and I think I have some explanation to that, making audiobox a non-use case.
A bit of background
The doubts in using CloudFront started when I was investigating the possibility to use this CDN as an asset server, serving css/javascript to the end user; however I did noticed that many users on Amazon forums started to ask why their assets were not in sync with the actual content of the bucket after an update.
The answer is simple, while CloudFront fetches from the S3 bucket, it does cache the file, which is in fact the purpose of a CDN.
It's not possible, at the moment, to manually expiry a file to be re-fetched from the bucket, instead the developer is asked to either:
- wait 24 hours
- rename the asset
So?
While many developers try to find a way to expire their assets we have the opposite problem, we would like to see them there stored forever (or at least for a long time).
When making an initial request to CloudFront, the system checks the existance of the file on the bucket and then it gets transferred using the internal Amazon resources to the nearest point.
If I ask a file when I'm in Europe, CloudFront will fetch the file from the US before serving it to me, thus adding a overhead in our request.
A 25MB file needs to travel through Amazon internal network, getting stored on CloudFront server and then served to me.
Now, this solution is super-fine when streaming content to the general public, let's say a video, because it will be requested multiple times and chances are that the file is already in the geographically located CloudFront distribution.
But for a private collection of audio, say 1000 files, this is impratical because records will expire in 24 hours. Files don't get stored on the CDN for more. There will always be a "fetch-again" from the US bucket, adding the extra overhead.
We will continue monitoring and testing CloudFront, but for our use there are disadvantages:
- slower streaming for a private, single-user cloud
- sometimes stream starts when the file has been full downloaded
- copy on CDN is useless because chances are that if the user wants to listen to a file again (in the 24 hours range) browser cache will help there, thus making no request to the CDN, rendering useless its purpose
- effective cost (CloudFront is not free)
- coupled with the fact that Safari/Webkit suffers a HTML 5 bug where the audio and video tag src gets requested twice (even three times sometimes) it's killer
The ideal solution would be that CloudFront proactively mirrors buckets in every of its geographic location, but that will never happen for many reasons.
Installing MySQL Ruby Gem in Snow Leopard Server
Snow Leopard Server doesn't come with MySQL client and library files.
In order to get them we need to download a package, for Snow Leopard Server 10.6.2:
http://www.opensource.apple.com/other/MySQL-49.binaries.tar.gz (71.2MB)
The main entry page should be http://www.opensource.apple.com/release/mac-os-x-1062/ but I cannot find the package there, getting the url was guess work from reading an Apple support page: http://support.apple.com/kb/TA25017
What we are doing is to uncompress a archive directly in our root.
Once you get the file you should decompress it unless your browser already did for you.
Since Safari did it for me I'm having MySQL-49.binaries.tar in my Downloads folder.
Open up a terminal.
cd Downloads sudo tar -xvf MySQL-49.binaries.tar -C / # use -zxvf if you still have the .tar.gz file
We should have the required libraries in order to install the MySQL Ruby Gem.
You may have already read the previous articles about MySQL and Ruby, however the procedure is different:
sudo env ARCHFLAGS="-arch x86_64" gem install mysql
This should give you a working installation of MySQL Gem.
Hosting Git repositories through WebDAV on Snow Leopard Server
This will be quick and dirty; the necessity comes from a particular source code that I can't trust to host online to a third party vendor/provider.
I have a Snow Leopard Server, so I will make use of the tools provided, there's little information about the subject, so here it is.
Also I choose WebDAV since my SSH is blinded to the outside world, in order to access it I require a VPN connection, but since I want to share some repo to the outside world, WebDAV comes handy.
I host my repositories in a subdomain, so before anything, open up your DNS Server Admin section and start editing by adding your new shiny hostname as you please by adding a new record there.
I assume you are using a subdomain along the lines of repositories.yourdomain.com; you don't have to do this step if using an ip address, this is up to you.
Login into your server through ssh or the way you like and create a repositories index directory somewhere.
In my particular setup I have this structure for my websites:
/Sites/kain/mydomain.com/[www, macruby, etc]
So go where you like and create a directory, I'll use repositories
mkdir /Sites/kain/mydomain.com/repositories
Assign this directory to the _www user
chown -R _www:_www /Sites/kain/mydomain.com/repositories cd /Sites/kain/mydomain.com/repositories
Start creating a new directory that will host the git repository:
mkdir myproject.gitAfter that go in the Web section, and create (or copy) a new website.
Relevant General setup:
Host name: repositories.yourdomain.com
Web folder: /Sites/kain/mydomain.com/repositories
Relevant Options setup:
Folder listing: enabled (not really necessary but useful for testing what you see in index later)
WebDAV: enabled
Rest disabled
Relevant Realm setup:
create a new realm by clicking the plus sign
Realm name: myproject
Authentication: Basic (will try Digest/Kerberos in future)
Select Location for the select widget instead of Folder and write
/myproject.gitIn the right section now add your users that will have access, after that change their permissions.
Since my repos are private I only keep the users that have write access there by setting their permissions to Browse and Read/Write WebDAV, None for Everyone.
Relevant Logging setup:
enable logging as you please
Relevant Security setup:
I choose to use SSL for this tutorial, so you are going to use a certificate and port 443.
Save and close Server Admin.
Let's go back to our SSH server session.
Enter your project directory and exec those commands:
cd /Sites/kain/mydomain.com/repositories/myproject.git sudo git init --bare sudo chown -R _www:_www . sudo mv hooks/post-update.sample hooks/post-update sudo git update-server-info
Now switch to your client.
Edit your ~/.netrc file with those informations:
machine repositories.mydomain.com
login myusername
password mypasswordEnter your project dir and exec those commands:
cd myproject git config --global http.sslVerify false # If you don't own a valid SSL certificate on your server, skip --global to enable this option only for this project git init # gitify the directory git add . # add files to git repo git commit -m "hi" # first commit git remote add origin https://repositories.mydomain.com/myproject.git/ # end slash is important git push origin master --force -v
Now edit this file, always in your project root dir:
mate .git/configAnd add
[branch "master"] remote = origin merge = refs/heads/master
This way you can do git push and git pull normally.
Done.
AudioBox.fm
Hi readers,
I'm putting together a new project, dubbed audiobox.fm.
It's in active development; if you feel you can leave your email or follow the project on Twitter to be notified when doors open for beta stage.
I'm going to limit the initial access to the app, so hurry :)
Cheers.
My ToolBox
Being a computer junkie I usually use a certain number of tools, ranging from productivity to my amusement. Here's a generic list.
Hardware
MacBookPro5,1 Intel Core 2 Duo 2,53 GHz 4 GB RAM
Time Capsule 1TB
Random Logitech Mouse
Development
TextMate
Safari
Firefox
Xcode and Interface Builder
Terminal.app
Navicat Premium
phpMyAdmin
phpPgAdmin
Sequel Pro
Parallels 5
GitX
System Utilities
Server Admin and Workgroup Manager
Angry IP Scanner
SuperDuper!
DiskWarrior
WhatSize
Imaging
Adobe Photoshop CS3
Skitch
Video
VLC
ReduxEncoder
IShowU HD
HandBrake
Networking
ForkLift
Tweetie
Skype
Transmission
X-Chat Aqua
Adium
Music
iTunes
CoverSutra
XLD
Mp3/Tag Studio (on Parallels) plus various home-made tools
Everyday Apps
Mail.app
Disco
AppCleaner
RAR Expander
Still something is missing, but you get the picture.
About ripping articles
My apologies, the Cappuccino series articles has been suspended indefinitely. I won't go into details, so don't ask.
Also sorry if it sounds cryptic to you.
Install id3lib-ruby on Snow Leopard
We are going to use MacPorts for this.
Install the prerequisite:
sudo port install id3lib
And install the 64-bit gem:
env ARCHFLAGS="-arch x86_64" gem install id3lib-ruby
Test:
16:53 ~ $ irb >> require 'id3lib' => true
Cappuccino: the jake branch

Those steps assumes you have already setup your Cappuccino source similarly to my environment.
If not, please read previous articles.
Keep in mind that the "jake" branch will be merged into master at some point, and the switch should happen sometime in the upcoming weeks; at least I've been told this way so take this information with a grain of salt.
Therefore: those instructions can turn obsolete at any time and probably are.
If you read my previous article you already know that I'm running Snow Leopard.
At any point do not close the Terminal window until you did every step.
Download Narwhal from GitHub.
cd ~/src/ git clone git://github.com/tlrobinson/narwhal.git
Activate paths and use tusk to install required packages.
source ~/src/narwhal/bin/activate tusk install jake browserjs narwhal-jsc jack
Build Narwhal JSC:
cd ~/src/narwhal/packages/narwhal-jsc make webkit export NARWHAL_ENGINE=jsc
Build Cappuccino:
# if you don't already have a cappuccino source remember to have a CAPP_BUILD defined and # git clone git://github.com/280north/cappuccino.git ~/src/cappuccino_source cd ~/src/cappuccino_source git checkout -b jake origin/jake rm -rf $CAPP_BUILD jake jake debug # if you want also the Debug build jake docs # if you want up to date local xml/html documentation, requires doxygen
Building Cappuccino with jake instead of rake will take some minutes to compile.
I read about some people reporting that the process gets stuck, this didn't happened to me (well it did, but because I was compiling against master by mistake); the jake building process took about 4 minutes to complete on a 2,53 GHz Unibody Intel Core 2 Duo Macbook Pro.
We are going to tweak our $PATH in order to find the new Cappuccino Tools and Narwhal binaries like jake.
Keep in mind that you probably already have them in /usr/local/bin so we must adjust our PATH with some precedence.
Old utilities in /usr/local/bin aren't modified by this procedure, since the jake task that should install them is currently commented.
Here is my .bash_profile related code:
# CAPP_BUILD, directory for built Cappuccino framework. export CAPP_BUILD="$HOME/src/cappuccino_framework" # Cappuccino jake branch. PATH="$CAPP_BUILD/Release/CommonJS/objective-j/bin:$PATH" PATH="$HOME/src/narwhal/bin:$PATH" # Finalize PATH export PATH=$PATH
This will make capp, nib2cib and press available on command line.
Reopen Terminal and test:
03:52 ~ $ which capp /Users/kain/src/cappuccino_framework/Release/CommonJS/objective-j/bin/capp 22:42 ~ $ cd Sites/ 22:42 ~/Sites $ capp gen MySecondApp -t NibApplication /Users/kain/src/cappuccino_framework/Release/CommonJS/objective-j/lib/capp/Resources/Templates/NibApplication -> MySecondApp # ...
Have fun!
