<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" version="2.0">

<channel>
	<title>Everyday ruby life</title>
	
	<link>http://pahanix.com</link>
	<description>программирование с каплей здравого смысла</description>
	<lastBuildDate>Fri, 12 Feb 2010 22:35:49 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.1</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://feeds.feedburner.com/pahanix-ruby-dev" /><feedburner:info xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" uri="pahanix-ruby-dev" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><item>
		<title>ActiveRecord, method_missing и stack level too deep</title>
		<link>http://pahanix.com/2010/02/active-record-method_missing-and-stack-level-too-deep/</link>
		<comments>http://pahanix.com/2010/02/active-record-method_missing-and-stack-level-too-deep/#comments</comments>
		<pubDate>Wed, 10 Feb 2010 17:54:13 +0000</pubDate>
		<dc:creator>pahanix</dc:creator>
				<category><![CDATA[Rails]]></category>
		<category><![CDATA[method_missing]]></category>

		<guid isPermaLink="false">http://pahanix.com/?p=141</guid>
		<description><![CDATA[Переопределяя у ActiveRecord модели #method&#95;missing важно помнить, что методы чтения атрибутов генерятся через сам #method&#95;missing

Так, например, код

class Appearance &#60; ActiveRecord::Base
  serialize :prefereneces, HashWithIndifferentAccess

  def method_missing(name, *args)
    preferences[name] &#124;&#124; super
  end
end


вывалит Exception SystemStackError: stack level too deep

Обойти это можно, вызвав сначала super и перехватив NoMethodError, а потом его рейзануть обратно [...]]]></description>
			<content:encoded><![CDATA[<p>Переопределяя у <em>ActiveRecord</em> модели <em>#method&#95;missing</em> важно помнить, что методы чтения атрибутов генерятся через сам <em>#method&#95;missing</em></p>

<p>Так, например, код</p>

<pre><code>class Appearance &lt; ActiveRecord::Base
  serialize :prefereneces, HashWithIndifferentAccess

  def method_missing(name, *args)
    preferences[name] || super
  end
end
</code></pre>

<p>вывалит Exception <strong>SystemStackError: stack level too deep</strong></p>

<p>Обойти это можно, вызвав сначала <em>super</em> и перехватив <em>NoMethodError</em>, а потом его рейзануть обратно если не выполнилось необходимое условие.</p>

<pre><code>def method_missing(name, *args)
  super
rescue NoMethodError
  preferences[name] || raise
end
</code></pre>
]]></content:encoded>
			<wfw:commentRss>http://pahanix.com/2010/02/active-record-method_missing-and-stack-level-too-deep/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Побочные эффекты в рельсах. ActiveRecord::Base#update_attribute</title>
		<link>http://pahanix.com/2009/07/side-effects-in-rails-1-active-record-update_attribute/</link>
		<comments>http://pahanix.com/2009/07/side-effects-in-rails-1-active-record-update_attribute/#comments</comments>
		<pubDate>Thu, 23 Jul 2009 19:12:53 +0000</pubDate>
		<dc:creator>pahanix</dc:creator>
				<category><![CDATA[Rails]]></category>

		<guid isPermaLink="false">http://pahanix.com/?p=101</guid>
		<description><![CDATA[Метод ActiveRecord::Base#update&#95;attribute согласно документации предназначен для обновления отдельного атрибута и &#8230; дальше мало кто читает внимательно &#8212; сохранения без прохождения процесса валидации, что приводит к некоторым побочным эффектам (side effects), когда метод может обновлять любое кол-во атрибутов и даже сохранять ассоциации! Смотрите.

Создаем тестовый проект

$ rails side_effects_in_rails_1_active_record_update_attribute
$ cd side_effects_in_rails_1_active_record_update_attribute
$ script/generate rspec_model Post title:string body:string
$ rake db:migrate
$ [...]]]></description>
			<content:encoded><![CDATA[<p>Метод ActiveRecord::Base#<strong>update&#95;attribute</strong> согласно <a href="http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M002335">документации</a> предназначен для обновления отдельного атрибута и &#8230; дальше мало кто читает внимательно &#8212; сохранения без прохождения процесса валидации, что приводит к некоторым побочным эффектам (side effects), когда метод может обновлять <em>любое кол-во атрибутов и даже сохранять ассоциации</em>! Смотрите.</p>

<p>Создаем тестовый проект</p>

<pre><code>$ rails side_effects_in_rails_1_active_record_update_attribute
$ cd side_effects_in_rails_1_active_record_update_attribute
$ script/generate rspec_model Post title:string body:string
$ rake db:migrate
$ rake db:test:clone
$ script/generate rspec
</code></pre>

<p>Напишем тест</p>

<pre><code># spec/models/post_spec.rb
describe Post, "#update_attribute" do
  before(:each) do
    @post = Post.create :title =&gt; "Old Title", :body =&gt; "Old Body"
    @post.should_not be_new_record
  end

  # строим наше предположение

  it "should update title and should NOT update body" do
    @post.body = "New Body"
    @post.update_attribute(:title, "New Title")

    @post.reload         # делаем reload, чтобы "сбросить" присвоение body

    @post.title.should == "New Title"
    @post.body.should == "Old Body"
  end
end
</code></pre>

<p>Запускаем</p>

<pre><code>$ spec -c -fs spec/models/post_spec.rb


Post#update_attribute
- should update title and should NOT update body (FAILED - 1)

1)
'Post#update_attribute should update title and should NOT update body' FAILED
expected: "Old Body",
     got: "New Body" (using ==)
./spec/models/post_spec.rb:18:

Finished in 0.12975 seconds
</code></pre>

<p>Атрибут body сохранился, хотя мы этого совсем не хотели.
Интересно? Идем дальше.</p>

<h3>Сохраняем ассоциации через #update&#95;attribute</h3>

<p>Создадим модель Comment</p>

<pre><code>$ script/generate rspec_model Comment body:string post:belongs_to
$ rake db:migrate
$ rake db:test:clone    
</code></pre>

<p>добавим минимальную валидацию</p>

<pre><code># app/model/comment.rb
class Comment &lt; ActiveRecord::Base
  validates_presence_of :body
end
</code></pre>

<p>и в Post добавим ассоциацию <em>comments</em></p>

<pre><code># app/model/post.rb
class Post &lt; ActiveRecord::Base
  has_many :comments
end
</code></pre>

<p>Добавим несколько тестов</p>

<ul>
<li>добавление валидного коментария</li>
<li>добавление невалидного коментария</li>
<li>добавление сразу двух валидного и невалидного комментариев</li>
</ul>

<p>&nbsp;</p>

<pre><code># spec/models/post_spec.rb

it "should update title and should NOT save a valid associated comment" do
  @post.comments.should be_blank
  # build добвляет объект в ассоциацию, но не сохраняет его
  @post.comments.build :body =&gt; "Comment"

  @post.update_attribute(:title, "New Title")

  @post.reload

  @post.title.should == "New Title"

  # вместо ...should be_blank, напишем should == [], чтобы получить @post.comments.inspect
  @post.comments.should == []
end

it "should update title and should NOT save an invalid comment" do
  @post.comments.should be_blank
  @post.comments.build :body =&gt; ""

  @post.update_attribute(:title, "New Title")

  @post.reload

  @post.title.should == "New Title"
  @post.comments.should == []
end

it "should update title and should NOT save any comment" do
  @post.comments.should be_blank
  @post.comments.build :body =&gt; ""        # invalid comment
  @post.comments.build :body =&gt; "Comment" # valid comment

  @post.update_attribute(:title, "New Title")

  @post.reload

  @post.title.should == "New Title"
  @post.comments.should == []
end
</code></pre>

<p>Снова запускаем</p>

<pre><code>$ spec -c -fs spec/models/post_spec.rb

Post#update_attribute
- should update title and should NOT update body (FAILED - 1)
- should update title and should NOT save a valid associated comment (FAILED - 2)
- should update title and should NOT save an invalid comment
- should update title and should NOT save any comment (FAILED - 3)

1)
'Post#update_attribute should update title and should NOT update body' FAILED
expected: "Old Body",
     got: "New Body" (using ==)
./spec/models/post_spec.rb:19:

2)
'Post#update_attribute should update title and should NOT save a valid associated comment' FAILED
expected: [],
     got: [#&lt;Comment id: 1, post_id: 1, body: "Comment", created_at: "2009-07-22 09:11:35", updated_at: "2009-07-22 09:11:35"&gt;] (using ==)
./spec/models/post_spec.rb:50:

3)
'Post#update_attribute should update title and should NOT save any comment' FAILED
expected: [],
     got: [#&lt;Comment id: 1, post_id: 1, body: "Comment", created_at: "2009-07-22 09:11:35", updated_at: "2009-07-22 09:11:35"&gt;] (using ==)
./spec/models/post_spec.rb:75:

Finished in 0.174812 seconds

4 examples, 3 failures
</code></pre>

<p>Как видим &#8212; валидный коментарий сохранился, невалидный &#8212; нет, при добалении обоих в ассоциацию сохранился только 1 валидный и никакого сообщения об ошибке мы не получили. А ведь мы всего лишь хотели обновить один аттрибут!</p>

<h3>Какой выход?</h3>

<p>Использовать ActiveRecord::Base#update_attribute только в тех случаях, когда известно на 100%, что объект не изменяется, в других же ситуациях использовать ActiveRecord::Base::update</p>

<p>Напишем тест</p>

<pre><code>it "should update title and should NOT update body (v2)" do
  @post.body = "New Body"

  Post.update(@post, :title =&gt; "New Title")

  @post.reload

  @post.title.should == "New Title"
  @post.body.should == "Old Body"
end
</code></pre>

<p>Запускаем тест</p>

<pre><code>$ spec -c -fs spec/models/post_spec.rb -l 70

Post#update_attribute
- should update title and should NOT update body (v2)

Finished in 0.224997 seconds

1 example, 0 failures
</code></pre>

<p>Вуаля! работает (<em>-l 70</em> означает запустить отдельный тест, обрамляющий 70-ю строку)</p>

<p>Я создал на гитхабе <a href="http://github.com/pahanix/side_effects_in_rails_1_ar_update_attribute/tree/master">репозиторий</a>, чтобы можно было пощупать пример целиком.</p>

<p><strong>P.S.</strong> Проверено на rails 2.3.2 и 2.3.3</p>

<p><strong>UPD.</strong> Этот вопрос уже <a href="http://www.nabble.com/update_attribute-updates-too-much-td17628364.html">поднимали</a>  в англоязычной среде</p>
]]></content:encoded>
			<wfw:commentRss>http://pahanix.com/2009/07/side-effects-in-rails-1-active-record-update_attribute/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Элегантный код. Округление до n-го знака после запятой.</title>
		<link>http://pahanix.com/2009/07/clear-code-1/</link>
		<comments>http://pahanix.com/2009/07/clear-code-1/#comments</comments>
		<pubDate>Tue, 21 Jul 2009 13:22:22 +0000</pubDate>
		<dc:creator>pahanix</dc:creator>
				<category><![CDATA[Rails]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[clear code]]></category>

		<guid isPermaLink="false">http://pahanix.com/?p=95</guid>
		<description><![CDATA[Не первый раз натыкаюсь в коде на округление до 3-го знака после запятой таким способом

require 'active_support'
# ...
(object.to_f*1000).round/1000.0


Я не даром отметил require &#8216;active&#95;support&#8217;, если бы этого не было выше по коду &#8212; этой заметки не было бы.

Дело в том, что ActiveSupport расширяет метод Float#round, добавляя в него дополнительный аргумент precision, и в случае если он задан, [...]]]></description>
			<content:encoded><![CDATA[<p>Не первый раз натыкаюсь в коде на округление до 3-го знака после запятой таким способом</p>

<pre><code>require 'active_support'
# ...
(object.to_f*1000).round/1000.0
</code></pre>

<p>Я не даром отметил <em>require &#8216;active&#95;support&#8217;</em>, если бы этого не было выше по коду &#8212; этой заметки не было бы.</p>

<p>Дело в том, что <em>ActiveSupport</em> расширяет метод Float#round, добавляя в него дополнительный аргумент <em>precision</em>, и в случае если он задан, он делает то же самое, что и код выше, но итоговый код намного понятнее.</p>

<pre><code>object.to_f.round(3)
</code></pre>

<p><em>P.S.</em> ActiveSupport много чего еще добавляет &#8212; хватит на большую статью. Вместо изобретения велосипедов почитайте исходники того, чем пользуетесь.</p>
]]></content:encoded>
			<wfw:commentRss>http://pahanix.com/2009/07/clear-code-1/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Как я патчил и рефакторил плагин what_column для rails</title>
		<link>http://pahanix.com/2009/07/what-column-plugin-refactoring/</link>
		<comments>http://pahanix.com/2009/07/what-column-plugin-refactoring/#comments</comments>
		<pubDate>Sun, 12 Jul 2009 13:13:48 +0000</pubDate>
		<dc:creator>pahanix</dc:creator>
				<category><![CDATA[Patching]]></category>
		<category><![CDATA[Rails]]></category>
		<category><![CDATA[patch]]></category>
		<category><![CDATA[what_column]]></category>

		<guid isPermaLink="false">http://pahanix.com/?p=49</guid>
		<description><![CDATA[Ничего не предвещало беды

В текущем проекте у нас используется довольно полезный плагин what_column,
который после каждой миграции обновляет файлы в app/models, добавляя в коментарии 
название атрибутов и типы полей. Примерно вот так:

class Post &#60; ActiveRecord::Base
  # === List of columns ===
  #   id         : [...]]]></description>
			<content:encoded><![CDATA[<h3>Ничего не предвещало беды</h3>

<p>В текущем проекте у нас используется довольно полезный плагин <a href="http://github.com/thechrisoshow/what_column/tree">what_column</a>,
который после каждой миграции обновляет файлы в <em>app/models</em>, добавляя в коментарии 
название атрибутов и типы полей. Примерно вот так:</p>

<pre><code>class Post &lt; ActiveRecord::Base
  # === List of columns ===
  #   id         : integer 
  #   title      : string 
  #   body       : string 
  #   created_at : datetime 
  #   updated_at : datetime 
  # =======================
end
</code></pre>

<p>Очень удобно видеть какие поля присутствуют у той или иной модели, не залезая в <em>db/scheme.rb</em></p>

<h3>Но не все с плагином оказалось хорошо</h3>

<p>Недавно, пришлось добавить исключение (exception) в модель</p>

<pre><code>class Post &lt; ActiveRecord::Base
  class SomeOneIsLookingAtMyBeautifulPostException &lt; ::StandardError; end
end
</code></pre>

<p>И после очередной миграции начались проблемы</p>

<pre><code>rake db:migrate
uninitialized constant SomeOneIsLookingAtMyBeautifulPostException
</code></pre>

<p>Коментирование вложенного класса</p>

<pre><code># class SomeOneIsLookingAtMyBeautifulPostException &lt; ::StandardError; end
</code></pre>

<p>не дало никакого результата.</p>

<h3>Начинаем копать</h3>

<p>Идем в исходники плагина и после небольшого изучения видим следующее</p>

<pre><code># vendor/plugins/what_column/lib/what_column.rb:41

if line.match(/class (.*)\&lt;/) and $1.strip.constantize == ar_class
</code></pre>

<p>Опа! Регулярка матчит любое вхождение <em>class</em>, не учитывая начало строки, то есть перед <em>class</em>
может стоять все что угодно! Именно поэтому, когда я закоментировал определение класса, то получил ту же ошибку с <em>uninitialized constant</em> &#8230;</p>

<p>Осталось понять почему же так происходит. Текст ошибки очень похож на <em>NameError</em>. Его может генерировать <em>$1.strip.constantize</em></p>

<p><em>$1</em> содержит имя класса выдернутое регуляркой, <em>#strip</em> просто убирает пробелы по краям строки, значит смотрим код <em>#constantize</em></p>

<pre><code># active_support-2.3.2/lib/inflector.rb:355-364

def constantize(camel_cased_word)
  names = camel_cased_word.split('::')
  names.shift if names.empty? || names.first.empty?

  constant = Object
  names.each do |name|
    constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
  end
  constant
end
</code></pre>

<p>Код говорит &#8212; чтобы превратить строку <em>&#8220;Post::SomeOneIsLookingAtMyBeautifulPostException&#8221;</em> в константу, нужно
у <em>Object</em>&#8216;a запросить <em>Post</em>, а затем у <em>Post</em>&#8216;a запросить <em>SomeOneIsLookingAtMyBeautifulPostException</em>, 
то есть, так как определение <em>SomeOneIsLookingAtMyBeautifulPostException</em> находится внутри класса 
<em>Post</em>, значит напрямую запросив у <em>Object</em>&#8216;a <em>SomeOneIsLookingAtMyBeautifulPostException</em> (что у нас и происходит) 
мы получим исключение</p>

<pre><code>uninitialized constant SomeOneIsLookingAtMyBeautifulPostException
</code></pre>

<p>Бинго! С корнем проблемы разобрались.</p>

<h3>Пишем патч</h3>

<p>Добавим наши &#8220;случаи&#8221; в тесты, благо они в плагине присутствуют.</p>

<pre><code>class Shop::Product &lt; ActiveRecord::Base
  class ProductError &lt; ::StandardError; end # &lt;========== 
end

# class Shop::HotProduct &lt; Product          # &lt;==========
</code></pre>

<p>Пофиксим сначала случай с коментариями. Для этого дополним регулярку (она встречается в 2-х местах) символами <strong>^\s*</strong></p>

<pre><code>/class (.*)\&lt;/ 
/^\s*class (.*)\&lt;/
</code></pre>

<p><strong>^\s*</strong> означает, что в начале строки могут быть только пробелы или ничего, проверяем и видим, 
что закоментированные классы игнорируются &#8212; уже хорошо <img src='http://pahanix.com/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' />  пол дела сделали.</p>

<p>Добавим теперь <em>rescue NameError</em> чтобы перехватывать наши &#8220;неудавшиеся&#8221; классы</p>

<pre><code>klass = begin
  line.match(/^\s*class (.*)\&lt;/) &amp;&amp; $1.strip.constantize
rescue NameError
  # it means that we have defined subclasses in the class
  # for example
  #
  # class Order &lt; ActiveRecord::Base
  #   class TrackingError &lt; StandardError; end
  # end
  # 
  # "TrackingError".constantize will raise NameError because TrackingError doesn't exists in Object space
end

if klass == ar_class
</code></pre>

<p>Вуа-ля! теперь все работает!</p>

<p>Точнее почти все, еще возможен случай, что имя вложенного класса будет совпадать с именем в общем пространстве имен, 
но такие случаи редки, и самое худшее, что может сделать плагин &#8212; это добавить пару лишних коментариев, что не критично.
Поэтому забьем.</p>

<h3>Выводы</h3>

<ol>
<li>Баги есть везде, даже в плагинах (на самом деле, глючных плагинов очень много)</li>
<li>Изучайте исходники перед тем как добавлять плагин в проект, 
 даже беглый взгляд по коду может выявить его неадекватность</li>
<li>В опенсорсе баг вседа можно пофиксить, написать свой патч или форкнуться</li>
</ol>

<h3>Форк и рефакторинг</h3>

<p>Я написал небольшой патч, который фиксит what_column, но после того как я внимательно посмотрел код плагина, 
мне <em>он очень не понравился</em>, я и <a href="http://github.com/pahanix/what_column/tree">форкнулся на гитхабе</a> и потратил почти всю ночь на рефакторинг. 
Можете сравнить что <a href="http://github.com/thechrisoshow/what_column/blob/1572a413cf5e81ccb9b3ef6eb87ba3bc927b97db/lib/what_column.rb">было</a> и что <a href="http://github.com/pahanix/what_column/blob/88619a43f9cafd8c6462b533a20e3f7215d98fc6/lib/what_column.rb">стало</a></p>

<p>P.S. Спасибо Крису (автору плагина) за то, что в проекте были тесты, они очень, нет, даже ОЧЕНЬ облегчили рефакторинг.</p>

<p>Пишите тесты <img src='http://pahanix.com/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://pahanix.com/2009/07/what-column-plugin-refactoring/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

