Just as you can check the class of an instance using .kind_of?, you can also see whether a specific class inherits from another class using the ‘<' operator.
As a simple example, you can see that ruby's String class inherits from Object.
irb(main):001:0> String < Object
=> true
So, let's create a class 'Foo' we can play around with.
irb(main):002:0> class Foo < String
irb(main):003:1> end
=> nil
irb(main):004:0> Foo
=> Foo
We can ask whether Foo inherits from String or Object:
irb(main):005:0> Foo < String
=> true
irb(main):006:0> Foo < Object
=> true
irb(main):007:0> String < Foo
=> false
irb(main):008:0> a = Foo.new('a')
=> "a"
irb(main):009:0> a
=> "a"
irb(main):010:0> a.class.name
=> "Foo"
irb(main):011:0> a.class < String
=> true
irb(main):012:0> a.kind_of? String
=> true
Kind of cool.
In ruby, you can find out whether a class is defined by calling:
Object.const_defined?('ClassName')
A simple demonstration of this in an irb session is:
ruby-1.8.7-p330 :020 > Object.const_defined?('String')
=> true
ruby-1.8.7-p330 :021 > Object.const_defined?('Asdf')
=> false
Recently I ran into the problem that some called to Object.const_defined? are raising a NameError instead of returning true or false.
ruby-1.8.7-p330 :022 > Object.const_defined?('asdf')
NameError: wrong constant name asdf
from (irb):22:in `const_defined?'
from (irb):22
I discovered that passing a string that is not capitalized is what causes this problem.
ruby-1.8.7-p330 :023 > Object.const_defined?('asdf'.capitalize)
=> false
This had me scratching my head for a minute. I hope it helps someone out.
To have extremely flexible DSLs, I occasionally find myself writing code like this:
if @options.class.name == 'Array'
@options = {'values' => @options}
elsif @options.class.name == 'Hash' && !@options.key?('values')
if @options.key? 'value'
@options['values'] = @options.delete('value')
end
end
Testing object type based on the .class.name string is extremely brittle. For example, what if @options is actually a HashWithIndifferentAccess?
Ruby has a much better method, kind_of?, defined on Object. The main reason kind_of? is better than testing the .class.name string is that kind_of? is aware of class hierarchies.
if @options.kind_of? Array
...
I just spent a while banging my head against activerecord. All my relations seemed right, but I kept getting the same error:
>> User.first.roles
ActiveRecord::HasManyThroughAssociationNotFoundError: Could not find the association "user_roles" in model User
Finally, I noticed that in my model, I had used a string to specify the :through value. Use a symbol!
Essentially, code that had to change was:
class User < ActiveRecord::Base
has_many :user_roles
has_many :roles, :through => "user_roles"
to:
class User < ActiveRecord::Base
has_many :user_roles
has_many :roles, :through => :user_roles
A frustrating error, but easily solved by using a symbol to do a symbol’s work.
If you want to see what your mysql server is working on – the current query, for example – there is an easy way to do it. Open your mysql prompt. This should be something like:
mysql -u root -p
then type this command:
SHOW PROCESSLIST;
You’ll then get a table listing what your server is working on at the moment. One of the entries will probably be processlist itself.
+-----+------+-----------------+------+---------+------+--------------+----------------------------+ | Id | User | Host | db | Command | Time | State | Info | +-----+------+-----------------+------+---------+------+--------------+----------------------------+ | 497 | root | localhost:58057 | lms | Sleep | 111 | | NULL | | 508 | root | localhost | NULL | Query | 0 | NULL | show processlist | | 509 | root | localhost | qrs | Query | 23 | Sending data | select count(*) from users | +-----+------+-----------------+------+---------+------+--------------+----------------------------+
You can kill a process that is taking too long or consuming too many resources. Just kill the number of the process (ID).
KILL 509
I am no longer out of a job. I am starting a new gig on Monday. After that I will be officially “in” a job. This is exciting!
I’m building a site for a record label I’m starting with some of the guys in my band. I imagine this will be an ongoing project for several months. I’m building it in Zend.
There are three basic types of users: consumers, bands, and admins (employees of the label). I decided the other day that a member of the admin team should be allowed to masquerade as any other user in the system without needing to know any login information. I also decided that I wanted the experience to be exactly as though the admin user had logged in as the other user. When the admin user is done being logged in as a band or a user, he should be able to hit the logout button and magically be logged in as his real (admin) user again, just as he was before he started.
Zend_Auth will store one identity at a time, but it allows you to use an auth_adapter to control who is logged in and when. So originally I thought it made sense to store a “masquerading” identity and a “real logged in person” identity in my adapter. What about a more complex situation where there are several levels of masquerading? What if a higher admin masquerades as a lower admin, and then as the lower admin masquerades as a user? So I decided that instead of keeping two identities, I would keep a queue of logged-in users in the auth adapter.
/**
* Medical_Auth - an auth adapter for use with zend_auth
*
* The adapter maintains a queue of logged-in people. At any given time you are
* logged in as the person on the top of the queue. If you masquerade as another
* user, your credentials are kept in the queue, while the other user's credentials
* are pushed to the top of the queue. When you log out (stop masquerading), your
* credentials are now at the top of the queue. You can masquerade as many levels
* deep as your heart desires.
*
*/
class Medical_Auth implements Zend_Auth_Adapter_Interface {
protected static $sess;
// instantiation of the auth adapter stores credentials to queue
public function __construct($email, $hashedpassword) {
self::getSess();
self::addCredentials($email, $hashedpassword);
}
protected static function getSess() {
if (!isset(self::$sess)) {
self::$sess = new Zend_Session_Namespace('medical.auth');
if (!is_array(self::$sess->emails)) {
unset(self::$sess->emails);
unset(self::$sess->passwords);
self::$sess->emails = array();
self::$sess->passwords = array();
}
}
}
// push credentials to top of queue
protected static function addCredentials($email, $hashedpassword) {
self::getSess();
self::$sess->emails[] = $email;
self::$sess->passwords[] = $hashedpassword;
}
// attempt to log in with email and password, using zend_auth
// -passing blank strings just uses the latest thing off the top of the queue
// -because it instantiates a medical_auth instance, it stores new creds to queue
protected static function forceLogin($email='', $hashedpassword='') {
self::getSess();
$newcreds = strlen($email);
if (!$newcreds) {
if (count(self::$sess->emails) == 0) {
return new Zend_Auth_Result(Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND);
}
$email = array_pop(self::$sess->emails);
$password = array_pop(self::$sess->passwords);
$adapter = new Medical_Auth($email, $password);
} else {
$adapter = new Medical_Auth($email, $hashedpassword);
}
$auth = Zend_Auth::getInstance();
return $auth->authenticate($adapter);
}
// returns true on successful masquerade, false otherwise
public static function masquerade($email, $hashedpassword) {
Zend_Auth::getInstance()->clearIdentity(); //forget logged in user
$result = self::forceLogin($email, $hashedpassword);
if ($result->isValid()) {
return true;
} else {
//revert to old login.
self::forceLogin(); // no credentials means go to old creds
return false;
}
}
/**
* Performs an authentication attempt
*
* @throws Zend_Auth_Adapter_Exception If authentication cannot
* be performed
* @return Zend_Auth_Result
*/
public function authenticate() {
self::getSess();
$email = self::$sess->emails[count(self::$sess->emails) - 1];
$password = self::$sess->passwords[count(self::$sess->passwords) - 1];
$db = Medical_Db::getInstance();
$result = $db->fetchAssoc('select * from user where email = ?' , $email);
$result = array_values($result); // reset keys
$ret = NULL;
if (!count($result)) {
// user does not exist
$ret = new Zend_Auth_Result(Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND,$email);
} else if ($result[0]['password'] != $password) {
// wrong password
$ret = new Zend_Auth_Result(Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID,$email);
} else {
$ret = new Zend_Auth_Result(Zend_Auth_Result::SUCCESS, $email);
}
if (!$ret->isValid()) {
// do not store invalid login credentials;
array_pop(self::$sess->emails);
array_pop(self::$sess->passwords);
}
return $ret;
}
public static function hashPassword($password) {
return md5('salt' . $password);
}
public static function hasIdentity() {
return Zend_Auth::getInstance()->hasIdentity();
}
public static function getIdentity() {
if (!self::hasIdentity()) return false;
return Zend_Auth::getInstance()->getIdentity();
}
// log out
// if there are more credentials in the queue, log back in with them
public static function clearIdentity() {
self::getSess();
Zend_Auth::getInstance()->clearIdentity();
array_pop(self::$sess->emails);
array_pop(self::$sess->passwords);
$email = '';
$password = '';
if (count(self::$sess->emails) > 0) {
// array_pop is the right thing because forceLogin will add it back to the queue
$email = array_pop(self::$sess->emails);
$password = array_pop(self::$sess->passwords);
}
if (!strlen($email)) {
// nothing more to grab from the queue, so log out for real
self::clearEverything();
return;
}
$result = self::forceLogin($email, $password);
if (!$result->isValid()) {
// somehow the data on the queue was wrong. This is weird. Log out.
self::clearEverything();
}
}
// a real life log out. Clear everything, destroy everything, log out all the way.
protected static function clearEverything() {
self::getSess();
unset(self::$sess->emails);
unset(self::$sess->passwords);
self::$sess->emails = array();
self::$sess->passwords = array();
// ran out of creds to log back in.
Zend_Session::destroy(true);
}
}
As you can see, there are plenty of helper methods in there for simplifying common tasks like checking to see if you’re logged in. But because I’m always using the built-in Zend_Auth mechanisms for storing the credentials, the standard things like Zend_Auth::getInstance()->getIdentity() will still work just fine. Of course, logging out should always be done through the provided Medical_Auth::clearIdentity().
As a sidenote, I have changed my Medical_Auth::hashPassword() method slightly. It does a little more than just adding a salted string and hashing in real life.
Anyway, in my authentication controller, where I process my login form, the code goes something like this:
// check credentials
$adapter = new Medical_Auth($form->getValue('email'),
Medical_Auth::hashPassword($form->getValue('password')));
$auth = Zend_Auth::getInstance();
$result = $auth->authenticate($adapter);
if (!$result->isValid()) {
// Invalid credentials
$this->_helper->flashMessenger('Invalid credentials provided');
$this->sess->login_form = $form;
$this->_helper->redirector('login');
} else {
$this->_helper->redirector('loggedin-home','index');
}
And to log out:
public function logoutAction() {
if (Medical_Auth::hasIdentity()) {
Medical_Auth::clearIdentity();
}
$this->_helper->redirector('login'); // back to login page
}
(the login page will just redirect to loggedin-home if the person is still logged in)
So you can see it’s actually really simple to do standard logging in and out. In the admin controller, when masquerading is requested, after policing rights and making sure the person logged in really is an admin, the following code puts the user into “chameleon mode”:
if (Medical_Auth::masquerade($email, $password)) {
$this->_helper->redirector('loggedin-home','index');
}
This is probably obvious to most of you, but I’m still getting used to my mac.
Between config files, svn commands, ssh’ing, and tailing errors logs, I live in terminal a lot. I made my terminal window transparent with a dark background. I find that when I hit command-N to get a new terminal window, the new window is just white with black text.
The solution? Click on the terminal window that is styled the way you want, then go to Shell->Use Settings as Default.
I feel silly that I didn’t figure this out in the first 3 seconds.
Lately I’ve been looking into researching svn repositories somewhere other than my personal servers. A friend tipped me off to free svn servers out there, and after some googling I had come up with quite a few. I had a few important requirements:
1. Free to use on small / personal projects
2. Multiple user accounts possible. I often work with other developers and designers.
3. Reasonable bandwidth and space limits.
4. Private repositories available. Some of my stuff is not open source.
5. Ability to interface with a bug tracker if necessary.
6. Ability to host git repositories is a plus.
Let’s compare services I found.
|
$$ |
# users |
# repos |
b/w |
space |
bug track |
SSL |
git |
|
|
ProjectLocker |
0 |
5 |
no limit |
no limit |
500 MB |
trac |
Y |
yes |
|
Beanstalk |
0 |
3 |
1 |
no limit |
100 MB |
?? |
Y |
? |
|
Unfuddle |
0 |
2 |
no limit |
no limit |
200 MB |
yes |
Y |
yes |
|
hosted-projects.com |
$7.00 |
unlimited |
no limit |
no limit |
100 MB |
trac |
Y |
? |
|
svnrepository.com |
$3.95 |
unlimited |
1 |
no limit |
500 MB |
trac, redmine |
? |
? |
|
codespaces |
$2.99 |
2 |
no limit |
no limit |
250 MB |
wikis |
? |
? |
|
Non-private options |
||||||||
|
Assembla |
||||||||
|
Google code |
||||||||
|
bountysource |
I must say that Project Locker is the clear winner to me!
I find myself using the Zend Framework more and more as I work on web applications. Recently I went back to an older project which was based on Zend 1.3, an ancient relic from the distant past of over a year ago. Currently Zend is on version 1.9. I realized that I had never really upgraded my version of Zend in any project, and decided that I wanted an automated process that kept all my projects up to date.
My solution is to use svn externals. The basic idea is that when I call ‘svn up’ on my library, it updates from both my repository and the external zend repository.
I decided to use the trunk, located at the following URL:
http://framework.zend.com/svn/framework/standard/trunk/
The first step is to decide where Zend will live. I’m doing this for a brand new project called “medical.” I created the following directories:
medical/library medical/application medical/public
Here, medical is the checked out version of my trunk.
I created a file called ~/zend_externals which contains the following definition:
Zend http://framework.zend.com/svn/framework/standard/trunk/library/Zend
The folder “Zend” will have an external repository pointing at the trunk of the Zend library.
Finally, navigate into the library directory and set up the external:
cd medical/library svn propset svn:externals -F ~/zend_externals .
Now you must commit the new properties to your own repository and perform an svn update to pull down the latest and greatest.
svn up svn ci -m "setting up svn:externals for zend" svn up
It really is that simple. I’m doing this on all my projects from now on.
