<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-25445709</id><updated>2011-07-29T01:21:18.613+04:00</updated><category term='javascript'/><category term='russian'/><category term='rails'/><title type='text'>Software development</title><subtitle type='html'>My thoughts on software development</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>28</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-25445709.post-6223126803507642276</id><published>2010-05-18T13:48:00.000+04:00</published><updated>2010-05-18T13:48:38.098+04:00</updated><title type='text'>Test until pass</title><content type='html'>"Test until pass" is this week one-liner.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-6223126803507642276?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/6223126803507642276/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=6223126803507642276' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/6223126803507642276'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/6223126803507642276'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2010/05/test-until-pass.html' title='Test until pass'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-5647995947316032662</id><published>2010-02-02T11:18:00.000+03:00</published><updated>2010-02-02T11:18:09.381+03:00</updated><title type='text'>Do you use LiveJournal ?</title><content type='html'>Every time I visit someone's page on LiveJournal, I see something like &lt;a href="http://ooprizrakoo.livejournal.com/"&gt;this&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;It reminds me a &lt;a href="http://www.youtube.com/watch?v=uMyEGgbrJBw"&gt;Homer Simpson's web page&lt;/a&gt;. How people can use that crap ? Duh.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-5647995947316032662?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/5647995947316032662/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=5647995947316032662' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/5647995947316032662'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/5647995947316032662'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2010/02/do-you-use-livejournal.html' title='Do you use LiveJournal ?'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-1229430394334738095</id><published>2009-09-14T11:10:00.009+04:00</published><updated>2009-09-16T19:14:51.235+04:00</updated><title type='text'>Building tree out of nested set</title><content type='html'>&lt;style type="text/css"&gt;    &lt;!--      .post-body { text-align: justify; }      code.ruby {        display: block;        overflow: auto;        padding: 1em;        color: #ffffff;        background-color: #000000;        font-size: 100%;      }      .comment {        /* font-lock-comment-face */        color: #ff0000;      }      .function-name {        /* font-lock-function-name-face */        color: #0000ff;        font-weight: bold;      }      .keyword {        /* font-lock-keyword-face */        color: #00ffff;        font-weight: bold;      }      .type {        /* font-lock-type-face */        color: #00ff00;      }      .variable-name {        /* font-lock-variable-name-face */        color: #ffff00;      }      a {        color: inherit;        background-color: inherit;        font: inherit;        text-decoration: inherit;      }      a:hover {        text-decoration: underline;      }    --&gt;    &lt;/style&gt;&lt;br /&gt;This keeps popping up over and over again. About 2 years ago I mentioned solution on how to build tree out of nested set, but the solution was lost in google groups. I'm going to publish it here.&lt;br /&gt;&lt;br /&gt;So, nested set: every item has LEFT and RIGHT numbers. LEFT number is less than any LEFT or RIGHT number of any descendant item. RIGHT number is greater than any LEFT or RIGHT number of any descendant item. To get a subtree, select all items with LEFT greater than given LEFT and RIGHT less than given RIGHT number. You get the flat array of items of that tree structure. Next thing is to convert it to tree which means for any given item you need the ability to iterate over all of it's children.&lt;br /&gt;&lt;br /&gt;First thing you need to do is to order items by LEFT number, than by RIGHT number.&lt;br /&gt;&lt;br /&gt;A small sidenote: we will assume that LEFT and RIGHT numbers are compact so that if your root item has LEFT=1 and RIGHT=10 then for each number X in interval [1, 10] there is item for which X is the value of LEFT or RIGHT. This allows easy calculation of how many descendants particular item has.&lt;br /&gt;&lt;br /&gt;So, say we have an flat array and we need to know how many items in array you need to skip to get to given item's next sibling. And that number is (RIGHT-LEFT) (minus one if you do not count the item itself).&lt;br /&gt;&lt;pre&gt;&lt;code class="ruby"&gt;&lt;span class="comment"&gt;# Class that allows performance wise traversing a list of items&lt;br /&gt;# that form a part of nested set structure.&lt;br /&gt;&lt;/span&gt;&lt;span class="keyword"&gt;class&lt;/span&gt; &lt;span class="type"&gt;NestedSetTreePresenter&lt;/span&gt;&lt;br /&gt;  include &lt;span class="type"&gt;Enumerable&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class="comment"&gt;# Constructs presenter with a items that form part of nested set,&lt;br /&gt;&lt;/span&gt;  &lt;span class="comment"&gt;# offset of first item on desired level and count - number of items&lt;br /&gt;&lt;/span&gt;  &lt;span class="comment"&gt;# on current level.&lt;br /&gt;&lt;/span&gt;  &lt;span class="comment"&gt;# Items should be ordered by their left bound values.&lt;br /&gt;&lt;/span&gt;  &lt;span class="keyword"&gt;def&lt;/span&gt; &lt;span class="function-name"&gt;initialize&lt;/span&gt;(items, offset = 0, count = &lt;span class="variable-name"&gt;nil&lt;/span&gt;)&lt;br /&gt;    &lt;span class="variable-name"&gt;@items&lt;/span&gt; = items&lt;br /&gt;    &lt;span class="variable-name"&gt;@offset&lt;/span&gt; = offset&lt;br /&gt;    &lt;span class="variable-name"&gt;@count&lt;/span&gt; = count || &lt;span class="variable-name"&gt;@items&lt;/span&gt;.size&lt;br /&gt;  &lt;span class="keyword"&gt;end&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class="comment"&gt;# For each item of the same level in nested set as the first item&lt;br /&gt;&lt;/span&gt;  &lt;span class="comment"&gt;# calls block passing corresponding item.&lt;br /&gt;&lt;/span&gt;  &lt;span class="keyword"&gt;def&lt;/span&gt; &lt;span class="function-name"&gt;each&lt;/span&gt;&lt;br /&gt;    i = &lt;span class="variable-name"&gt;@offset&lt;/span&gt;&lt;br /&gt;    bound = &lt;span class="variable-name"&gt;@offset&lt;/span&gt; + &lt;span class="variable-name"&gt;@count&lt;/span&gt;&lt;br /&gt;    bound = &lt;span class="variable-name"&gt;@items&lt;/span&gt;.size &lt;span class="keyword"&gt;if&lt;/span&gt; bound &amp;gt; &lt;span class="variable-name"&gt;@items&lt;/span&gt;.size&lt;br /&gt;&lt;br /&gt;    &lt;span class="keyword"&gt;while&lt;/span&gt; i&amp;lt;bound&lt;br /&gt;      item = &lt;span class="variable-name"&gt;@items&lt;/span&gt;[i]&lt;br /&gt;      descendants_count = (item.rgt-item.lft)/2&lt;br /&gt;      &lt;span class="keyword"&gt;yield&lt;/span&gt; item&lt;br /&gt;      i += 1 + descendants_count&lt;br /&gt;    &lt;span class="keyword"&gt;end&lt;/span&gt;&lt;br /&gt;  &lt;span class="keyword"&gt;end&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class="comment"&gt;# For each item of the same level in nested set as the first item&lt;br /&gt;&lt;/span&gt;  &lt;span class="comment"&gt;# calls block passing corresponding item and collection, that&lt;br /&gt;&lt;/span&gt;  &lt;span class="comment"&gt;# represent all immediate children of that item. Collection is also&lt;br /&gt;&lt;/span&gt;  &lt;span class="comment"&gt;# an instance of this class.&lt;br /&gt;&lt;/span&gt;  &lt;span class="keyword"&gt;def&lt;/span&gt; &lt;span class="function-name"&gt;each_with_children&lt;/span&gt;&lt;br /&gt;    i = &lt;span class="variable-name"&gt;@offset&lt;/span&gt;&lt;br /&gt;    bound = &lt;span class="variable-name"&gt;@offset&lt;/span&gt; + &lt;span class="variable-name"&gt;@count&lt;/span&gt;&lt;br /&gt;    bound = &lt;span class="variable-name"&gt;@items&lt;/span&gt;.size &lt;span class="keyword"&gt;if&lt;/span&gt; bound &amp;gt; &lt;span class="variable-name"&gt;@items&lt;/span&gt;.size&lt;br /&gt;&lt;br /&gt;    &lt;span class="keyword"&gt;while&lt;/span&gt; i&amp;lt;bound&lt;br /&gt;      item = &lt;span class="variable-name"&gt;@items&lt;/span&gt;[i]&lt;br /&gt;      descendants_count = (item.rgt-item.lft)/2&lt;br /&gt;      &lt;span class="keyword"&gt;yield&lt;/span&gt; item, &lt;span class="type"&gt;NestedSetTreePresenter&lt;/span&gt;.new(&lt;span class="variable-name"&gt;@items&lt;/span&gt;, i+1, descendants_count)&lt;br /&gt;      i += 1 + descendants_count&lt;br /&gt;    &lt;span class="keyword"&gt;end&lt;/span&gt;&lt;br /&gt;  &lt;span class="keyword"&gt;end&lt;/span&gt;&lt;br /&gt;&lt;span class="keyword"&gt;end&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;I chose not to yield a proxy, container or anything else to minimize the number of objects created.&lt;br /&gt;&lt;br /&gt;Here is how to use it:&lt;br /&gt;&lt;pre&gt;&lt;code class="ruby"&gt;&lt;span class="keyword"&gt;def&lt;/span&gt; &lt;span class="function-name"&gt;print_tree&lt;/span&gt;(items, level = 0)&lt;br /&gt;  items.each_with_children &lt;span class="keyword"&gt;do&lt;/span&gt; |item, children|&lt;br /&gt;    puts &lt;span class="string"&gt;"  "&lt;/span&gt;*level + item.name&lt;br /&gt;    print_tree(children, level+1)&lt;br /&gt;  &lt;span class="keyword"&gt;end&lt;/span&gt;&lt;br /&gt;&lt;span class="keyword"&gt;end&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;print_tree(&lt;span class="type"&gt;NestedSetTreePresenter&lt;/span&gt;.new(items))&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-1229430394334738095?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/1229430394334738095/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=1229430394334738095' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/1229430394334738095'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/1229430394334738095'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2009/09/building-tree-out-of-nested-set.html' title='Building tree out of nested set'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-7812550090829578288</id><published>2009-08-25T10:11:00.002+04:00</published><updated>2009-08-25T10:17:35.203+04:00</updated><title type='text'>RSpec's matchers without all bullshit</title><content type='html'>I've used to use and like RSpec. The problem was that it always broke with new versions of Rails and internals were too complex to understand. Then appeared Shoulda and removed the pain of writing RUnit tests, so I switched to using it. But I always missed the elegance of RSpec's matchers.&lt;br /&gt;&lt;br /&gt;Until &lt;a href="http://github.com/jnunemaker/matchy/tree/master"&gt;Matchy&lt;/a&gt; appeared.&lt;br /&gt;&lt;br /&gt;The cool thing about it is that you can create custom matchers easily with #custom_matcher() method provided. Nice.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-7812550090829578288?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://github.com/jnunemaker/matchy/tree/master' title='RSpec&apos;s matchers without all bullshit'/><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/7812550090829578288/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=7812550090829578288' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/7812550090829578288'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/7812550090829578288'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2009/08/rspecs-matchers-without-all-bullshit.html' title='RSpec&apos;s matchers without all bullshit'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-577934624094897442</id><published>2009-07-22T01:09:00.005+04:00</published><updated>2009-07-22T11:46:04.353+04:00</updated><title type='text'>Allowing ssh access to Darcs repositories</title><content type='html'>I've been using Darcs since early 2006 and I do not see any reason to switch to any other (D)VCS like git.&lt;br /&gt;&lt;br /&gt;Today at work we decided to put our new project into Darcs and I needed to setup access for others to my repository. The readonly access is not a problem, but allowing others to push into my repositories was a bit trickier. There is a protocol wrapper called "darcs-server", but I don't like that solution. I wanted to configure Darcs to do get/pull/push with minimal additional components.&lt;br /&gt;&lt;br /&gt;The traditional way to do Push is via SSH. You can create a separate account for Darcs VCS or just share your own. Then, you set up access via public/private keys and here we go: you can do all operations. The only thing is that everybody can do anything with your account, which is bad. You need to restrict users to running just a small set of commands sufficient for normal Darcs operation. With Darcs 2 it turns out that you only need to allow running "darcs" with various arguments (as Darcs 2 uses special tunelling to do all work while Darcs 1 used to open tons of connections). After several experiments, here is what you need:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;#!/bin/sh&lt;br /&gt;&lt;br /&gt;first_word() { echo $1; }&lt;br /&gt;&lt;br /&gt;line="$SSH_ORIGINAL_COMMAND"&lt;br /&gt;&lt;br /&gt;command=$(first_word $line)&lt;br /&gt;if [ "$command" = "darcs" ]; then&lt;br /&gt;  sh -c "$line"&lt;br /&gt;else&lt;br /&gt;  echo "$command is not allowed"&lt;br /&gt;fi&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Then, you add "command='/home/username/bin/darcs-wrapper' " at the beginning of your collegues' public key lines in ~/.ssh/authorized_keys and boom - they are restricted to running only darcs. You can also add no-port-forwarding, no-X11-forwarding, no-agent-forwarding, no-pty to tighten security. And you can set environment variable DARCS_LOGNAME to name of particular user to get proper names in darcs changes logs: add "environment='DARCS_LOGNAME=username'" to your authorized_keys file.&lt;br /&gt;&lt;br /&gt;Hope that helps.&lt;br /&gt;&lt;br /&gt;UPDATE: after thinking a little, I found a case when this can be workaround to execute any command, e.g. "darcs help; rm -rf *". The problem is with "sh -c". Originally, it was introduced in place of call to "exec" to workaround problem with extra quotation marks that are not removed when parsing line and which cause Darcs to handle arguments improperly. It turns out, that "exec" is really needed there and unquoting should be done manually. Here is the corrected version:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;#!/bin/sh&lt;br /&gt;&lt;br /&gt;first_word() { echo $1; }&lt;br /&gt;unquoted_words() {&lt;br /&gt;  for word in $@; do&lt;br /&gt;    echo $(echo $word | sed -E "s/^'(.*)'$/\1/; s/^\"(.*)\"$/\1/")&lt;br /&gt;  done&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;line="$SSH_ORIGINAL_COMMAND"&lt;br /&gt;&lt;br /&gt;command=$(first_word $line)&lt;br /&gt;if [ "$command" = "darcs" ]; then&lt;br /&gt;  exec $(unquoted_words $line)&lt;br /&gt;else&lt;br /&gt;  echo "$command is not allowed"&lt;br /&gt;fi&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;UPDATE2: that "sed -E" thing seem to not work on Linux. Linux Sed's option for full regular expressions is called "-r". Probably need to try rewriting regexps to normal Sed's functionality. Later.&lt;br /&gt;&lt;br /&gt;UPDATE3: very handy thing in debugging Darcs' SSH is using "--debug --verbose" options to "darcs". Then, you can see what commands are sent to server and how response is interpreted. Then, you can issue those commands manually and see if server's wrapper script outputs errors.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-577934624094897442?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/577934624094897442/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=577934624094897442' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/577934624094897442'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/577934624094897442'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2009/07/allowing-ssh-access-to-darcs.html' title='Allowing ssh access to Darcs repositories'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-1099462431778752476</id><published>2009-01-10T22:02:00.003+03:00</published><updated>2009-01-10T22:57:02.797+03:00</updated><title type='text'>Specific or default templates in Rails</title><content type='html'>&lt;div style="text-align: justify;"&gt;I want to develop admin controller to do simple CRUD operations on not-so-complex models. I want it to be DRY and to allow adding features to all of them easily. This is my second attempt, the first one was way too ugly: it used inheritance from admin's base controller and descendants passed customization parameters to base class; also, have used custom "render" methods to find template for specific controller or, if the specific template is missing, fall back to general template.&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;And then it broke. Yeah, this could be fixed, but the whole idea seemed bad.&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;I just finished the second attempt and it looks promising.&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;First, it includes only one controller and uses routes to determine the actual model to operate on. Every route includes additional parameter that is passed to controller that contains name of model:&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;pre&gt;&lt;code&gt;map.resources :users, :controller =&gt; 'items',&lt;br /&gt;    :requirements =&gt; { :item_type =&gt; 'User' }&lt;br /&gt;map.resources :regions, :controller =&gt; 'items',&lt;br /&gt;    :requirements =&gt; { :item_type =&gt; 'Region' }&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;Second, the most troublesome was to make it work like this: I have default views in app/views/items... I wanted to have subdirectories with name of particular model that would contain overrides to default templates:&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;pre&gt;app/views/items&lt;br /&gt;    index.html.erb&lt;br /&gt;    new.html.erb&lt;br /&gt;    edit.html.erb&lt;br /&gt;    _item.html.erb&lt;br /&gt;    _form.html.erb&lt;br /&gt;app/views/items/users&lt;br /&gt;    _item.html.erb&lt;br /&gt;    _form.html.erb&lt;br /&gt;app/views/items/regions&lt;br /&gt;    _form.html.erb&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;Regions doesn't need custom 'item' partial because default one (which outputs only item.to_s) fits well.&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;Obviously, controller's view_paths should be manipulated to search first in concrete subfolders and then in default place. But the problem was that it always tried to prepend controller name and so find it in corresponding subfolder. It's not very nice being forced to create additional directories like this:&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;pre&gt;app/views/items&lt;br /&gt;app/views/items/users/items&lt;br /&gt;    _item.html.erb&lt;br /&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;Well, after some digging the way to do it elegantly I found this solution:&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;1. Redefine controller's controller_path to return nil. After this all templates are searched in app/views/ instead of app/views/items, so it needs to be fixed, so&lt;/div&gt;&lt;div style="text-align: justify;"&gt;2. Redefine controller's view_paths to base in app/views/items.&lt;/div&gt;&lt;div style="text-align: justify;"&gt;3. Based on current item type prepend corresponding view path.&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;The result is as follows:&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;pre&gt;&lt;code&gt;class ItemsController &amp;lt; ApplicationController&lt;br /&gt;  def self.controller_path&lt;br /&gt;    nil&lt;br /&gt;  end&lt;br /&gt;  self.view_paths = [ RAILS_ROOT + '/app/views/items' ]&lt;br /&gt;&lt;br /&gt;  before_filter :setup_model&lt;br /&gt;&lt;br /&gt;  # ... action code&lt;br /&gt;&lt;br /&gt;  private&lt;br /&gt;&lt;br /&gt;  def setup_model&lt;br /&gt;    @model  = params[:item_type].constantize&lt;br /&gt;    prepend_view_path(RAILS_ROOT + '/app/views/items/' + params[:item_type].underscore)&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Happy coding!&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-1099462431778752476?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/1099462431778752476/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=1099462431778752476' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/1099462431778752476'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/1099462431778752476'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2009/01/specific-or-default-templates-in-rails.html' title='Specific or default templates in Rails'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-1138065549545720883</id><published>2009-01-09T17:37:00.002+03:00</published><updated>2009-01-09T18:02:29.200+03:00</updated><title type='text'>Back to Rails</title><content type='html'>Lately I wasn't doing any Rails development because I was busy re-learning C/C++ "the right way". Turns out it's not that painful as they say.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Then I had to do some Rails coding using latest and greatest Rails 2.2 and I find that that the simple things are broken (or somewhat obscure).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Case 1.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I was developing some general resource controller and wanted to spec out a redirect after #create action. My controller should not be bound to any specific resource url (as it is planned to bind many different resources with that controller), so I decided to make use of Rails' url rewriting. Spec was like this:&lt;/div&gt;&lt;div&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;it "should redirect to #index after item creation" do&lt;/div&gt;&lt;div&gt;  @model.stubs(:create!)&lt;br /&gt;&lt;/div&gt;&lt;div&gt;  post :create&lt;/div&gt;&lt;div&gt;  response.should redirect_to(:action =&gt; :index)&lt;/div&gt;&lt;div&gt;end&lt;/div&gt;&lt;div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;And this spec failed because of &lt;span class="Apple-style-span" style="font-style: italic;"&gt;MethodNotAllowed: Only get and post requests are allowed&lt;/span&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;WTF? It's the very basic functionality. Why does it fail to work ?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;After half a day of investigation I found out that this is a bug in rspec-rails package and redirect_to matcher fails to handle hash-like url specifier.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Case 2.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I wanted to handle AR::RecordInvalid exceptions with one handler to DRY up controller code. I wrote corresponding "rescue_from" call in controller and wrote specs (in fact, the reverse order: specs then code). And those specs failed too.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The other several hours of investigation revealed hidden option: RSpec-rails overrides default Rails' error handling (which is based on ActiveSupport's #rescue_from feature) with pass-through exception handler, and you have to write following code to make YOUR exception handlers work:&lt;/div&gt;&lt;div&gt;&lt;pre&gt;&lt;code&gt;&lt;/div&gt;&lt;div&gt;before do&lt;/div&gt;&lt;div&gt;  controller.use_rails_error_handling!&lt;/div&gt;&lt;div&gt;end&lt;/div&gt;&lt;div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Yeah, Rails is nice, but upgrading to a new version is a pain.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-1138065549545720883?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/1138065549545720883/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=1138065549545720883' title='Комментарии: 2'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/1138065549545720883'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/1138065549545720883'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2009/01/back-to-rails.html' title='Back to Rails'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-2974475943849638126</id><published>2008-05-21T15:34:00.002+04:00</published><updated>2008-05-21T16:26:04.233+04:00</updated><title type='text'>Sphinx FAIL</title><content type='html'>&lt;div style="text-align: justify;"&gt;On my recent Rails project I decided to try Sphinx search engine. Before that I used Ferret and then - SOLR. I abandoned Ferret because it's instability and lack of tools to track problems (like Luke - index browser for Lucene).&lt;br /&gt;&lt;br /&gt;So, Sphinx. I have found two plugins for Rails - acts_as_sphinx and Unltrasphinx. Ok, I've heard in mailing lists that Ultrasphinx is better (and Sphinx recipe in Advanced Rails Recipes book also involves it) so I decided to use it.&lt;br /&gt;&lt;br /&gt;First I have to install Sphinx from sources because version in MacPorts (although, latest released version) is too old and Ultrasphinx requiresa newer one (at that point - release candidate 2 of next revision, which is 0.9.8rc2 vs 0.9.7). Then I had  to do various dances around Sphinx to compile it on my Mac OS X  (which are described in my &lt;a href="http://maximkulkin.blogspot.com/2008/05/installing-sphinx-on-mac-os-leopard.html"&gt;previous post&lt;/a&gt;.).&lt;br /&gt;&lt;br /&gt;And then it began:&lt;br /&gt;&lt;br /&gt;1. It was needed that new data (e.g. new articles) could be searched with Sphinx right after it was added. And then I find out, that it is not encouraged to do such updates often, you'd better run full updates once a day... Wtf? I have heard that there are something called "deltas", but as I found out from plugin, it doesn't install any hooks on models, so I assume that deltas need to be built periodically (which is also unacceptable).&lt;br /&gt;After consulting with other people who have used Sphinx I found out that:&lt;br /&gt;1) they don't use plugins and do all communication with some low level library (Riddle, as far as I remember) manually.&lt;br /&gt;2) they install their own hooks on models and call indexer manually to reindex their models. I have tried to to install after_save hook but it is run BEFORE transaction is commited and indexer can't see inserted/updated data, so I don't know how this can be accomplished easily.&lt;br /&gt;&lt;br /&gt;2. Sphinx configuration is one big scary thing. Although Ultrasphinx managed to build it for me, I needed to do some tweaks to it and then next time I needed to build configuration for another model my tweaks were lost.&lt;br /&gt;&lt;br /&gt;3. Model on which I have used Ultrasphinx (called ::is_indexed) failed to load by automatic dependency loader. After several hours of tracking this problem I stuck "require 'my_model'" into environment.rb (which helped) and cursed that Sphinx and all it's plugins.&lt;br /&gt;&lt;br /&gt;So, for me Sphinx definitely FAIL.&lt;br /&gt;&lt;br /&gt;PS&lt;br /&gt;I have tried SOLR and it worked like charm:&lt;br /&gt;1) almost no configuration.&lt;br /&gt;SOLR configuration consists of type definitions (which describe how value of that type should be analyzed), field definitions and dynamic field definitions (acts_as_solr rails plugin uses only dynamic fields). Acts_as_solr comes with default SOLR configuration which contains commonly used types and dynamic field definitions for them.&lt;br /&gt;2) no compilation needed. Acts_as_solr comes with SOLR JAR files, so you just need proper version of Java runtime installed.&lt;br /&gt;3) works like charm. Everything you would expect from full-text search engine.&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-2974475943849638126?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/2974475943849638126/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=2974475943849638126' title='Комментарии: 5'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/2974475943849638126'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/2974475943849638126'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2008/05/sphinx-fail.html' title='Sphinx FAIL'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-5623170900065597682</id><published>2008-05-15T00:57:00.003+04:00</published><updated>2008-05-15T01:13:45.558+04:00</updated><title type='text'>Installing Sphinx on Mac OS Leopard</title><content type='html'>&lt;div style="text-align: justify;"&gt;One of my ongoing projects required using Sphinx search engine. While I'm more comfortable with Lucene based solutions (I'm evaluating SOLR currently), I decided to give Sphinx a try.&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;I have installed Sphinx via MacPorts system and was going to use UltraSphinx Rails plugin. But when I tried to run some test query, it complained, that server version is older than the client version. So, I needed to update Sphinx as UltraSphinx depends on some development release of Sphinx. But when I downloaded sources, configured and issued "make" command, the build failed because there were link problems with iconv library:&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;pre&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;Undefined symbols:&lt;/div&gt;&lt;div style="text-align: justify;"&gt;  "_iconv_close", referenced from:&lt;/div&gt;&lt;div style="text-align: justify;"&gt;      xmlUnknownEncoding(void*, char const*, XML_Encoding*)in libsphinx.a(sphinx.o)&lt;/div&gt;&lt;div style="text-align: justify;"&gt;  "_iconv", referenced from:&lt;/div&gt;&lt;div style="text-align: justify;"&gt;      xmlUnknownEncoding(void*, char const*, XML_Encoding*)in libsphinx.a(sphinx.o)&lt;/div&gt;&lt;div style="text-align: justify;"&gt;  "_iconv_open", referenced from:&lt;/div&gt;&lt;div style="text-align: justify;"&gt;      xmlUnknownEncoding(void*, char const*, XML_Encoding*)in libsphinx.a(sphinx.o)&lt;/div&gt;&lt;div style="text-align: justify;"&gt;ld: symbol(s) not found&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;I saw some &lt;a href="http://www.viget.com/extend/installing-sphinx-on-os-x-leopard/"&gt;posts&lt;/a&gt; on how to overcome and they suggest downloading and installing iconv library. But I was strongly against doing such things (and also iconv library installed on my system was the latest one), so after some trial-and-error I have figured how to build it: just give standard system's library path precedence:&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;pre&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;export LDFLAGS="-L/usr/lib"&lt;/div&gt;&lt;div style="text-align: justify;"&gt;./configure&lt;/div&gt;&lt;div style="text-align: justify;"&gt;make&lt;/div&gt;&lt;div style="text-align: justify;"&gt;sudo make install&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;Hope this will help somebody.&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-5623170900065597682?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/5623170900065597682/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=5623170900065597682' title='Комментарии: 4'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/5623170900065597682'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/5623170900065597682'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2008/05/installing-sphinx-on-mac-os-leopard.html' title='Installing Sphinx on Mac OS Leopard'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-5994756821800846431</id><published>2008-04-30T16:47:00.002+04:00</published><updated>2008-04-30T17:24:49.418+04:00</updated><title type='text'>On Prototype vs jQuery</title><content type='html'>&lt;div style="text-align: justify"&gt;&lt;br /&gt;I see more and more posts like &lt;a href="http://blog.inquirylabs.com/2008/04/29/what-makes-jquery-a-good-choice/"&gt;this&lt;/a&gt; or &lt;a href="http://jquery.com/blog/2006/08/20/why-jquerys-philosophy-is-better/"&gt;this&lt;/a&gt; recently and I always see that when people are telling about how poor the Prototype framework is they really don't know even half of it's possibilities.&lt;br /&gt;&lt;br /&gt;As it always happens, the code comparison in those posts is written by person who is not experienced in one framework or another.&lt;br /&gt;&lt;br /&gt;I agree, that jQuery has some nice features: 1) actions on groups of objects (that you have mentioned), 2) more powerful selectors: prototype has support for css3 selectors only, but jQuery can handle more sophisticated queries like selecting hidden elements or inputs of particular type.&lt;br /&gt;&lt;br /&gt;I haven't dug into what it takes to write custom effect or plugin (plan to do it soon), but I don't see how you can base your effects on existing ones in a way other than aggregating/compositing (the thing with Prototype effects is that you can inherit your effect from some base effect and have some nice features like configurable 'easing' function, which can control effect speed over time, or some other stuff for free).&lt;br /&gt;&lt;br /&gt;But seeing syntax comparisons like that just makes me mad.&lt;br /&gt;First, adding a class name to element in Prototype is just as easy as in jQuery:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;  $('element').addClassName('className')&lt;br /&gt;  $('element').removeClassName('classsName')&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Second, the Ajax syntax. Prototype's variant is designed to do POST requests by default. You don't need to specify 'method' parameter if you do POST request. As for GET requests, in Prototype you can do just as in jQuery:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;  new Ajax.Updater('element', url + '&amp;amp;' + parameters)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;(I believe, the '&amp;amp;' thing is needed in jQuery also, but author of code comparison just 'forgot' to mention that)&lt;br /&gt;&lt;br /&gt;Moreover, if you don't like the wordy syntax of Ajax.Updater, you can extend Element to support whatever syntax you like:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;  Element.addMethods({&lt;br /&gt;    load: function(element, url) {&lt;br /&gt;      new Ajax.Updater(element, url)&lt;br /&gt;    }&lt;br /&gt;  })&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;and then you can do&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;  $('element').load(url + params)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;just as in jQuery.&lt;br /&gt;&lt;br /&gt;And, yes, Prototype doesn't handle working with sets of elements by default. To add a particular class to a set of elements in Prototype you need to do some extra stuff, but not as it is mentioned in code comparison:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;  $$('.element').invoke('addClassName', 'className')&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And yes, I agree, that Prototype and Scriptaculous have bad documentation and I personally have bought a book on them (see Pragmatic Programmers bookshelf) and my problems with Prototype and Scripaculous have gone since then.&lt;br /&gt;&lt;br /&gt;I don't see any big reason to switch to jQuery.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;PS There are lots of other javascript frameworks like mooTools (if I spell it correctly), Dojo, Rhino and such and I always haven't paid much attention to those, because usually my web application require custom solutions, not those that are present in such frameworks. But recently I came across some book ("Mastering Dojo" published by Pragmatic Programmers) and a podcast on it and it got my attention. They say that lot's of fortune 500 companies are using their framework because it is very solid and stable. And I'm curious about what particular features of Dojo make them think so. I have visited Dojo's site and tried to see at some of the examples (e.g. fish eye component) and they were running way too slow on my latest Firefox 2.x (which I consider as rather modern web browser). Probably, I need to give it one more chance and, probably, read that book.&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-5994756821800846431?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/5994756821800846431/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=5994756821800846431' title='Комментарии: 6'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/5994756821800846431'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/5994756821800846431'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2008/04/on-prototype-vs-jquery.html' title='On Prototype vs jQuery'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-7759459583103521332</id><published>2007-12-11T11:36:00.000+03:00</published><updated>2007-12-11T12:05:53.608+03:00</updated><title type='text'>ActiveRecord's database shortcut methods</title><content type='html'>&lt;div style="text-align: justify;"&gt;I see more and more people nowadays complain about other people using destroy_all to delete a bunch of records without understanding the performance decrease compared to using delete_all.&lt;br /&gt;&lt;br /&gt;Well, I'll tell you what: in general they do the right thing. My opinion is that having such shortcut methods like delete_all (or update_attribute that issues an update command without triggering validation/callbacks) are evil. While ActiveRecord tries to provide a foundation for building your domain model (smart model, not just containers for data) those methods allow to mess everything up. E.g. you have a model, and when you decide to provide a means to delete all objects, you use delete_all because of better performance than destroy_all. Later on you decide to add some on_destroy callbacks, and then you find out that you need to change every call to delete_all to destroy_all.&lt;br /&gt;&lt;br /&gt;Calling delete_all just breaks an invariant for your objects.&lt;br /&gt;&lt;br /&gt;What it would be nice is to optimize destroy_all to just call delete_all if nothing else is required. The main reason to call destroy_all is the on_destroy callback. So, ActiveRecord could check if there is any callbacks and if there is no callbacks, then just call delete_all operation.&lt;br /&gt;&lt;br /&gt;The same could be done for updates (and it was discussed for a long time): update only those fields that have changed. Then, instead of calling "special" method to update just one field, you will just update the required field and call regular #save. AR will detect all changed fields and issue an update command for just those fields.&lt;br /&gt;&lt;br /&gt;How this can be implemented ? The ideal solution is to keep a set of "unmodified" data and compare object's data to that "unmodified" set on save. But on big objects it could require two times more memory. Instead, it could be easier to track what fields were assigned values to and set "changed" flag for that field. If the flag wasn't yet set and the assigned value differs from the one that it is now, then set the flag. If the flag was already set, then don't update it. Of course, it will sometimes update the field to the same value if you first set it to some other value and then set the value that was before. But I believe that such cases are rare and it is not a problem if we update it with the same value.&lt;br /&gt;Storing a boolean flag per database column is not an issue also.&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-7759459583103521332?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/7759459583103521332/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=7759459583103521332' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/7759459583103521332'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/7759459583103521332'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2007/12/activerecords-database-shortcut-methods.html' title='ActiveRecord&apos;s database shortcut methods'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-7071550998700070632</id><published>2007-11-06T09:36:00.000+03:00</published><updated>2007-11-06T09:36:19.891+03:00</updated><title type='text'>Giles Bowkett: IRB: What Was That Method Again?</title><content type='html'>&lt;a href="http://gilesbowkett.blogspot.com/2007/11/irb-what-was-that-method-again.html"&gt;Giles Bowkett: IRB: What Was That Method Again?&lt;br /&gt;&lt;/a&gt;why should someone bother adding just another method to do stuff that is easily done with existing methods ?&lt;br /&gt;&lt;code&gt;"any arbitrary string".methods.grep("ch")&lt;/code&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-7071550998700070632?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://gilesbowkett.blogspot.com/2007/11/irb-what-was-that-method-again.html' title='Giles Bowkett: IRB: What Was That Method Again?'/><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/7071550998700070632/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=7071550998700070632' title='Комментарии: 2'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/7071550998700070632'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/7071550998700070632'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2007/11/giles-bowkett-irb-what-was-that-method.html' title='Giles Bowkett: IRB: What Was That Method Again?'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-8591595736258676110</id><published>2007-11-06T09:04:00.000+03:00</published><updated>2007-11-06T09:29:35.535+03:00</updated><title type='text'>On Auto Migrations</title><content type='html'>&lt;div style="text-align: justify;"&gt;There is an &lt;a href="http://errtheblog.com/post/12447"&gt;auto_migrations plugin&lt;/a&gt; that people nowadays start using more and more often. This plugin allows to maintain only one file with database schema definitions and it changes DB structure to match that definition automagically.&lt;br /&gt;&lt;br /&gt;Giles Bowkett &lt;a href="http://gilesbowkett.blogspot.com/2007/11/always-use-automatic-migrations.html"&gt;blogged&lt;/a&gt; about it recently and about problems with regular migrations. But wait a minute, I've seen (and experienced) only one type of situations when you can't migrate from zero up to latest migration: it's situation when your model has changed through version and e.g. some method vital for that migration was removed or some new validation was added and you try to create objects in migration that have that field set to an invalid value.&lt;br /&gt;&lt;br /&gt;How can changing DB structure make migrations invalid ?&lt;br /&gt;&lt;br /&gt;And having that in mind, how that &lt;a href="http://errtheblog.com/post/12447"&gt;auto_migrations&lt;/a&gt; thing can help ? Also, how will you actually migrate your data with auto_migrations ?&lt;br /&gt;&lt;br /&gt;PS btw, the way to fix broken migrations in situation described above is to use schema dump that rails creates anyways in db/ directory: when you create from scratch, there could be no data to migrate, so you could just create the DB structure. But if there are migrations to e.g. create an admin user,  then you could need to run actual migrations. And the only way I see (when they fail half way) is to use version control system to update to each revision where new migration was added (so that source code match the migration), do the migration, then update to next version where new migration was introduced, do the migration and so forth.&lt;br /&gt;Probably, this could be automated. Assuming it is needed only when new member joins the development team, it is a rear operation so the performance is not critical.&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-8591595736258676110?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/8591595736258676110/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=8591595736258676110' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/8591595736258676110'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/8591595736258676110'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2007/11/on-auto-migrations.html' title='On Auto Migrations'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-4503188675602250866</id><published>2007-07-05T00:52:00.000+04:00</published><updated>2007-07-05T02:02:34.748+04:00</updated><title type='text'>Завершение саги о датах</title><content type='html'>&lt;div style="text-align: justify;"&gt;&lt;br /&gt;Никак не мог успокоиться: все решения, которые я находил, не содержали решения для выдачи ошибок, если дату невозможно распарсить. Чаще всего, если попытка перевода строки в дату не была успешной, вместо даты - nil и никаких тебе ошибок.&lt;br /&gt;&lt;br /&gt;Наконец мне удалось сложить все кусочки воедино и получить достаточно неплохое, на мой взгляд, решение.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;1. Как редактировать.&lt;/span&gt;&lt;br /&gt;Как уже обсуждалось в моих предыдущих постах, самое правильное при редактировании даты - отказаться от трех списков и оставить только одно текстовое поле, куда можно вводить строковое представление даты в определенном формате. Плюс можно прикрутить жаваскриптовый календарик к этому текстовому полю.&lt;br /&gt;Как же высветить введенное значение, если распарсить строку не удалось ? Вспоминаем, что для этого есть *_before_type_cast атрибуты. Напомню: когда мы присваиваем значения конкретному атрибуту, это значение сохраняется внутри экземпляра модели нетронутым (про многопараметровые значения мы не говорим). Конвертация же происходит, когда мы пытаемся получить значение атрибута экзепляра класса модели, а *_before_type_cast возвращает значение, обходя конвертацию. Поэтому, надо только сделать date_select хэлпер, который будет пытаться сконвертировать значение атрибута (сконвертированное, т.е. типа Date) в строку, если это значение не nil, иначе - выкладывать строковое представление значение *_before_type_cast. Плюс код для яваскриптового календарика (например, &lt;a href="http://www.dynarch.com/projects/calendar/"&gt;этого&lt;/a&gt;).&lt;br /&gt;Отлично, теперь можно вводить даты в стандартных форматах (например, YYYY-MM-DD) и не терять введенное значение, если преобразовать его в дату не удастся.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;2. Нужные форматы даты.&lt;/span&gt;&lt;br /&gt;В моем приложении просто необходимо, чтобы дату можно было вводить в русском формате (т.е. DD.MM.YYYY). Поэтому, надо как-то научить дату понимать такой формат.&lt;br /&gt;Поизучав исходники, я пришел к тому, что конвертация из строки в дату производится экземпляром объекта, который представляет соответствующий столбец базы данных. По реализации становится ясно, что конвертация осуществляется методом ParseDate#parsedate стандартной библиотеки Ruby. Недолго думая, расширяем этот метод, чтобы он понимал нужный нам формат даты:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;ParseDate.class_eval do&lt;br /&gt; class &amp;lt;&amp;lt; self&lt;br /&gt;   def parsedate_with_ru_format(str, comp=false)&lt;br /&gt;     str = str.to_s&lt;br /&gt;     str = "#{$3}-#{$2}-#{$1}" if /(&lt;/code&gt;&lt;code&gt;\d{2}&lt;/code&gt;&lt;code&gt;)\.(&lt;/code&gt;&lt;code&gt;\d{2}&lt;/code&gt;&lt;code&gt;)\.(\d{4})/ =~ str&lt;br /&gt;     parsedate_without_ru_format(str, comp)&lt;br /&gt;   end&lt;br /&gt;&lt;br /&gt;   alias_method_chain :parsedate, :ru_format&lt;br /&gt; end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Подключаем этот код в config/environment.rb и вуаля: Rails понимает нужный нам формат даты.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;3. Ошибка при неправильном формате даты.&lt;/span&gt;&lt;br /&gt;Теперь формат понимаем, неправильное значение никуда не девается, осталось только понять, что формат даты неверный. В текущем состоянии при ошибке конвертации вместо даты имеем nil. Это может быть немного confusing для пользователя, если он думал, что он заполнил (необязательное) поле с датой и сохранил его, а потом выяснит, что в базе вместо значения получился nil.&lt;br /&gt;При этом, не хотелось бы добавлять никаких явных валидаций (типа, validates_date_format :foo и т.п.). Итак: прикручиваем к ActiveRecord::Base валидацию по умолчанию, которая перебирает все столбцы модели и для всех столбцов типа :date проверяет, что если сконвертированное значение == nil, а значение *_before_type_cast не пустое (!foo_before_type_cast.empty?), то добавляет ошибку на это поле.&lt;br /&gt;&lt;pre&gt;&lt;code&gt;ActiveRecord::Base.class_eval do&lt;br /&gt; class &amp;lt;&amp;lt; self&lt;br /&gt;   def date_columns&lt;br /&gt;     columns.select { |column| column.type == :date }&lt;br /&gt;   end&lt;br /&gt; end&lt;br /&gt;&lt;br /&gt; def validate_dates_format(record)&lt;br /&gt;   record.class.date_columns.each do |column|&lt;br /&gt;     if record.send(column.name).nil? &amp;&amp;amp;&lt;br /&gt;        record.send("#{column.name}_before_type_cast").empty?&lt;br /&gt;       record.errors.add(column.name, "has invalid date format")&lt;br /&gt;     end&lt;br /&gt;   end&lt;br /&gt; end&lt;br /&gt;&lt;br /&gt; validate :validate_dates_format&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Данное решение избавляет еще и от проблем с невозможностью легко выставить дату для атрибута, который защищен от массового присвоения (посредством, например, &lt;i&gt;attr_protected&lt;/i&gt;).&lt;br /&gt;&lt;br /&gt;Надеюсь, описанный выше способ пригодится кому-нибудь (лично меня он полностью устраивает) или послужит источником знаний/вдохновения для написания "следующего самого клевого плагина для Rails".&lt;br /&gt;&lt;br /&gt;PS Конечно, расширение стандартной библиотеки для поддержки своих форматов строк не самое изящное регение и черевато проблемами, если вдруг разработчики решат использовать какой-либо другой метод конвертации строк в даты. Но я считаю, что на данный момент этого решения вполне достаточно.&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-4503188675602250866?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/4503188675602250866/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=4503188675602250866' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/4503188675602250866'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/4503188675602250866'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2007/07/blog-post.html' title='Завершение саги о датах'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-3992035826522402341</id><published>2007-06-27T23:05:00.000+04:00</published><updated>2007-06-27T23:47:54.009+04:00</updated><title type='text'></title><content type='html'>&lt;div style="text-align: justify;"&gt;&lt;br /&gt;После долгих раздумий, я пришел к мнению, что многопараметровые значения все-таки зло:&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;Пользоваться ими без ActiveRecord нельзя. Представьте вариант, когда у Вас есть модель (назовем ее Task) с кучей полей + поле с датой, которое вы хотите заполнять только в опрделенном случае (скажем, при завершени Task'а), соответственно, не хотите, чтобы пользователь мог его менять самостоятельно (назовем это поле closed_at). Для этого поле делается защищенным от массового присвоения (&lt;code&gt;attr_protected :closed_at&lt;/code&gt;). Теперь у вас есть действие контроллера, которое   закрывает Task. Там надо поменять значения состояния Task'а и выставить дату. Если у Вас дата - мультипараметр (представлена тремя списками), то Вам надо превратить этот мультипараметр в дату.&lt;br /&gt;Сборка мультипараметров предусмотрена только через метод &lt;code&gt;attributes=&lt;/code&gt;, который в то же время делает проверку на атрибуты, защищенные от массового присвоения (т.е. в нашем случае это не будет работать). Придется собирать вручную (или прибегать к черной магии, способ описывать не буду).&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Ошибки сборки мультипараметров надо обрабатывать отдельно, нет способа показать неправильное значение, если ошибка произошла.&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;Я для себя решил не пользоваться мультипараметрами, а собирать единое значение на клиенте скриптом, а потом обрабатывать (парсить) его на сервере. С одной стороны, это плохо, т.к. требует поддержки и активации яваскрипта в браузере. Есть альтернатива: иметь текстовое поле, в котором можно будет ввести дату в определенном формате + кнопочку для активации яваскриптового календаря для наглядного выбора даты (который в свою очередь вставит нужную дату в текстовое поле). Таким образом все будут довольны. А на сервере преобразовать дату будет так же просто: надо будет всего лишь сделать &lt;code&gt;Date.strptime(params[:date_value], date_format)&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;Осталось только выбрать подходящий плагин и жаваскриптовую библиотеку для скриптового календаря.&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-3992035826522402341?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/3992035826522402341/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=3992035826522402341' title='Комментарии: 2'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/3992035826522402341'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/3992035826522402341'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2007/06/activerecord.html' title=''/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-456164869467673243</id><published>2007-06-26T09:51:00.000+04:00</published><updated>2007-06-26T10:13:59.136+04:00</updated><title type='text'>Валидация многопараметровых значений</title><content type='html'>Работал над тем, чтобы добавить нормальную обработку неправильных значений для дат. У меня одно из требований заказчика - чтобы даты можно было вводить и изменять с клавиатуры. При этом вводить день и особенно год проще текстом, нежели выбирать их из списка. Поэтому элементы для редактирования дат у меня выглядят как текстбокс для дня, список для месяца и текстбокс для года.&lt;br /&gt;&lt;br /&gt;Далее, хочется, чтобы пустые значения в любом из тех текст боксов отрабатывались правильно. Как известно, внутри рельсов даты являются многопараметровым (multiparameter) значением (т.е. оно складывается из значений нескольких элементов на форме), и логика преобразования типов там простая: позвать to_i для каждого значения и скормить получившиеся значения в Date.new... При этом (!) там стоит логика, что пустые значения не обрабатываются. Из за чего, если, например, у вас на форме не заполнен год (а он должен идти первым аргументом в конструкторе Date), то в результате его значение обработано не будет и на вход Date.new будет передано не 3, а 2 значения, что означает, что номер месяца (который идет вслед за годом в аргументах Date.new) будет расценен как номер года, а номер дня - как номер месяца... Такая же проблема, насколько я понимаю, происходит и со стандартным date_select (тот, который состоит из трех списков), если включен &lt;span style="font-style:italic;"&gt;:include_blank&lt;/span&gt;...&lt;br /&gt;&lt;br /&gt;Я считаю, такое поведение неоправданным, поэтому я сделал запил N1 - не выбрасывать пустые атрбуты (см код ниже, метод extract_callstack_for_multiparameter_attributes).&lt;br /&gt;&lt;br /&gt;Зачем же вообще код в ActiveRecord был написан так, чтобы выбрасывать пустые значения ? Сделано это, по видимому, было для того, чтобы обрабатывать пустые значения (как видно из&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;  if values.empty?&lt;br /&gt;    send(name + "=", nil)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;в execute_callstack_for_multiparameter_attributes). Вот это очень важно, т.к. не хотелось бы в последствие иметь ошибки на пустых и не обязательных к заполнению датах. Поэтому, (!) запил N2 - в конце обработки параметров проверить, если для любого из атрибутов заготовлен только массив из nil'ов, то заменить этот массив на пустой.&lt;br /&gt;&lt;br /&gt;Таким образом, если у вас любой из трех компонент даты окажется пустым, то он так и будет занимать свое законное место в списке аргументов, но будучи сконвертированным в нужный тип данных (в нашем случае - целое число). Конвертация осуществляется вызовом метода, начинающегося с "to_" и продолжающегося одной буквой соответствующего типа (например, "i" - "to_i").. to_i, как известно, пустые строки превращает в 0... Значит, если у вас дата будет пустая, то это превратится в Date.new(0, 0, 0), что не очень хорошо, т.к. если 0 в качестве параметра для года и допускается, то передача 0 для месяца или дня порождает исключение (которое потом порождает "ошибку присвоение атрибута с несколькими параметрами" - MultiparameterAssignmentError)... Хотелось бы и с этим забороться, а также не трактовать пустое поле год как нулевой год.&lt;br /&gt;&lt;br /&gt;И тут мне пришел в голову самый аццкий запил (N3):&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;class String&lt;br /&gt;  def to_n&lt;br /&gt;    Integer(self) rescue nil&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Если кто не знает, Integer(string) преобразует строковое представление числа в, собссно, число, но кидает исключение, если строка хоть немного не число (содержит посторонние символы, пустая и т.п.)&lt;br /&gt;&lt;br /&gt;Далее, модифицируем хелпер date_select (который мы и так уже модифицировали, см. требования заказчика в начале) так, чтобы к названию полей он добавлял не (1i) / (2i) / (3i), а (1n) / (2n) и (3n). Тогда ActiveRecord будет пытаться преобразовать значения этих полей, используя наш метод String#to_n.&lt;br /&gt;&lt;br /&gt;Отлично! Теперь у нас вылетает MultiparameterAssignmentError, если данные дата не является корректными. Теперь осталось только обработать эту ошибку. Вообще, мне не понятно, почему такие ошибки не включаются в список ошибок валидации, а выбрасываются отдельным исключением. Некоторые люди даже научились бороться с этим разными извращенными способами (например, &lt;a href="http://i.nfectio.us/articles/2006/02/22/handling-invalid-dates-with-activerecord-date-helpers"&gt;так&lt;/a&gt;)&lt;br /&gt;&lt;br /&gt;Я же хотел, чтобы мне ничего для этого менять не нужно было (ни ловить исключение, ни доставать список ошибок из каких-то дополнительных мест). Поэтому: 1) исключение надо ловить внутри, 2) ошибки аккуратно складывать все в тот же errors. Я уже было начал делать очередной запил =), но наткнулся на этот замечательный &lt;a href="http://agilewebdevelopment.com/plugins/validates_multiparameter_assignments"&gt;плагин&lt;/a&gt; .. Добрые люди уже все сделали именно так, как я хотел.&lt;br /&gt;&lt;br /&gt;Еще бы мои запилы оформить в виде плагина, а лучше (если нет каких-то концептуальных препятствий) - закомитить в само ядро.&lt;br /&gt;&lt;br /&gt;Надеюсь, вам, люди, эта информация пригодится.&lt;br /&gt;&lt;br /&gt;Ну и, собссно, мои запилы:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;  def extract_callstack_for_multiparameter_attributes(pairs)&lt;br /&gt;    attributes = { }&lt;br /&gt;&lt;br /&gt;    for pair in pairs&lt;br /&gt;      multiparameter_name, value = pair&lt;br /&gt;      attribute_name = multiparameter_name.split("(").first&lt;br /&gt;      attributes[attribute_name] = [] unless attributes.include?(attribute_name)&lt;br /&gt;&lt;br /&gt;      # запил N1: не пропускать пустые значения&lt;br /&gt;      #unless value.empty?&lt;br /&gt;        attributes[attribute_name] &lt;&lt;&lt;br /&gt;          [ find_parameter_position(multiparameter_name), type_cast_attribute_value(multiparameter_name, value) ]&lt;br /&gt;      #end&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;    # запил N2: заменять массив из только nil'ов на пустой&lt;br /&gt;    attributes.each do |name, values|&lt;br /&gt;      attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last }&lt;br /&gt;      attributes[name] = [] unless attributes[name].detect { |x| !x.nil? }&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-456164869467673243?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/456164869467673243/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=456164869467673243' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/456164869467673243'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/456164869467673243'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2007/06/blog-post.html' title='Валидация многопараметровых значений'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-7875859042891866725</id><published>2006-12-22T08:55:00.000+03:00</published><updated>2006-12-22T09:06:30.294+03:00</updated><title type='text'>Что если нет никаких богов ?</title><content type='html'>Очень интересный пост по поводу использования разных языков программирования в IT индустрии. Очень рекомендую:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://gilesbowkett.blogspot.com/2006/12/what-if-there-are-no-gods.html"&gt;What if there are no gods ?&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-7875859042891866725?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://gilesbowkett.blogspot.com/2006/12/what-if-there-are-no-gods.html' title='Что если нет никаких богов ?'/><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/7875859042891866725/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=7875859042891866725' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/7875859042891866725'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/7875859042891866725'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2006/12/blog-post.html' title='Что если нет никаких богов ?'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-1337823912310239446</id><published>2006-11-08T17:59:00.002+03:00</published><updated>2009-12-17T12:35:41.507+03:00</updated><title type='text'>Работа с данными в ActiveRecord</title><content type='html'>Недавно я продолжил изучение внутренностей ActiveRecord, потому как для меня до сих пор оставалось загадкой, как же правильно работать с данными в ActiveRecord (например, осуществлять конвертацию).&lt;br /&gt;&lt;br /&gt;Начнем с сердца экземпляра ActiveRecord - переменной @attributes. Это то самое хранилище, где хранятся все данные экземпляра AR. По сути, это хэш "атрибут =&gt; значение" и именно его (и ничего больше) инициализирует класс, вынимая запись из базы данных.&lt;br /&gt;&lt;br /&gt;Но изменять значения напрямую - не самая хорошая идея. Для доступа к значениям есть методы read_attribute(name) / write_attribute(name, value). Помимо вынимания/засовывания значения из/в @attributes, эти методы делают некоторые преобразования:&lt;br /&gt;&lt;br /&gt;- read_attribute осуществляет приведение типа к типу, соответствующему типу столбца БД (так, например, данные sql типа данных date превращаются в экземпляры класса Date). Также этот метод осуществляет десериализацию данных из YAML (помните еще о такой возможности ? =)). Если атрибуту не соответствует столбец базы данных - значение возвращается как есть.&lt;br /&gt;&lt;br /&gt;- write_attribute проще: он преобразовывает boolean данные в числа, пустую строку - в nil для числовых столбцов.&lt;br /&gt;&lt;br /&gt;Для этой парочки есть укороченные варианты: [] / []=. Т.е. если вы переопределили аццессор для вашего атрибута, то читать / писать данные внутри аццессора надо посредством этих "индексных" методов.&lt;br /&gt;&lt;br /&gt;Далее, есть свойство attributes. При чтении, оно, в принципе, просто возвращает копии всех атрибутов (не пытайтесь менять значения того, что вернул attributes - сам экземпляр AR не изменится), прогнанных сквозь read_attribute (т.е. данные с правильными типами и десериализованные). Также этот метод поддерживает опции, позволяющие исключить некоторые атрибуты или оставить конкретные из них (опции :except и :only).&lt;br /&gt;Врайтер attributes= или же направляет значение соответствующему врайтеру атрибута (например, firstname=), если это обычный атрибут, или же собирает мультиатрибут (тот, элементы которыго, имеют одинаковое имя и суффикс в виле порякового номера (и опционально - типа данных) в круглых скобках).&lt;br /&gt;&lt;br /&gt;И теперь самое интересное: каждый атрибут поддерживает _before_type_cast ридер. Этот ридер возвращает значение соответствующего ключа @attributes напрямую, минуя преобразование данных как в read_attribute.&lt;br /&gt;&lt;br /&gt;Выводы:&lt;br /&gt;* нельзя получить значения частей мультиатрибута (потому что они не сохраняются в @attributes. В предыдущей статье я описывал один из способов сделать сборку / разборку данных посредством composed_of. Так вот в этом способе нельзя сохранить на форме введенные неправильные данные.&lt;br /&gt;* при обновлении атрибутов можно получить сырые обновленные данные с поправкой на то, что для числовых столбцов True/False будет преобразован в 1/0, пустая строка - в nil, посредством _before_type_cast аццессора.&lt;br /&gt;&lt;br /&gt;Теперь попробуем переделать предыдущий пример с временем в календаре: сделаем так, чтобы можно было редактировать время как текст HH:MM. Для этого:&lt;br /&gt;&lt;br /&gt;1. Сделаем отдельный аттрибут для текстового представления:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;class Event &lt; ActiveRecord::Base&lt;br /&gt;  def time_text&lt;br /&gt;    "%d:%02d" % [self.time/60, self.time%60]&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def time_text=(value)&lt;br /&gt;    self.time = begin&lt;br /&gt;      parts = value.split ':'&lt;br /&gt;      parts[0].to_i*60+parts.to_i&lt;br /&gt;    rescue&lt;br /&gt;      nil&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;Отлично! Теперь делаем на форме text_field :event, :time_text и оно уже отображается. Но он стирает текст, если в нем есть ошибки. Хотелось бы сохранять его.2. Надо сохранять несконвертированное значение:&lt;pre&gt;&lt;code&gt;&lt;br /&gt;class Event &lt; ActiveRecord::Base&lt;br /&gt;  def time_text&lt;br /&gt;    self[:time_text] || ("%d:%02d" % [self.time/60, self.time%60])&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def time_text=(value)&lt;br /&gt;    self[:time_text] = value&lt;br /&gt;    self.time = begin&lt;br /&gt;      parts = value.split ':'&lt;br /&gt;      parts[0].to_i*60+parts[1].to_i&lt;br /&gt;    rescue&lt;br /&gt;      nil&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;text_field один из немногих (если не единственный) хелперов, которые используют _before_type_cast аццессоры. Теперь, поскольку мы сохранили неконвертированное значение, он сможет вывести его, если возникли ошибки.Теперь надо подумать про валидацию:&lt;pre&gt;&lt;code&gt;&lt;br /&gt;class Event &lt; ActiveRecord::Base&lt;br /&gt;  validates_format_of :time_text, :with =&gt; /\d?\d\:\d\d/&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Ну вот, собственно, и все.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-1337823912310239446?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/1337823912310239446/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=1337823912310239446' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/1337823912310239446'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/1337823912310239446'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2006/11/activerecord.html' title='Работа с данными в ActiveRecord'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-6276882444501019975</id><published>2006-11-02T19:00:00.000+03:00</published><updated>2006-11-13T00:43:03.262+03:00</updated><title type='text'>Сборка / разборка данных</title><content type='html'>Недавно понадобился такой функционал:&lt;br /&gt;&lt;br /&gt;В базе в поле типа integer хранится время события (кол-во минут с начала суток).&lt;br /&gt;Надо организовать редактирование этих данных в привычном для пользователя виде (т.е. часы : минуты). Пришлось залазить в кишки рельсов.&lt;br /&gt;&lt;br /&gt;В HTML редактирование времени выглядит как 2 SELECT'а (часы и минуты). Проблема: собрать два этих параметра в одно поле (общее_количество_минут), чтобы потом сохранить его в базе.&lt;br /&gt;&lt;br /&gt;В рельсах есть поддержка механизма сборки одного поля из нескольких request-параметров. Она активируется, если поле имеет суффикс вида "(x)", где x - порядковый номер параметра. Если рельсы встречают такие параметры, они их собирают в отдельную кучку и начинают по ним создавать "правильный" тип данных. Для этого они сначала выводят тип (класс) будущего значения, и вызывают у него метод new с соответствующим (соответствующим кол-во параметров в мультипараметре) количеством аргументов.&lt;br /&gt;&lt;br /&gt;Теперь надо понять, как подсунуть нужный класс. Класс будет или классом типа данных соответствующего столбца (имеющего то же имя) в базе данных, или именем класса аггрегации. Нас интересует именно эта аггрегация. В рельсах объявление агрегации осуществляется посредством вызова метода composed_of.&lt;br /&gt;&lt;br /&gt;Итак начнем писать наш тип данных для времени. Нам понадобится конструктор с одним параметром - число минут с начала суток (данные, которые будут храниться в базе). Также, сразу же объявим ридеры для часов и минут, и метод to_s:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;class CalendarTime&lt;br /&gt;  attr_reader :minutes_since_midnight&lt;br /&gt;&lt;br /&gt;  def initialize(minutes)&lt;br /&gt;    @minutes_since_midnight = minutes&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def hour&lt;br /&gt;    @minutes_since_midnight / 60 rescue nil&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def min&lt;br /&gt;    @minutes_since_midnight % 60 rescue nil&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def to_s&lt;br /&gt;    "%d:%02d" % [ hour, min ]&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Теперь добавим агрегацию в модель:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;class CalendarEvent &lt; ActiveRecord::Base&lt;br /&gt;  composed_of :time,&lt;br /&gt;    :class_name =&gt; 'CalendarTime',&lt;br /&gt;    # отображаем AR атрибут time на поле&lt;br /&gt;    # minutes_since_midnight нашего класса&lt;br /&gt;    :mapping =&gt; [:time, :minutes_since_midnight ],&lt;br /&gt;    # разрешить nil в качестве значения агрегата&lt;br /&gt;    :allow_nil =&gt; true &lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Отлично. Теперь при обращении к event.time мы получим экземпляр CalendarTime.&lt;br /&gt;Теперь представление. Надо сделать хелпер который будет выдавать два селекта с хитрыми именами.&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;module ApplicationHelper&lt;br /&gt;  def time_select(object_name, method, options = {})&lt;br /&gt;    # Получим сам объект&lt;br /&gt;    object = options[:object] ||&lt;br /&gt;      instance_variable_get('@'+object_name)&lt;br /&gt;    # и значение&lt;br /&gt;    value = object.send(method)&lt;br /&gt;&lt;br /&gt;    # Подготовим опции&lt;br /&gt;    options.delete :object&lt;br /&gt;    object[:discard_type] = true&lt;br /&gt;&lt;br /&gt;    # Подготовим шаблон для имени элементов&lt;br /&gt;    name = "#{object_name}[#{attr}(%di)]"&lt;br /&gt;&lt;br /&gt;    # Наш объект CalendarTime поддерживает свойства hour и minute&lt;br /&gt;    # поэтому можно будет воспользоваться стандартными хэлперами&lt;br /&gt;    select_hour(value, options.merge :prefix =&gt; name%1) + ' : ' +&lt;br /&gt;      select_minute(value, options.merge :prefix =&gt; name%2)&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Теперь в представлении сделаем вызов этого хелпера:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;p&amp;gt;&lt;br /&gt;  &amp;lt;label for="event_time"&amp;gt;Time&amp;lt;/label&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;  &amp;lt;%= time_select 'event', 'time', :include_blank =&gt; true %&amp;gt;&lt;br /&gt;&amp;lt;/p&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Отлично, осталось только собрать данные назад.&lt;br /&gt;Как я уже говорил, при обработке мультипараметров ActiveRecord сконструирует агрегирующий объект (в нашем случае - CalendarTime) с соответствующим количеством параметров. Значит надо обработать два параметра в конструкторе CalendarTime. Для этого перепишем его так:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;class CalendarTime&lt;br /&gt;  def initialize(*args)&lt;br /&gt;    if args.size == 1 &amp;&amp; args[0].is_a?(Numeric)&lt;br /&gt;      @minutes_since_midnight = args[0]&lt;br /&gt;    elsif (args.size == 2 &amp;&amp;&lt;br /&gt;           args[0].is_a?(Numeric) &amp;&amp;&lt;br /&gt;           args[1].is_a?(Numeric))&lt;br /&gt;      @minutes_since_midnight = args[0]*60 + args[1]&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Собственно, все.&lt;br /&gt;&lt;br /&gt;Чтобы еще больше понимать механизмы работы рельсов, было полезно узнать, как (и когда) работает отображение данных в composed_of. Так вот, агрегат создается по каждому требованию и ему в качестве аргемнтов конструктора передаются значения всех замапленых атрибутов (перечисленных в :mapping опции) в указанном порядке (поэтому значение этой опции или массив из двух элементов, или массив массивов из двух элементов - чтобы можно было отследить порядок аргументов). Обратное присвоение происходит при присвоении модели нового значения агрегата.&lt;br /&gt;&lt;br /&gt;PS есть один gotcha: при сборке агрегата из мультипараметра, рельсы по каким-то причинам отфильтровывают все пустые строки или nil. Будьте осторожны с этим. Вам в конструкторе могут подсунуть аргументов меньше, чем должно быть.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-6276882444501019975?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/6276882444501019975/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=6276882444501019975' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/6276882444501019975'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/6276882444501019975'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2006/11/blog-post.html' title='Сборка / разборка данных'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-5610731466794855376</id><published>2006-10-03T18:16:00.000+04:00</published><updated>2006-10-03T18:26:27.692+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='russian'/><title type='text'>Inline layouts</title><content type='html'>В Rails уже давно есть возможность брать контент экшена не из файла, а передовать его в строке. Таким образом легко сделать простой CMS: берем шаблон из базы и делаем render :inline =&gt; . Проблема же построения более менее пригодного CMS заключалась в том, что подобную операцию нельзя было провести с лэйаутами.&lt;br /&gt;&lt;br /&gt;Недавно я написал маленький плагин - Nested layouts (http://nested-layouts.rubyforge.org). Я решил, что возможность использования inline лэйаутов будет полезной добавкой к нему.&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&lt;% inside_inline_layout @layout_template do %&gt;&lt;br /&gt;  content&lt;br /&gt;&lt;% end %&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Так что, кому надо - берите, пользуйте, пишите впечатления.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-5610731466794855376?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/5610731466794855376/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=5610731466794855376' title='Комментарии: 3'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/5610731466794855376'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/5610731466794855376'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2006/10/inline-layouts.html' title='Inline layouts'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-1724957205721263661</id><published>2006-10-02T15:52:00.000+04:00</published><updated>2006-10-31T20:48:41.114+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='russian'/><title type='text'>IF/UNLESS блоки в RJS шаблонах</title><content type='html'>Недавние занятия программированием с Ajax в Rails привели к созданию патча для поддержки создания IF блоков в javascript'е. Основная идея (помимо условий в виде строк) - поддержка элемент-прокси:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;page.if page['element_id'].visible do&lt;br /&gt;  page['element_id'].hide&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Проблема в том, что page['element_id'].visible сгенерит код до того, как будет обернуто в IF блок.&lt;br /&gt;Также можно использовать более традиционные методы:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;page.unless "$('element_id').visible()" do&lt;br /&gt;  page['element_id'].show&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Патч был отклонен в core, но было предложено сделать плангин для обкатки.&lt;br /&gt;&lt;br /&gt;Собственно, вот и он: http://rubyforge.org/projects/js-if-blocks. Инсталировать можно так:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;  ./script/plugin install svn://rubyforge.org/var/svn/js-if-blocks/trunk/js-if-blocks&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Тепрь думаю о добавлении возможности повторного использования proxy объектов, например:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;element = page['element_id]&lt;br /&gt;page.if element.visible do&lt;br /&gt;  element.hide&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-1724957205721263661?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/1724957205721263661/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=1724957205721263661' title='Комментарии: 7'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/1724957205721263661'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/1724957205721263661'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2006/10/ifunless-rjs.html' title='IF/UNLESS блоки в RJS шаблонах'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-115831980087888990</id><published>2006-09-15T15:24:00.000+04:00</published><updated>2006-09-15T15:30:00.886+04:00</updated><title type='text'>RJS tricks</title><content type='html'>Just have read &lt;a href="http://project.ioni.st/images/uploads/EuroRailsConf.pdf"&gt;presentation slides&lt;/a&gt; from Euro RailsConf 2006 talk "Sharing RJS: Reuse at the app level" and I think it is VERY helpful.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-115831980087888990?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/115831980087888990/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=115831980087888990' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/115831980087888990'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/115831980087888990'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2006/09/rjs-tricks.html' title='RJS tricks'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-115815428198023311</id><published>2006-09-13T16:48:00.000+04:00</published><updated>2006-09-13T17:33:52.246+04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><title type='text'>Rails javascript generation and conditions</title><content type='html'>I've recently faced the problem: I need to generate javascript that would insert some HTML block unless it is already there. So, the code should be something like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;if( !($('block_id')) ) {&lt;br /&gt;   // insert block&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;All that should go to RJS template.&lt;br /&gt;&lt;br /&gt;First revision:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;page &lt;&lt; "if( !($('block_id')) ) {"&lt;br /&gt;page.insert_html :before, 'container_id', 'my html'&lt;br /&gt;page &lt;&lt; "}"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Next, it would be nice to make use of JavaScriptProxy objects (that are spawn, e.g., by page#[] method). The problem is that HTML is emited when those proxy created or evaluated, thus it's not possible to wrap that javascript code into e.g. "if( ... )"..&lt;br /&gt;Solution, first try:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class &lt;&lt; page&lt;br /&gt;  def if(expr)&lt;br /&gt;    self &lt;&lt; "if( #{expr} ) {"&lt;br /&gt;    yield if block_given?&lt;br /&gt;    self &lt;&lt; "}"&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;page.if "$('#{element_id}').visible()" do&lt;br /&gt;  page[element_id].hide&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This is better then the first one, but too ugly though. A better solution would include some black magick:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;module AcitiveView&lt;br /&gt;  module Helpers&lt;br /&gt;    class JavaScriptProxy&lt;br /&gt;      def respond_to?(name)&lt;br /&gt;        return true if name.to_sym == :to_script&lt;br /&gt;        super&lt;br /&gt;      end&lt;br /&gt;&lt;br /&gt;      def to_script&lt;br /&gt;        @generator.instance_variable_get('@lines').pop.chomp(';')&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Then:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class &lt;&lt; page&lt;br /&gt;  def if(expr)&lt;br /&gt;    self &lt;&lt; "if( #{expr.respond_to?(:to_script) ? expr.to_script : expr} ) {"&lt;br /&gt;    yield if block_given?&lt;br /&gt;    self &lt;&lt; "}"&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;page.if(page[element_id].visible) do&lt;br /&gt;  ...&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And finally, the more peaceful variant:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;module ApplicationHelper&lt;br /&gt;  def if_exists(page, element_id)&lt;br /&gt;    page &lt;&lt; "if( !($('#{element_id}')) ) {"&lt;br /&gt;    yield if block_given?&lt;br /&gt;    page &lt;&lt; "}"&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;if_exists page, 'block_id' do&lt;br /&gt;  page.insert_html .....&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-115815428198023311?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/115815428198023311/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=115815428198023311' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/115815428198023311'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/115815428198023311'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2006/09/rails-javascript-generation-and.html' title='Rails javascript generation and conditions'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-115791660366637336</id><published>2006-09-10T22:46:00.000+04:00</published><updated>2006-09-10T23:30:03.713+04:00</updated><title type='text'>Site map</title><content type='html'>How do you divide site into areas ? How do you highlight current link ?&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Definition&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;You've got site with several navigation bars, e.g. top menu and sidebar. You want current location highlighted on both top menu and sidebar.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Analysis&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Rails has &lt;span style="font-style: italic;"&gt;link_to_unless_current&lt;/span&gt; method to deal with such things, but it will not be enough: it handles link to only one page, but you need the bunch of pages be linked to. Link to can become selected only on one of the page, but on others it will not be selected (because they have different urls).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Solution&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;My solution was to develop some site structure markup that could associate each page with some "area" or "section" or kind of..&lt;br /&gt;&lt;br /&gt;I wrote &lt;span style="font-weight: bold;"&gt;site_map&lt;/span&gt; plugin that handles that:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;  SiteMap.draw do |map|&lt;br /&gt;    map.area 'member' do&lt;br /&gt;      map.section 'profile', :controller =&gt; 'member' do&lt;br /&gt;        ['profile', 'profile_update',&lt;br /&gt;          'company_profile', 'company_profile_update'&lt;br /&gt;        ].each do |action|&lt;br /&gt;          map.location :action =&gt; action&lt;br /&gt;        end&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt; &lt;br /&gt;    map.area 'admin' do&lt;br /&gt;      map.section 'users', :controller =&gt; 'users'&lt;br /&gt;      map.section 'products', :controller =&gt; 'products'&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;In controller and in view there is a current_location method available that returns a location descriptor - object, that has &lt;span style="font-style: italic;"&gt;area&lt;/span&gt;, &lt;span style="font-style: italic;"&gt;section&lt;/span&gt;, etc. values populated with names that correspond to current page. You can use it to find out what link should be "selected":&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;  &lt;%= link_to_if current_location.section == 'users',&lt;br /&gt;           'User management', :controller =&gt; 'users' %&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-115791660366637336?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/115791660366637336/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=115791660366637336' title='Комментарии: 2'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/115791660366637336'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/115791660366637336'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2006/09/site-map.html' title='Site map'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-114924547260368016</id><published>2006-06-02T13:59:00.000+04:00</published><updated>2006-06-02T14:51:12.703+04:00</updated><title type='text'>Pagination/Sorting/Filtering Automation</title><content type='html'>After reading "Nested with_scope" post I thought that it would be handy way to create entity lists with sorting, pagination and filtering features.&lt;br /&gt;&lt;br /&gt;You could setup an around filter that would automatically paginate and filter your model entities.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;class MyController &lt; ApplicationController&lt;br /&gt;  around_filter SearchFilter.new(:products, :search_string), :only =&gt; :list&lt;br /&gt;  around_filter PagintationFilter.new(:products, 20), :only =&gt; :list&lt;br /&gt;&lt;br /&gt;  def list&lt;br /&gt;    @products = Product.find(:all)&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Those could be wrapped in some kind of DSL (class methods).&lt;br /&gt;&lt;br /&gt;The problem with similar sorting filter is that AR::Base::with_scope doesn't allow :order param.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-114924547260368016?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://habtm.com/articles/2006/02/22/nested-with_scope' title='Pagination/Sorting/Filtering Automation'/><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/114924547260368016/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=114924547260368016' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/114924547260368016'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/114924547260368016'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2006/06/paginationsortingfiltering-automation.html' title='Pagination/Sorting/Filtering Automation'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-114899566441888498</id><published>2006-05-30T17:22:00.000+04:00</published><updated>2006-05-30T17:29:40.893+04:00</updated><title type='text'>Acts As Authenticated and Ajax</title><content type='html'>I've been using AAA-based authentication system and got problems with session expiry. When session is expired action from protected page should redirect to login page. But this doesn't work right if the action is called by Ajax.&lt;br /&gt;&lt;br /&gt;The solution is to do the redirect with RJS if it is Ajax request.&lt;br /&gt;&lt;br /&gt;Though I don't plan to handle redirects in Ajax request other then to do them, I've changed ActionController::Base.redirect_to to produce RJS redirect for Ajax requests:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;def redirect_to(options = {}, *params)&lt;br /&gt;  if request.xhr?&lt;br /&gt;    render :update do |page|&lt;br /&gt;      page.redirect_to url_for(options, *params)&lt;br /&gt;    end&lt;br /&gt;  else&lt;br /&gt;    super&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-114899566441888498?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/114899566441888498/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=114899566441888498' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/114899566441888498'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/114899566441888498'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2006/05/acts-as-authenticated-and-ajax.html' title='Acts As Authenticated and Ajax'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-114899475510099045</id><published>2006-05-30T16:45:00.000+04:00</published><updated>2006-06-02T13:53:42.500+04:00</updated><title type='text'>RJS update and redirects</title><content type='html'>&lt;div style="text-align: justify;"&gt;Recently I've ran into problem with processing redirects with Ajax.&lt;br /&gt;First solution that I've found was using custom status code handler in &lt;span style="font-style: italic;"&gt;link_to_remote&lt;/span&gt; (and some other functions), like this:&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&lt;% link_to_remote "Foo",&lt;br /&gt;      :url =&gt; { :action =&gt; "foo" },&lt;br /&gt;      302 =&gt; "document.location = request.getResponseHeader('location')" %&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div style="text-align: justify;"&gt;But I've got problems with it:&lt;br /&gt;1. It didn't work for me&lt;br /&gt;2. I find it rather harassing to add 302 =&gt; .... to every link_to_remote function (which sometimes are hidden deep inside helper methods)&lt;br /&gt;&lt;br /&gt;Second attempt to solve this problem was using RJS &lt;span style="font-style: italic;"&gt;redirect_to&lt;/span&gt;:&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;render :update do |page|&lt;br /&gt;  page.redirect_to '/some/url'&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div style="text-align: justify;"&gt;This worked fine but had some unwanted side-effects: when redirect is rendered it appeared on a page inside updated element while browser was preparing to do the redirect.&lt;br /&gt;After some investigation I've found out that the javascript was sent with content-type of 'text/javascript' and plain text (without any &amp;lt;script&amp;gt; tag surrounding). That's why after Prototype's &lt;span style="font-style: italic;"&gt;Ajax.Updater&lt;/span&gt; inserted it into update element it showed up. Then I dug some Prototype's sources and found out that it can strip &amp;lt;script&amp;gt; tags and evaluates everything inside it (if needed). So, my solution was to alter ActionController::Base.render_javascript&lt;br /&gt;to surround javascript code with &amp;lt;script&amp;gt; tags if it was Ajax request.&lt;br /&gt;&lt;br /&gt;Here is the code:&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;module ActionController&lt;br /&gt;  class Base&lt;br /&gt;    def render_javascript(javascript, status = nil) #:nodoc:&lt;br /&gt;      if @request.xhr?&lt;br /&gt;        render_text("&amp;lt;script type="'text/javascript'"&amp;gt;#{javascript}&amp;lt;/script&amp;gt;", status)&lt;br /&gt;      else&lt;br /&gt;        @response.headers['Content-Type'] = 'text/javascript; charset=UTF-8'&lt;br /&gt;        render_text(javascript, status)&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;hr&gt;&lt;br /&gt;I found out that content-type recongnition is implemented in Prototype v1.5something... So there is no need to patch render_javascript anymore...&lt;br /&gt;&lt;br /&gt;Just be sure to run "rake rails:update:javascripts"&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-114899475510099045?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/114899475510099045/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=114899475510099045' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/114899475510099045'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/114899475510099045'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2006/05/rjs-update-and-redirects.html' title='RJS update and redirects'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-25445709.post-114423196692685739</id><published>2006-04-05T13:28:00.000+04:00</published><updated>2006-04-05T14:12:47.130+04:00</updated><title type='text'>ActiveRecord observers and STI</title><content type='html'>&lt;div style="text-align: justify;"&gt;Last couple of days I spent my time trying to figure out, why my AR model observers are not working.&lt;br /&gt;&lt;br /&gt;I've got several models (actually, different user types) that shared the same table through Single Table Inheritance. So, I've got User and Member models (where Member inherit from User).&lt;br /&gt;&lt;br /&gt;I've also got an observer for User model (a UserObserver) that should send a notification email after User has registered. Then I've made a form for Member registration and was confused by the fact that my observer callbacks didn't get triggered every time I register new user.&lt;br /&gt;&lt;br /&gt;After digging Rails internals, I've found out that my understanding of how observers work was not correct: when observer was registered for particular class, it traverses that class and all it's subclasses and applies callback hooks to each of them.  The problem was that when observer is applied, my Member model is not loaded yet (and thus it had no callback hooks). So, I put "require 'member'" at the end of my "user.rb" and it worked.&lt;br /&gt;&lt;br /&gt;So, it worked first but soon broke. After another digging Rails internals I figured out, that the problem was in class caching. In development mode, reloadable classes (that are controller, model and some other classes) are reloaded every request. BUT, observers are not applied after the model has been reloaded.&lt;br /&gt;&lt;br /&gt;Then I went digging Rails on how class reload is accomplished and found out that there were pretty nice schema: ActiveSupport package provided simple dependency loading by implementing custom "const_missing" method. Every time user reference some class that wasn't loaded yet, the "const_missing" is called and that class got loaded.&lt;br /&gt;&lt;br /&gt;ActionController also had some dependency tricks like "model ...", "observer ..." etc class methods. So, I set up "observer :user_observer" in the ApplicationController to force observer to be applied on every reload:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class ApplicationController &lt; ActionController::Base&lt;br /&gt;  observer :user_observer&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This would be sufficient if there were no STI thing: UserObserver observer get loaded and reference User model, which is loaded then (but it appears that Member model doesn't registered as User subclass at that time)..&lt;br /&gt;&lt;br /&gt;So, after all that the solution was to include "model :user" before "observer :user_observer" in the ApplicationController:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class ApplicationController &lt; ActionController::Base&lt;br /&gt;  model :user&lt;br /&gt;  observer :user_observer&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/25445709-114423196692685739?l=maximkulkin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://maximkulkin.blogspot.com/feeds/114423196692685739/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=25445709&amp;postID=114423196692685739' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/114423196692685739'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/25445709/posts/default/114423196692685739'/><link rel='alternate' type='text/html' href='http://maximkulkin.blogspot.com/2006/04/activerecord-observers-and-sti.html' title='ActiveRecord observers and STI'/><author><name>Maxim Kulkin</name><uri>http://www.blogger.com/profile/00272049513122973998</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_Es7FfWo9nj4/SNFIr0KUkhI/AAAAAAAAAE4/i_IF4PBBIfk/S220/hapk_160_3_2.jpg'/></author><thr:total>1</thr:total></entry></feed>
