Popular Posts

четверг, 12 января 2012 г.

Печём для моделей. Тестируем модели в rails

Привет.
Сегодня я покажу, как протестировать модели в rails. Для этого мы будем использовать RSpec - специальная утилита для Ruby. Сделана для BDD.
Сперва устанавливаем необходимые gem'ы.
gem install rspec
gem install factory_girl_rails
gem install rspec-rails
gem install shoulda-matchers
Первых два gem'а это непосредственно rspec и rspec для rails. Третий gem - это фабрика, которуб будет использовать вместо фикстур. Последний для тестирования ассоциаций.


и так у меня есть две модели: Post и Comment.
Таблицы для них элементарные:
- posts
   - title
   - content
 - comments
   - name
   - comment
   - post_id


Пост может иметь много комментариев - зависимость has_many.
Также я проверяю, чтобы все поля были заполнены - валидатор presence, а также для названия поста проверяю длину (от 3 символов).


Таким образом классы:
#/models/post.rb
Class Post < ActiveRecord::Base
   validates :title, :presence => true
   validates :content, :presence => true
   validates :title, :length => { :minimum => 3 }
#асосиации
   has_many :comments
end

#/models/comment.rb
Class Comment < ActiveRecord::Base
   validates :name, :presence => true
   validates :comment, :presence => true
#связываем 
   belongs_to :post
end

Всё, модели готовы.


Теперь напишем элементарные тесты, но для начала нужно включить установленные gem'ы в Gemfile:


#Gemfile
group :test do
  gem "rspec" 
  gem "rspec-rails"
  gem "factory_girl_rails"
  gem "shoulda-matchers"
end

Сейчас генерируем спеки:
rails g rspec:install

Эта команда генерирует директории для контроллеров, моделей, хелперов и так далее.


Отлично. Теперь самое интересное пишем тесты спеки.


#/spec/models/post_spec.rb
require 'spec_helper'

describe Post do
  #сначала проверяем валидаторы
  context "validates" do
     before(:each) do
        @post = Factory.create(:post)
     end
     
     #должна быть валидная, если валидные атрибуты (title, content)
     it "is valid with valid attributes" do
       @post.should be_valid
     end
     
     #не валидная без title   
     it "is not valid without a title" do
        @post.title = nil
        @post.errors_on(:title).should include("can't be blank")  
        @post.should be_valid
     end
     
     #не валидная без content'а
     it "is not valid without a content" do
               @post.content = nil
               @post.errors_on(:content).should include("can't be blank")
               @post.should_not be_valid
          end
     
      #не валидная, если длина названия, меньше чем 3 символа
           it "is not valid if length of title less then 3 characters" do
               @post.title = "12"
               @post.should_not be_valid
           end
      
      #валидная с длиной больше, чем 3 символа
           it "is valid if length of title greater then 3 characters" do
               @post.title = "123"
               @post.should be_valid
           end
   end
end
Что я использовал здесь.
Сперва я подписался на Post, (describe Post)
Затем я разбил всё на контексты (context), первый это валидаторы.
Затем методами it, я проверял утверждения, например: Длина названия должна быть больше 3 символов - это просто утверждение, проверяется в методе it. Но перед вызовами каждого теста, я устанавливал @post - это происходит в методе before(:each). Проверка происходит методами RSpec, should, should_not (должен и не должен). Например:


      #валидная с длиной больше, чем 3 символа
           it "is valid if length of title greater then 3 characters" do
               @post.title = "123"
               @post.should be_valid
           end


будет звучать так: Если длина названия содержит более чем 3 символа, то пост валиден.


Тоже самое пишем и для модели Comment


#/spec/models/comment_spec.rb
describe Comment do
   context "validates" do 
       before(:each) do
             @comment = Factory.create(:comment)
        end

   
        it "is valid with valid attributes" do
              @comment.should be_valid
          end

          it "is invalid without name" do
             @comment.name = nil
             @comment.should_not be_valid
          end

          it "is invalid without comment" do
             @comment.comment = nil
             @comment.should_not be_valid
          end
     end
 end
end


Как видем тоже самое, проверка имени и комментария.
Что дальше, теперь напишем тесты для ассоциаций, всё это можно было бы сделать и без shoulda, но с ним более удобно. Но не удобно (для меня) тестировать валидаторы, поэтому я применяю этот gem для тестирования ассоциаций.
Добавляем контексты


#/spec/models/post_spec.rb

context "associations" do
    it "should have many comments depending" do
               should have_many(:comments) #пост имеет много комментариев
         end
end
#/spec/models/comment_spec.rb
context "associations" do
       it "should belongs_to post" do
          should belong_to(:post) #комментарии должны быть связаны с постом
       end
end
Теперь, что с Factory Girl (хорошее название)
фактори делает не нужными фикстуры, кто с ними работал, тот знает, что написание фикстур это утомительно, очень утомительно.


#/spec/factories/factoryes.rb

FactoryGirl.define do
   factory :post do
       title "Title"
       content "Content"
   end
end

FactoryGirl.define do
   factory :comment do
        name "Foo"
        comment "Bar"
        post
   end
end
Когда мы делаем Factory.create(:post), то получаем модель с  задаными атрибутами. Всё просто, не фикстуры.


Теперь запускаем тесты, так как я написал спеки только для моделей, то и запускать буду для моделей:
rake spec:models
===>
/home/xxx/.rvm/rubies/ruby-1.9.3-p0/bin/ruby -S rspec ./spec/models/post_spec.rb ./spec/models/comment_spec.rb
..........


Finished in 0.13836 seconds
10 examples, 0 failures


 
Ссыли:


https://github.com/thoughtbot/shoulda-matchers
https://github.com/rspec/rspec-rails
https://github.com/thoughtbot/factory_girl

понедельник, 9 января 2012 г.

scriptaculous. Делаем свой эффект.

Привет!

Сейчас давайте рассмотрим работу, пожалуй одной из самых распространённых библиотек для javascript - scriptaculous.
Scriptaculous надстройка над другим фреймворком - prototypejs.

Она состоит из частей:
  • Effects - включает различные эффекты и также помощники.
  • Behaviours - это drag, drop, sort, и работа с формами.
  • Controls - аякс и автокомлитеры
  • Miscellaneous - это "другое" или остальное, сюда входят тесты (unit testing), звук (sound), создание элементов и узлов из элементов (builder)
Всё это можно найти на сайте и потом я расскажу о каждой из частей.

Сейчас же я покажу как работать с эффектами, вернее не работать, а как создать собственный эффект.
Но сначала пару слов об эффектах, ну то есть немного теории.
Все эффекты наследуются от Effect.Base.
Наследуемый эффект может определить 3 необязательных метода:

  • setup() - этот метод вызывается до начала выполнения эффекта, и делает необходимые настройки, например там можно определить, некоторые нужные переменные, и так далее.
  • update(pos) - вызывается для каждой итерации. Аргумент pos - это текуший кадр. 
  • finish() - вызовется после выполнения эффектов.
Как я говорил, все методы не обязательны, поэтому наиболее часто вызывается update(pos), в котором и происходит вся "магия" эффекта.

Сейчас перейдём от теории к практике. Сделаем свой эффект.
Effect.MyEffect = Class.create(Effect.Base, {
/*   

initialize - метод-конструктор для эффекта, принимает аргументы<

   element - это id элемента.

    setting - необязателен, я его упомянул для того, чтобы показать, что он есть, это также дополнительные настройки, которые могу использоваться в вашем эффекте.

*/ 
    initialize: function(element, setting)
   {
      this.element = $(element);
    //бросаем исключение, если элемент не найден.
      if(!this.element) throw(Effect._elementDoesNotExistError); 
      this.start(setting);
   },
  setup: function()
  {
    Effect.tagifyText(this.element); //разбиваем элемент
    this.letters = this.element.select('span');
  },
  update: function(position)
  {
     var F = position < 0.5 ? 2*position: 2*(1-position);
     var f_pos;
     var s_pos;
     this.letters.collect(function(letter, j){    
        f_pos = Math.sin(position)*15*F;
        s_pos = - f_pos;
        if(j%2==0)
           letter.setStyle({top: Math.round(f_pos) + 'px'});
        else
           letter.setStyle({top: Math.round(s_pos) + 'px'});
     });
 }
});
Вот и всё! Эффект готов. Для примера, этот эффект принимает id элемента, например, заголовка <h1>Lorem ipsum dolore</h1> Но также можно передать и Id параграфа, тогда будет веселее. А сейчас более подробно. В методе-конструкторе мы принимает id, и оборачиваем его $(), если элемент не найден будем бросать исключение. В методе setup, с помощью tagifyText, мы оборачиваем каждую букву элемента в span. Затем мы просто отбираем все элементы. Как и говорилось в update() вся магия, но суть в чём, если чётное в одну сторону, если нет, то  в другую. Формула, которая я использовал, не несёт в себе какой-либо смысловой нагрузки, поэтому понять не пытайтесь. (Хотя на самом деле это простая формула синуса). Затем мы на полученых значениях сдвигаем либо вверх, либо вниз отобранные span'ы. Вот и всё. Просто же. 

О чём этот блог

Привет Всем.
Когда я начал программировать, то мне не хватало примеров. Все примеры, ну или почти все, которые я находил в книгах, были какими-то надуманными, оторванными от реального мира.
Сейчас, когда я оглядываюсь назад, я думаю, что многим новичкам, которые хотя программировать, не хватает такого блога, где были бы сравнения фреймворков, сравнения языков, где были бы описаны реальные примеры, а не абстрактные задачи и абстрактные решения.