Archive for the ‘Ruby on Rails’ Category

Timestamp vs. Datetime

We encountered a strange behavior in our Rails application: created_on timestamp was sometimes younger than updated_at_.

For each item in our application, we had a created_on and an updated_at field in the database. These were supposed to be updated by the Rails framework at item creation and item update times respectively. However, created_on got to be updated more reliably, even for small flag changes. We tried

ActiveRecord::Timestamp.record_timestamps = false

but things got even worse: created_on kept changing with every little update but updated_at remained untouched. So, we sometimes had a strange situation that created_on was younger than updated_at.

So what could help? Read the good old manual? Well, sometimes you just have to…

And there it was: even though Rails does not seem to distinguish much between :datetime and :timestamp, our MySQL database has the following default behaviour:

The first TIMESTAMP column in table row automatically is updated to the current timestamp when the value of any other column in the row is changed, unless the TIMESTAMP column explicitly is assigned a value other than NULL.

So the innocently looking:

CREATE TABLE items (
…
created_on timestamp NOT NULL,
…

in create_tables.sql is completely wrong (don’t ask me how it got there, it has survived an unbelievable number of refactorings). This is made obvious by

mysql> show create table items;
…
`created_on` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
…

What helped us in the end was:

mysql>alter table items modify created_on datetime NOT NULL;
update items set created_on=updated_at where created_on>updated_at;

Note: actually the error showed only for small flag changes, done by update_attributes. When using the full update, Rails keeps the correct old value of created_on.

Rails upgrade from 1.1.6 to 1.2.3

I have just finished rails and gems upgrade and it was surprisingly smooth. There were just one minor issue.

The server was not able to start. It was teling me something like:

/opt/local/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:266:in `load_missing_constant’: uninitialized constant Recconfig (NameError)
        from /opt/local/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:452:in `const_missing’
        from /opt/local/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:464:in `const_missing’
        from ./script/../config/../config/environment.rb:34
        from /opt/local/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/initializer.rb:41:in `run’
        from ./script/../config/../config/environment.rb:15
        from /opt/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require’
        from /opt/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require’
        from /opt/local/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:495:in `require’
         … 11 levels…
        from /opt/local/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:495:in `require’
        from /opt/local/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:342:in `new_constants_in’
        from /opt/local/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:495:in `require’
        from script/server:3

The solution was trivial. We are using an object Recconfig to store configuration of the web site. We do load and initialize the object in the environment.rb. In the 1.1.6 it was enough to write

recconfig = Recconfig.create

in the new version it was necessary to require it.

require ‘recconfig’

Loading data in migrations

I found migrations to be very useful tool to maintain data structures in a rails project. It is clean, easy to understand and fast when it comes to recreate a database.

During develoment, I faced several times the same problem: “How to import static data into the database?” Of course, one can create fixture and load it. But sometimes one needs more powerful tool.

So, I tried to import the data in the migrations themselves. Since the data to import was huge (tens of MBs), I did try several methods and approaches. Here is the result.

Fixtures in Migrations

This is method that I found on the web in an article of Adam Christensen Loading Fixtures in a Migration. It shows how to load the fixtures file into database.

require 'active_record/fixtures'

class CreateCategories < ActiveRecord::Migration
  def self.up
    create_table :categories do |t|
      t.column :name, :string
    end

    Fixtures.create_fixtures(’test/fixtures’, File.basename(”categories.yml”, ‘.*’))
  end
  def self.down
    drop_table :categories
  end
end

Active record way

If your ActiveRecord object already exists, you can use the following code. It is obviously not the way to import thousands of records, but for ten objects it is good enough.

class CreateAuthors < ActiveRecord::Migration
    def self.up
      create_table :authors do |t|
          t.column :name, :string, :length=>20, :null=>false
          t.column :surname, :string, :length=>50, :null=>false
          t.column :blog, :string, :length=>100, :default=>'http://blog.zmok.net'
      end
      Author.create( :name=>'Roman', :surname=>'Mackovcak')
    end
    def self.down
      drop_table :authors
    end
end

External CSV

If the data came in the CSV format then a fastercsv library could be used.

First of all, the library needs to be installed.

sudo gem install fastercsv

then create the migration

require 'fastercsv'

class LoadAuthors < ActiveRecord::Migration

  def self.up
    FasterCSV.foreach(’db/data/authors.csv’) do |row|
      Author.create(:name=>row<sup><a href=”#fn14068651694926bca8a5dde”>0</a></sup>, :surname=>row<sup><a href=”#fn10810929634926bca8a6d82″>1</a></sup>, :blog=>row<sup><a href=”#fn1048664414926bca8a7d1a”>2</a></sup>)
    end
  end
  def self.down
    Author.delete_all
  end
end

And place the CSV file (example below) into db/data folder.

"Miro","Skultety","http://blog.zmok.net"
"Abhishek","Balaria","http://blog.zmok.net"

Embedded CSV

Sometimes it is useful to bundle the data directly with the migration. In this example, the data are provided to the load_articles method from articles_data.

require 'csv'

class CreateArticles < ActiveRecord::Migration
  def self.up
    create_table :articles do |t|
      t.column :title, :string, :length=>20, :null=>false
      t.column :url, :string, :length=>100, :null=>false
      t.column :author_id, :integer, :null=>false
      t.column :abstract, :string, :length=>255
    end

    load_articles
  end
  def self.down
    drop_table :articles
  end
  def self.load_articles
    cnx = ActiveRecord::Base.connection
    ActiveRecord::Base.silence do
      reader = CSV::Reader.create(articles_data)
      reader.each do |row|
        values = row.collect {|v| cnx.quote(v).gsub(’\\n’, “\n”).gsub(’\\r’, “\r”) }.join(’, ‘)
        sql = “INSERT INTO articles(id, title, url, author_id, abstract) VALUES (#{values})”
        cnx.insert(sql)
      end
    end
  end
  def self.articles_data
    <<’END_OF_DATA’
1,”Integration is the killer app (even in Web 2.0)”,”http://blog.zmok.net/articles/2006/08/20/integration-is-the-killer-app-even-in-web-2-0″,3,”Integration is the key to success”
2,”Visualize your Rails schema”,”http://blog.zmok.net/articles/2006/11/13/visualize-your-rails-schema”,2,”Exporting rails schema to UML”
END_OF_DATA
  end

end

ActiveRecord – native commands

Last, but not least is the method that uses native functions of MySQL database. This could work only if the rails application sits on the same machine as the MySQL.

require 'tempfile' 

class CreateReaders < ActiveRecord::Migration
    def self.up
      create_table :readers do |t|
          t.column :nickname, :string, :length=>20, :null=>false
          t.column :email, :string, :length=>50, :null=>false
      end
      load_from_file
    end

    def self.down
      drop_table :readers
    end
    def self.load_from_file
      file = ‘db/data/readers.csv’
      tp = Tempfile.new(File.basename(file)).path
      File.copy(file, tp)
      File.chmod(0666, tp)
      ActiveRecord::Base.connection.execute(”load data infile \’#{tp}\’ into table readers fields terminated by \’,\’ enclosed by \’\”\’ lines terminated by \’\\n\’”)
    end

end

and the corresponding CSV file

1,"bob","bob@really-cool-office.org"
2,"sergey","sergey@hermitage-magazin.ru"

That’s it! Enjoy it.

Ruby on Rails with Oracle

I am working on a prototype in an big company and they do store data in Oracle. It took me some time to set up RoR working with Oracle. So, here are the things you need to do.

First of all, there is a great tutorial on Oracle site: http://www.oracle.com/technology/pub/articles/haefel-oracle-ruby.html.
In fact, you need to:

Download Ruby OCI library from http://rubyforge.org/projects/ruby-oci8

Configure access to Oracle in database.yml

development:
  adapter: oci
  username: your_username
  password: your_password
  host: HOST_NAME

Create a sequencer for each table (rails uses it to generate id). The sequencer must follow naming convention (see script below). You can create one using:

CREATE SEQUENCE <<table_name>>_seq;

Since I am using one of the non-english character sets and the Oracle instance uses ISO 8859-2 and it is not compatible with Windows code page, I had problems with encoding. To avoid it, it is necessary to set up variable NLS_LANG. I did it in environment.rb.

ENV[’NLS_LANG’]=’czech_CZECH REPUBLIC.UTF8′

Now you should be able to access the data source, perform basic operations with data, and see the data correctly.

Ruby on Rails in enterprise

Ruby is great! Ruby is cool! It will penetrate in enterprises within few months.
These are some of the ideas that occupies one’s mind during the RoR excitement period. But things are not always so simple.

Enterprises does not behave same as small companies do. Their IT complexity reached one of the highest level and the only way how to manage it, is to follow internal standards and management rules. Complexity is the reason why the big companies are often so rigid concerning new platforms.
The enterprise IT managers expects

  • Strong partner that can help in case of big troubles
  • Wide specialist support on market, not to be too dependent on one vendor and to be able to expand system built on new technology themselves
  • Technical support, bug fixes, hot-line… simply, a set of services that guarantees quick production problem fix
  • Consolidation – the managers does not want to add new platform to the enterprise. And if they add one, they expect at least one platform will go away

Fortunatelly, there are still oportunities for new platfoms. If there is a need for certain functionality and the only acceptable is built on a new technology, it is probable that the new technology will come into the enterprise.

Prototyping

And RoR covers a need that is very important and currently does not have any reasonable alternative. RoR could be used for prototyping.

  • You do not need a strong partner for prototyping, because the prototype should never go into production.
  • You do not really need a wide specialist support. If the enterprise does not have the RoR specialists, it can behave the same as before
  • There is not high demand for technical support. Simply, if something minor does not work in prototype, nothing happens. Business will understand it.
  • Consolidation is also “no issue”. The platform is needed for a short period of time, it will never be in production and thus does not cause addiditonal mess in IT environement.

I have discussed this issue with few companies delivering to enterprises. They currently use prototyping, but it is based on java or .Net. They create a set of screens that is used for further application generation. I can see few weak points in this approach

  • Prototype is not part of the development phase, but part of the analytical phase. Its main purpose is to communicate user requirements. No user will tell you what he/she wants without your help. The best help I can see is a working application. Once something works, users starts to imagine the possibilities.
  • Prototype should be thrown away. It should not be used for further development. Simply, during the prototyping phase there is a lot of garbage code created.

Why Rails?

Let me do small +/- analysis

Positives

  • It uses enterprise design patterns like Active record, MVC (Model View Controller) and what is the most important, it is hard to avoid it!
  • Since the RoR uses the patterns, it will be a solid base for high quality design of the final application
  • RoR is build on ruby. Thus the prototype must be thrown away. I know, managers does not like to throw anything away, but from my experience, the best pieces of code are the ones that were lost and I did rewrite them completely.
  • Increases productivity (I have seen a comparison of ruby/java productivity. It was around 10:1. Nevertheless, from my experience, creation of an application skeleton in RoR is faster than configuration of hibernate)

Negatives

  • No big player supporting it. This is not a problem for prototyping. It could be a competitive advantage of smal flexible companies. And of course, this might change.
  • Missing integration to enterprise systems like domain controllers, messaging systems…
  • It brings a new platform into enterprise
  • There is stil not enough specialists to build stable teams. There are specialised companies that uses RoR, but enterprises needs massive support.

Nevertheless, in prototyping, the mentioned issues are minor ones.

Future

So what should be expected in the near future? It seems, that developer’s community is growing. But not only in small companies. Our blog have hits from the biggest IT companies.
I expect that sooner or later the big consulting companies will add RoR into their standard portfolio of supported technologies.