Webmaster's Notes

Sunday, May 25, 2008

music: iPhone sync, iTunes alternative

A post unrelated to coding - about a problem that was occupying my hobby time ever since I got the iPhone - it's called iTunes, one of the most offending pieces of commercial software I have seen, right after RealPlayer and Quicktime. Apple is notorious for unusable software (while having the best hardware design), as if they outsource their coding to some African tribe, receiving the deliverables engraved on banana leafs.

So, I've had enough of iTunes when one day it removed all of my tracks on the 16GB iPhone. Before that, it was making computer unusable when opening (pretty small by today's standards) 60Gb music lib, suddenly destroying the library index, being unable to sync with more than one computer (amazing brainstorming Apple!) and so on. Considering that iTunes is a total joke in it's music organization attempts with the need to manually add/remove every file to the library, manage tags and album art by hand, not showing directory album structure, and forcing it's own tag-based categorization it could be only useful for random pop-track purchasing iTunes customers. Uninstalled, and started looking for some sane alternatives. I have tried mostly every piece of software that claimed it could sync iPhone/iPod music with a Windows computer, including iPhone-based ones, with no luck - they either don't work with the new Apple encryption, or don't do their simple job well, crashing left and right.

Relief came when I discovered and installed a little gem called MediaMonkey. Purchasing it was a no-brainer - even though it syncs through the evil iTunes, you can erase iTunes's library and disable iTunes syncing so it should not be able to do any harm. MediaMonkey works with the existing music library wonderfully, through a set of views and filters, not locking you into a stupid "vision", with a lightning fast presentation of any amount of albums. It converts FLAC/OGG to iPod-compatible mp3 on the sync (once), so you don't have to keep two copies of the same albums. You can view the track/album/artist conveniently and you can re-tag albums from freedb/web. You can actually see the directory structure from within the program. You are able to get album art without using iTunes Store. Rip/Burn Cd-Dvds. Music DB is automatically updated with watched folders feature (yes you don't have to add each file manually). And so much more is done right in that little Monkey-driven app that comparing it to iTunes is an offense. Did I mention that the sync check takes 1 second, instead of eons with iTunes?

Apple software engineers should download a trial version of MM and then go hang themselves in shame.

Labels: , , ,

Tuesday, March 25, 2008

RoR: Caching Dynamic Association Conditions

The problem which is verbosely described in a previous post on dynamic associations still does not have a clean solution - I've been researching possible workarounds, and there is no definite answer. One feasible workaround is specifying :conditions => 'send(:method)' in single quotes, this way Rails will only eval the conditions when forming the SQL string. This, and what was suggested in my previous post, both would work... Unless you would want to reuse that association with different (or no) conditions later on.

And here comes the trouble - Rails preloads all model classes on startup, and any changes that your controller actions do to the model classes stay cached for all further requests. So on production system, modifying an association in run-time would affect all future references through that association (and that's in fact what has happened to me). We better restore the conditions back after modifying them in with_conditions method. But if your Rails app is anything complicated, it probably uses delayed loading when rendering the views, so you can not reset association conditions right away in the model. Let's delay it until the next request:

class ActiveRecord::Base
@@saved_conds = Hash.new

# dynamically modify conditions as there is no other way in Rails
# to specify run-time conditions on joins...
def self.with_conditions(assoc, conditions)
@@saved_conds[self.to_s] ||= Hash.new
# only save if that's the first call during this request
@@saved_conds[self.to_s][assoc] ||=
reflect_on_association(assoc).options[:conditions]
reflect_on_association(assoc).options[:conditions] = conditions
yield
end

# reset association conditions if any has been modified by our
# with_conditions calls in the previous request
def self.reset_conditions
return if @@saved_conds.empty?
@@saved_conds.each { |klass,associations|
associations.each { |assoc,saved|
model = klass.constantize
model.reflect_on_association(assoc).options[:conditions] = saved
}
}
@@saved_conds = Hash.new
end
end


This solution I came up with is pretty hacky - but it works. If you know of a better way to mark a class for reload in Rails, let me know. This code saves modified conditions in a hash by class and association and then you would need to add code to restore them to a mint condition in a before_ or around_ filter in ApplicationController (call like Magazine.reset_conditions). Using class name as a Hash parameter since class variable behavior in Ruby is strange to say the least - it is shared in all inherited classes and the parent class :)

Labels: , , , ,

Wednesday, February 27, 2008

PHP: XCache Installation

A quick note to self: IBM site has a very nice writeup on installing XCache, a php opcode and variable caching which seems popular lately. Other sources of information are missing all the important details which leads to wasted time. Visit the linked page for details (http://www.ibm.com/developerworks/library/os-php-fastapps1/).

It does not have a walkthrough on configuring xcache options in the php.ini or xcache.ini, but they are self-explanatory if you read comments. Out of practice, variable cache (xcache.var_size) has to be about 10% size of the opcode cache (xcache.size).

Labels: , , ,

Sunday, February 10, 2008

RoR: Overriding Comparison Operators in Your Model

Ruby allows you to override everything. Ruby allows you to override everything using two lines of code. You just got to love ruby! This small entry is about overriding comparison operators (or, to be correct, it is about bulk-defining comparison operators for user class).

Common use case from Ruby on Rails is to define a simple model which is associating a Fixnum with some additional properties (like string name): examples are sport leagues (number), or bank account (balance), and so on. Whenever the primary objective of your model (say, League) is to store the number, you would want to override the comparison operators so that instead of that ugly code:

league.number > other_league.number
...
leagues.sort! {|a,b| a.number <=> b.number}


you would write this beautiful ruby:

league > other_league
...
leagues.sort!


But overriding each comparison operator would quickly make your model look like some java code or worse. Ruby "meta-programming" to the rescue:

Class League < ActiveRecord::Base
%w(<=> == < > <= >=).each do |operator|
define_method operator do |other|
case other.class.to_s
when "League"
self.number.send(operator,other.number)
when "Fixnum"
self.number.send(operator,other)
else
raise ArgumentError, "Illegal argument"
end
end
end
end


This not only would allow you to compare League with some other League in every possible perverted way you please, but also to compare apples and oranges, i.e. League and a Fixnum, and get an ArgumentError when comparing it to anything else.

You may find it useful to compare with a String, too, if a string attribute is sensible identifier for your model. We use send here to evaluate comparison operator for our number attribute, but you can send to String attributes just as easily (and to anything else which already has it's own comparison operators defined).

But, surely, while this code above defines 6 methods for you while keeping it DRY, there is a better way - using Comparable mixin. It would define 6 comparison methods (plus between?(min,max) method) all based on the <=> operator, and the code will look much clearer:

class League < ActiveRecord::Base
include Comparable

def <=>(other)
case other.class.to_s
when "League"
self.number <=> other.number
when "Fixnum"
self.number <=> other
else
raise ArgumentError, "Illegal argument"
end
end
end


Have fun!

Labels: , , , ,

Friday, January 25, 2008

Ruby on Rails: Dynamic Association Conditions Using Reflection

Associations between models are part of what makes Ruby on Rails framework so elegant, define Class Magazine belongs_to :publisher and Class Publisher has_many :magazines and you can simply use magazine.publisher or publisher.magazines without worrying about underlying database and object construction details. Basic associations do work for most cases, but sometimes you would need to go deeper.

Extending the Magazines example, consider that you have a third model, Reader, which has a many-to-many relationship with the Magazine model through a Subscription model:

Class Reader < ActiveRecord::Base
has_many :subscriptions
has_many :magazines, :through => :subscriptions
end

Class Subscription < ActiveRecord::Base
belongs_to :magazine
belongs_to :reader
end

Class Magazine < ActiveRecord::Base
has_many :subscriptions
has_many :readers, :through => :subscriptions
belongs_to :publisher
end

Class Publisher < ActiveRecord::Base
has_many :magazines
end

All nice and clean so far. But imagine you want to do something like output a list of publisher.magazines for a specified reader. For this, you would need to add conditions to an association. Condition for association is simply an SQL snippet which is applied to the LEFT OUTER JOIN (a database way of saying "I need all matching rows from the first table with rows from the second table if they exist") when you are doing find's on the model with :include => <association>,

# Adding condition here applies it to 
# the WHERE clause of your SELECT
# So this line selects publisher's magazines
# that specified reader is subscribed to
publisher.magazines.find(:all,
:include => :subscriptions,
:conditions => ["subscriptions.reader_id = ?",
reader.id])

# Adding a condition on association applies it
# to the left outer join
# So this code selects all magazines for the
# publisher, including only expired subscriptions
Class Magazine < ActiveRecord::Base
has_many :expired_subscriptions,
:conditions => "subscriptions.expiration <= now()"
end
publisher.magazines.find(:all,
:include => :expired_subscriptions)


However, sometimes this is not sufficient, too :) While you can dynamically specify WHERE conditions, there is no straightforward way to dynamically specify conditions for the LEFT OUTER JOIN (association conditions). So, you can't select all publisher.magazines while only loading subscriptions for a selected reader. This is needed, for example, if you want to show publisher.magazines which the current reader is subscribed to while also showing all the other magazines from this publisher with no subscription details.

There are two solutions, one is non-Railsy and involves writing find_by_sql with a full SQL query, which in this particular example would be very cumbersome. The other solution is to override the association object's conditions dynamically, shown below:

Class Magazine < ActiveRecord::Base
# The method which dynamically modifies association's
# condition. Can also be added as ActiveRecord::Base
# extension, then it will be available for all models
def self.with_conditions(assoc, conditions)
options = reflect_on_association(assoc).options
options[:conditions] = conditions
yield
end

# Finds all magazines sorting reader's magazines
# by reader's subscription date
def self.find_for_reader(reader)
with_conditions(:subscriptions,
["subscriptions.reader_id = ?", reader.id]) do
find(:all,
:include => :subscriptions,
:order => "subscriptions.expiration")
end
end
end

# And finally, this call somewhere in controller
# achieves our goal of displaying *all* publisher
# magazines sorted by selected reader's subscription
publisher.magazines.find_for_reader(current_reader)


self.with_conditions basically just adds passed conditions to the specified association, the same thing that specifying :conditions => {...} for the association does, but not limited to static predefined values. The trick is possible with reflect_on_association, a Reflection method which allows us to modify associations in runtime (as any other ruby entity could be).

NOTE: I did not find a way to save and restore association conditions back after the yield call, as restoring options[:conditions] yields the block with the last assigned conditions. So keep in mind that any association used in with_conditions call will have it's conditions changed for the duration of the current request. Fixes accepted :)

This solution is working well in production on Rails 1.2.3-1.2.6

Labels: , , ,