myfitv live

Posted by Janos on February 07, 2010

Another successful project after six months of development. myfitv is live. You can see recommendations:

Find out what’s on:

…and of course, you can watch a show, a movie or just a clip:

Simple caching in Rails

Posted by Janos on February 04, 2010

We have seen the documentation on Rails caching. So the real question about caching, then, is where to put the code? Caching is a crosscutting concern, it can occur anywhere in the code, because anything can be cached. It does not go well with other modules. Caching code stays separate.

Almost all calls end up in ActiveRecord::Base. There is also first, last, but let’s focus on find for now. All queries go through Base.find – a class method.
We need to alias it so we can do more. The new method will be called find, but not before the old method is renamed to ‘old_find’.

alias old_find find

Nobody will know about the new find method, and that’s the right thing to do. When it’s called, it will check the cache to see whether the requested thing is there. If it’s there, it will return the value and never call the original cache. If there is a miss, the requested item is not there, the find method will call old_find. If old_find returns a value, it will cache that value.

value=Rails.cache.read(key)
 if !value
    value=old_find(*args)
     Rails.cache.write(key, value, :expires_in => 1.hours) if value
end

How do I generate the key? If we assume that all queries return different results, then one way to go is to use the method arguments. I will write a method like this:

def generate_key(*args)
    key="#{self.name}#{args.join}"
    key
end

I made it two lines for illustration so that you can put a breakpoint there and inspect the value in the debugger. I also use the name of the class since it’s possible to have a Video with id=94 and the Customer with id=94.

Preloading

You might be out of luck when referencing a cached object’s collections. This is because the connection to the database may be closed since the ‘parent’ object was cached. So if your videos have actors, for example, you might just want to call actors on your video to force the proxy to load the actors. Since finders either return a single object or multiple objects in an Array, I need to handle that:

def preload(result)
   if result.class==Video
      result.actors
      result.images
   elsif result.class==Array
      result.each do |video|
         video.actors
         video.images
     end
   end
end

Notice how you can do this for multiple collections.
Let’s put this all together:

if ENV['RAILS_ENV']=='development'
  RAILS_DEFAULT_LOGGER.debug "environment.rb: Enabling finder interceptor for Videos."
  class ActiveRecord::Base
     class << self
        alias old_find find
        def find(*args)
          begin
            #if defined?(@cache_finds) and @cache_finds
            if self.name=='Video'
              key=generate_key(*args)
              value=Rails.cache.read(key)
              if value
                logger.debug ">>>>>>>>> Returning "<<key<<" from cache."
              end
              if !value
                value=old_find(*args)
                preload value
                Rails.cache.write(key, value, :expires_in => 1.hours) if value
              end
            else
              value=old_find(*args)
            end
            value
          rescue Exception => e
              logger.error e
          end
        end

        def generate_key(*args)
          key="#{self.name}#{args.join}"
          key
        end

        def preload(result)
          if result.class==Video
            result.series
            result.images
          elsif result.class==Array
            result.each do |video|
              video.actors
              video.images
            end
          end
        end
      end
  end
end

Put this code in an appropriate location in Rails.
The beauty of the solution is that it keeps all other parts of the code clean – it is non-invasive. You can remove it, without affecting other places in the code. You can use any caching technology such as Redis, if you like. Or just use the MemoryStore that comes with Rails.

Starting and stopping mySQL on Snow Leopard

Posted by Janos on February 01, 2010

If you installed mySQL with ports, do the following:

cd /Library/LaunchDaemons
sudo launchctl unload -w org.macports.mysql5.plist
sudo launchctl load -w org.macports.mysql5.plist

Ajax Pagination

Posted by Janos on January 16, 2010

It’s often necessary to paginate in Ajax applications. For example, let’s say we have a tool for editors that lets them search for videos. The search is performed in one area and in an other area the user selects the page he wants to populate. Then he can just drag the chosen videos over the selected page. Full-page reload on pagination would be a problem, because the user would have to find the page he wants to edit again. We need to to able to paginate within the search results while leaving the rest of the page intact.

I did not want to implement pagination on the back end myself, so I took advantage of the mislaw-will_paginate gem.
The first task was to capture the pagination links. We render the links into a local variable like this:

@videos=Video.paginate :page=>params[:page]
pagination=ActionView::InlineTemplate.new("<%= will_paginate @videos %>", nil).render(@template, {})

The code to query the database would typically be more complicated, there would be chained named scopes, for example, and the call to ‘paginate’ would come last:

@videos=Video.most_popular.paginate :page=>params[:page]

We use JSON to communicate between the browser and the server. The video results are in an array generated by ActiveRecord. Let’s add the pagination string in the first position in the array:

@videos.unshift pagination # let's stick the pagination information in the first position
render({ :json => @videos.to_json(..) })

This does not disrupt the json encoding, the links stay a string while the rest of the array’s members get transformed according to the rules given in the to_json method.

On the JavaScript side, assuming that I have a div somewhere that looks like this:

<div id="pagination">
</div>

I can take resolve the pagination information and stick it to the div:

function onSearchResultsReady(results) {
  var pagination=results.shift();
  //processing here
  // ..
  $('#pagination').append(pagination);
  ajaxifyLinks();
}

The final step is to prevent the browser from reloading the page so that it instead makes an Ajax call when the user clicks on a page link. Notice the call to ajaxifyLinks. That function overrides the links’ default behavior:

function ajaxifyLinks() {
    $('#pagination a').click (function(){
        $.getJSON(this.href,
          {},
          onSearchResultsReady);
        return false; // don't follow the link
    });
  }

With minimal amount of work, we were able to make the gem do all the heavy-lifting, and even got our ajaxified pagination as well:

video_editor

Autotest

Posted by Janos on January 11, 2010

Autotest is a convenient way to keep your tests passing without having to continuously issue the rake rspec commands.
To use autotest, create a Rails project if you have not done so already:

rails huhu

cd into the new directory and enable RSpec:

./script/generate rspec

Now generate two RSpec-enabled models:

./script/generate rspec_model book title:string
./script/generate rspec_model author full_name:string

Notice how now you have two tests in spec/models.
Before running the tests, make sure your test database is ready:

rake db:migrate RAILS_ENV=test

Now from the root directory of the Rails application, execute:

./script/autospec

You should see all tests passing. Observe that the script does not exit.
Open a second shell and add a test like the following to spec/models/author_spec.rb:

it "should be stuff" do
    false.should== true
  end

Obviously, you would write a meaningful test but it illustrates the point. As soon as you save the file, you will see autotest execute in the first shell window. Here is the relevant output:

'Author should be stuff' FAILED
expected: true,
     got: false (using ==)
./spec/models/author_spec.rb:15:

Finished in 0.087639 seconds

2 examples, 1 failure

Now correct the test so that the offending line reads:

false.should==false

You’ll see how all the tests are rerun and there are no failures.
Edit the other test file, book_spec.rb, and add a passing test. You will see autotest execute only the tests in book_spec.rb.
However, if you add a failing test to book_spec.rb, then you correct it, you will see that this time autotest first executes the tests in book_spec.rb to check whether they all pass, then it goes on to execute all tests in all spec files.

It takes a little time to get used to the work style autotest offers, but it’s well worth the peace of mind to know that all your tests are passing.

Passenger on Ubuntu

Posted by Janos on November 26, 2009

These notes describe how to install Passenger in a typical environment.
First get the gem:

sudo gem install  passenger

Run the installer following its intructions:

sudo apt-get install apache2-prefork-dev
sudo passenger-install-apache2-module

The installer now will build and install the Apache module.

Once, that’s done, add the snippet given by the installer to /etc/apache2/apache2.conf. For example, it might look like this:

LoadModule passenger_module /usr/lib/ruby/gems/1.8/gems/passenger-2.2.7/ext/apache2/mod_passenger.so
PassengerRoot /usr/lib/ruby/gems/1.8/gems/passenger-2.2.7
PassengerRuby /usr/bin/ruby1.8

Let’s say we want to make the application available on port 3000. Add the port to /etc/apache2/ports.conf:

NameVirtualHost *:3000
Listen 3000

Create a file in /etc/apache2/sites-available choosing any name for it. In this example, let’s call it rails:

<VirtualHost *:3000>
  ServerName myserver.thruhere.net:3000
  CustomLog /var/www/ruby/log/access_log common
  ErrorLog /var/www/ruby/log/error_log
  DocumentRoot /somewhere/myrailsapp/public
</VirtualHost>

Notice how the port matches the entry in ports.conf.
Now go to /somewhere/ and type rails myrailsapp. Once the application is generated, restart Apache:

sudo /etc/init.d/apache2 restart

Now if you go to the url specified in the file rails, you should see the Rails welcome page:


http://myserver.thruhere.net:3000

Enable mod rewrite in Apache

Posted by Janos on November 26, 2009

Enable url rewriting like this:

sudo a2enmod rewrite

Then restart Apache:

sudo /etc/init.d/apache2 restart

In the file /etc/apache2/sites-available/default, in the section <Directory /var/www> make sure that ‘AllowOverride All’ appears:

<Directory /var/www/>
          Options Indexes FollowSymLinks MultiViews
           AllowOverride All
           Order allow,deny
           allow from all
</Directory>

Evolution of languages

Posted by Janos on November 17, 2009

I went to a talk given by Yukihiro Matsumoto (see picture taken with my fantastic Samsung phone) and somebody posted him the question, “What do you think about C++?” Matz was shaking his head. He considers himself a C programmer and sees nothing important in C++.

matsumoto

Later at a party, I bumped into a friend of a friend. Again the talk drifted to C++, this time because he works on financial applications and uses it. I have not touched C++ in more than a decade. He told me that C++ came a long way since the 1990s and now it’s cleaner and easier to use. How its libraries (STL, etc.) now take care of calling delete when necessary so today’s programmer almost never has to use the new and delete keywords. Great. Sounds good. It is not going to take me back to C++.
Eliminating certain language elements is not specific to C++. High-quality frameworks in other languages also take care of low-level tasks for you, such as wiring up singletons to each other and creating instances of domain objects.
In the days when I discovered Java I decided that, until I worked on a real Java project, I would write my C++ code so that it looked like Java. It did help me write better code, but eventually I had to change my job.
I am reminded of a similar thing with CVS and Subversion. Subversion was meant to solve problems with CVS such as renaming files, but it still did not tackle real problems like dependency on the network and the speed of the network. Sometimes you need to move on and not try to perfect the steam engine.

Enabling ssh on iTouch

Posted by Janos on November 04, 2009

I googled around and found the following answers on several sites. Here is the quick rundown of steps.

1. Install blackra1n from http://blackra1n.com
2. You may have to run blackra1n several times until the phone reboots without going to iTunes. The Blackra1n icon will not always be on the first screen, scroll to the second one if you don’t see it.
4. Press Settings, General, Auto-Lock and set it to Never. This is to prevent accidental reboots.
5. Install Cydia using Blackra1n.
6. In Cydia, look for OpenSSH and install it.
6. Go back to the All Packages folder, and select SBSettings. Install the program.
7. Slide your finger across the top bar until you get a settings screen. Make sure the ssh icon and the wifi icon are green. Note the WiFi IP Address.
8. Go to a computer and ssh into your iTouch. Type ‘root’ for user and ‘alpine’ for password. The inital connection may take minutes.
9. When you log in, you will notice that Applications folder does not have enough space. You can move it to the second ‘disk’ with more room like this:

mv Applications /private/var/
ln -s /private/var/Applications /Applications

The blog of the creator of Blackra1n is here: http://iphonejtag.blogspot.com/

Install Ruby on Rails on a Mac

Posted by Janos on July 27, 2009

We don’t want boring colors, right?
export CLICOLOR=1
export LSCOLORS=ExFxCxDxBxegedabagacad
Install latest Xcode:
xcode-xxxx_developerdvd.dmg
install macports
Final check on mac ports:
sudo port selfupdate

Install Rubygems:
gunzip rubygems-1.3.5.tgz
Rails and whatever you need:
sudo gem install rails
sudo gem install cucumber rspec

Install and configure Git:

sudo port install git-core
git config --global github.user janosmuxxi
git config --global github.token ad492d3dac660859611b0
git config --global user.name "Janos Mucsi"
git config --global user.email "janos.mucsi@pragmaticruby.net"

Check the settings:

git config --global --list

copy your private key to ~/.ssh
make sure it is readable only by you
get the project you want:
git clone git@github.com:blah/blah.git
If you use mySQL:

sudo port install rb-mysql
sudo port install mysql5-server
sudo -u mysql mysql_install_db5
sudo gem install mysql -- --with-mysql-config=/opt/local/lib/mysql5/bin/mysql_config

start mysql

sudo /opt/local/lib/mysql5/bin/mysqld_safe &
sudo launchctl load -w /Library/LaunchDaemons/org.macports.mysql5.plist
PATH=/opt/local/lib/mysql5/bin:$PATH