tag:pragdevnotes.com,2008:/activerecordActiverecord - Pragmatic Development Notes2010-10-03T20:31:26ZEnkiBoško Ivaniševićbosko.ivanisevic@gmail.comtag:pragdevnotes.com,2008:Post/142010-10-03T13:33:56Z2010-10-03T20:31:26ZActiveRecord SchemaDumper and MySQL problem<p>After finishing first version of <a href="http://github.com/bosko/rmre">Rmre</a> and issuing fix gem dependency in version 0.0.2, I got an idea for additional functionality. Why not use Rmre for dumping complete schema with all foreign keys data? What would be possible scenario for using this, one might ask? We have possibility to create ActiveRecord models in order to move to Ruby on Rails where main premise is to keep logic out of database and maintain it in application. Therefore we do not need foreign keys since we already have constraints defined in models.</p>
<p>But what if you cannot move to Ruby on Rails and you only have to change <span class="caps">DBE</span>, i.e. instead of MS <span class="caps">SQL</span> you must use Oracle? In that case you still have to work with legacy database from <span class="caps">PHP</span> or Hibernate in Java and “<em>only</em>” thing you have to do is to make create script for all tables but for another <span class="caps">DBE</span>. When database has hundreds of tables with lots of relations this can turn into nightmare, especially if you have to maintain both versions.</p>
<p>Rmre should simplify this. First you use Rmre to dump schema to some file and later you can use ActiveRecord’s capabilities to load it on different <span class="caps">DBE</span>. Since loading schema in ActiveRecord is <span class="caps">DBE</span> agnostic it should correctly create tables, indices and foreign keys on any database engine. That’s theory and, as usual, practice is a little bit different. On a very first step I’ve faced problem in MySQL database.</p>
<p>Let’s examine database with just a two tables – <code>city</code> and <code>country</code>. Create script would look like (example from <a href="http://dev.mysql.com/doc/sakila/en/sakila.html">Sakila</a> database):</p><table class="CodeRay"><tr>
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt>2<tt>
</tt>3<tt>
</tt>4<tt>
</tt>5<tt>
</tt>6<tt>
</tt>7<tt>
</tt>8<tt>
</tt>9<tt>
</tt><strong>10</strong><tt>
</tt>11<tt>
</tt>12<tt>
</tt>13<tt>
</tt>14<tt>
</tt>15<tt>
</tt>16<tt>
</tt>17<tt>
</tt>18<tt>
</tt>19<tt>
</tt><strong>20</strong><tt>
</tt>21<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="r">CREATE</span> <span class="r">TABLE</span> <span class="">city</span> (<tt>
</tt> <span class="">city_id</span> <span class="pt">SMALLINT</span> <span class="pt">UNSIGNED</span> <span class="r">NOT</span> <span class="pc">NULL</span> <span class="di">AUTO_INCREMENT</span>,<tt>
</tt> <span class="">city</span> <span class="pt">VARCHAR</span>(<span class="i">50</span>) <span class="r">NOT</span> <span class="pc">NULL</span>,<tt>
</tt> <span class="">country_id</span> <span class="pt">SMALLINT</span> <span class="pt">UNSIGNED</span> <span class="r">NOT</span> <span class="pc">NULL</span>,<tt>
</tt> <span class="">last_update</span> <span class="pt">TIMESTAMP</span> <span class="r">NOT</span> <span class="pc">NULL</span> <span class="di">DEFAULT</span> <tt>
</tt> <span class="">CURRENT_TIMESTAMP</span> <span class="r">ON</span> <span class="r">UPDATE</span> <span class="">CURRENT_TIMESTAMP</span>,<tt>
</tt> <span class="r">PRIMARY</span> <span class="r">KEY</span> (<span class="">city_id</span>),<tt>
</tt> <span class="r">KEY</span> <span class="">idx_fk_country_id</span> (<span class="">country_id</span>),<tt>
</tt> <span class="">CONSTRAINT</span> <span class="s"><span class="dl">`</span><span class="k">fk_city_country</span><span class="dl">`</span></span> <tt>
</tt> <span class="">FOREIGN</span> <span class="r">KEY</span> (<span class="">country_id</span>)<tt>
</tt> <span class="">REFERENCES</span> <span class="">country</span> (<span class="">country_id</span>)<tt>
</tt> <span class="r">ON</span> <span class="r">DELETE</span> <span class="">RESTRICT</span> <span class="r">ON</span> <span class="r">UPDATE</span> <span class="">CASCADE</span><tt>
</tt>) <span class="r">ENGINE</span>=<span class="">InnoDB</span> <span class="di">DEFAULT</span> <span class="di">CHARSET</span>=<span class="">utf8</span>;<tt>
</tt><tt>
</tt><span class="r">CREATE</span> <span class="r">TABLE</span> <span class="">country</span> (<tt>
</tt> <span class="">country_id</span> <span class="pt">SMALLINT</span> <span class="pt">UNSIGNED</span> <span class="r">NOT</span> <span class="pc">NULL</span> <span class="di">AUTO_INCREMENT</span>,<tt>
</tt> <span class="">country</span> <span class="pt">VARCHAR</span>(<span class="i">50</span>) <span class="r">NOT</span> <span class="pc">NULL</span>,<tt>
</tt> <span class="">last_update</span> <span class="pt">TIMESTAMP</span> <span class="r">NOT</span> <span class="pc">NULL</span> <span class="di">DEFAULT</span> <span class="">CURRENT_TIMESTAMP</span><tt>
</tt> <span class="r">ON</span> <span class="r">UPDATE</span> <span class="">CURRENT_TIMESTAMP</span>,<tt>
</tt> <span class="r">PRIMARY</span> <span class="r">KEY</span> (<span class="">country_id</span>)<tt>
</tt>) <span class="r">ENGINE</span>=<span class="">InnoDB</span> <span class="di">DEFAULT</span> <span class="di">CHARSET</span>=<span class="">utf8</span>;<tt>
</tt></pre></td>
</tr></table>
<p>As can be seen from above script table <code>city</code> has foreign key on table <code>country</code>. Now let’s see what is result of a dump:</p><table class="CodeRay"><tr>
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt>2<tt>
</tt>3<tt>
</tt>4<tt>
</tt>5<tt>
</tt>6<tt>
</tt>7<tt>
</tt>8<tt>
</tt>9<tt>
</tt><strong>10</strong><tt>
</tt>11<tt>
</tt>12<tt>
</tt>13<tt>
</tt>14<tt>
</tt>15<tt>
</tt>16<tt>
</tt>17<tt>
</tt>18<tt>
</tt>19<tt>
</tt><strong>20</strong><tt>
</tt>21<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="co">ActiveRecord</span>::<span class="co">Schema</span>.define(<span class="sy">:version</span> => <span class="i">0</span>) <span class="r">do</span><tt>
</tt><tt>
</tt> create_table <span class="s"><span class="dl">"</span><span class="k">actor</span><span class="dl">"</span></span>, <span class="sy">:primary_key</span> => <span class="s"><span class="dl">"</span><span class="k">actor_id</span><span class="dl">"</span></span>, <span class="sy">:force</span> => <span class="pc">true</span> <span class="r">do</span> |t|<tt>
</tt> create_table <span class="s"><span class="dl">"</span><span class="k">city</span><span class="dl">"</span></span>, <span class="sy">:primary_key</span> => <span class="s"><span class="dl">"</span><span class="k">city_id</span><span class="dl">"</span></span>, <span class="sy">:force</span> => <span class="pc">true</span> <span class="r">do</span> |t|<tt>
</tt> t.string <span class="s"><span class="dl">"</span><span class="k">city</span><span class="dl">"</span></span>, <span class="sy">:limit</span> => <span class="i">50</span>, <span class="sy">:null</span> => <span class="pc">false</span><tt>
</tt> t.integer <span class="s"><span class="dl">"</span><span class="k">country_id</span><span class="dl">"</span></span>, <span class="sy">:limit</span> => <span class="i">2</span>, <span class="sy">:null</span> => <span class="pc">false</span><tt>
</tt> t.timestamp <span class="s"><span class="dl">"</span><span class="k">last_update</span><span class="dl">"</span></span>, <span class="sy">:null</span> => <span class="pc">false</span><tt>
</tt> <span class="r">end</span><tt>
</tt><tt>
</tt> add_index <span class="s"><span class="dl">"</span><span class="k">city</span><span class="dl">"</span></span>, [<span class="s"><span class="dl">"</span><span class="k">country_id</span><span class="dl">"</span></span>], <span class="sy">:name</span> => <span class="s"><span class="dl">"</span><span class="k">idx_fk_country_id</span><span class="dl">"</span></span><tt>
</tt><tt>
</tt> create_table <span class="s"><span class="dl">"</span><span class="k">country</span><span class="dl">"</span></span>, <span class="sy">:primary_key</span> => <span class="s"><span class="dl">"</span><span class="k">country_id</span><span class="dl">"</span></span>, <span class="sy">:force</span> => <span class="pc">true</span> <span class="r">do</span> |t|<tt>
</tt> t.string <span class="s"><span class="dl">"</span><span class="k">country</span><span class="dl">"</span></span>, <span class="sy">:limit</span> => <span class="i">50</span>, <span class="sy">:null</span> => <span class="pc">false</span><tt>
</tt> t.timestamp <span class="s"><span class="dl">"</span><span class="k">last_update</span><span class="dl">"</span></span>, <span class="sy">:null</span> => <span class="pc">false</span><tt>
</tt> <span class="r">end</span><tt>
</tt> <tt>
</tt> execute <span class="s"><span class="dl">"</span><span class="k">ALTER TABLE city<tt>
</tt> ADD CONSTRAINT fk_city_country<tt>
</tt> FOREIGN KEY (country_id)<tt>
</tt> REFERENCES country(country_id)</span><span class="dl">"</span></span><tt>
</tt><span class="r">end</span><tt>
</tt></pre></td>
</tr></table>
<p>At the first glance this looks good but unfortunately doesn’t work. Problem is that loading this schema through ActiveRecord will create columns <code>city_id</code> in table <code>city</code> and <code>country_id</code> in table <code>country</code> as <code>integer</code> type but column <code>country_id</code> in table <code>city</code> is created as <code>smallint</code>. Defining constraint on columns which are not of same type is not allowed so last statement for altering table fails. At the moment I have no idea how to fix this and any suggestion is very welcome. I still have to check what happens on other DBEs: PostgreSQL, Oracle and MS <span class="caps">SQL</span>.</p>tag:pragdevnotes.com,2008:Post/132010-09-30T13:50:23Z2010-09-30T20:50:23ZRMRE - rails models reverse engineering gem<p>Very often I have to work on databases which do not follow ActiveRecord convention and making ActiveRecord models, if number of tables is large, is very slow and boring task. In order to speed up and simplify it I’ve created <a href="http://github.com/bosko/rmre">Rmre gem</a>. Gem is quite simple yet you might find it useful if you want to create fixtures, migrations or simply port application to Ruby on Rails.</p>
<p>So how it works? For each table in the database, gem creates model. Name of the model is created using Rails <code>classify</code> method. Moreover, if table’s primary key is not column named “id” gem sets primary key by adding <code>set_primary_key "primaryKeyColumnName"</code> line to the model. In addition for MySQL, PostgreSQL, Oracle or MS <span class="caps">SQL</span> foreign keys are analyzed and for each constraint gem generates <code>belongs_to</code> or <code>has_many</code> lines. Here is model created for table <em>store</em> in <a href="http://dev.mysql.com/doc/sakila/en/sakila.html">Sakila</a> MySQL test database:</p><table class="CodeRay"><tr>
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt>2<tt>
</tt>3<tt>
</tt>4<tt>
</tt>5<tt>
</tt>6<tt>
</tt>7<tt>
</tt>8<tt>
</tt>9<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="r">class</span> <span class="cl">Store</span> < <span class="co">ActiveRecord</span>::<span class="co">Base</span><tt>
</tt> set_primary_key <span class="sy">:store_id</span><tt>
</tt> set_table_name <span class="s"><span class="dl">'</span><span class="k">store</span><span class="dl">'</span></span><tt>
</tt> has_many <span class="sy">:customers</span>, <span class="sy">:class_name</span> => <span class="s"><span class="dl">'</span><span class="k">Customer</span><span class="dl">'</span></span><tt>
</tt> has_many <span class="sy">:inventories</span>, <span class="sy">:class_name</span> => <span class="s"><span class="dl">'</span><span class="k">Inventory</span><span class="dl">'</span></span><tt>
</tt> has_many <span class="sy">:staffs</span>, <span class="sy">:class_name</span> => <span class="s"><span class="dl">'</span><span class="k">Staff</span><span class="dl">'</span></span><tt>
</tt> belongs_to <span class="sy">:address</span>, <span class="sy">:class_name</span> => <span class="s"><span class="dl">'</span><span class="k">Addres</span><span class="dl">'</span></span>, <span class="sy">:foreign_key</span> => <span class="sy">:address_id</span><tt>
</tt> belongs_to <span class="sy">:staff</span>, <span class="sy">:class_name</span> => <span class="s"><span class="dl">'</span><span class="k">Staff</span><span class="dl">'</span></span>, <span class="sy">:foreign_key</span> => <span class="sy">:manager_staff_id</span><tt>
</tt><span class="r">end</span><tt>
</tt></pre></td>
</tr></table>
tag:pragdevnotes.com,2008:Post/102010-02-18T22:34:00Z2010-02-18T19:34:17ZRuby, Rails and MS SQL server<p>Setting up Rails and Ruby to use MS <span class="caps">SQL</span> server was always painful task. Fortunately things have changed – a lot! With new <a href="http://github.com/rails-sqlserver/2000-2005-adapter">Rails <span class="caps">SQL</span> Server 2000, 2005 and 2008 Adapter</a> and Christian Werner’s <a href="http://www.ch-werner.de/rubyodbc">ruby-odbc gem</a> you can do it in a few minutes.</p>
<p>If you want to use these gems on Windows grab Ruby installation from <a href="http://www.rubyinstaller.org">RubyInstaller</a> site and be sure to install <a href="http://rubyforge.org/frs/download.php/66888/devkit-3.4.5r3-20091110.7z">DevKit</a> prior to installing ruby-odbc.</p>
<p>Versions of ruby-odbc before 0.9999 do not work on mingw based (RubyInstaller) Ruby. Luckily author was very fast and made new version very quickly after I sent him a patch. Thanks Christian!</p>
<p>Both gems work well on Ruby 1.8.6 and 1.9.1 Ruby versions on Windows with old ActiveRecords, but I hope rails adapter will be ported to ActiveRecords 3 soon.</p>