Пересекая границы: Rails-миграции

Пересмотр изменений схемы базы данных

Ruby on Rails - это прогрессивная интегрированная среда Web-разработки, реализующая радикальные идеи, такие как соглашение вместо конфигурирования, интенсивное метапрограммирование, предметно-ориентированные языки и обертывание (wrapping) базы данных вместо объектно-реляционного отображения. В данной статье исследуются миграции схемы Rails, философия отделения каждого изменения схемы базы данных от базовой объектной модели.

Брюс Тэйт, президент, RapidRed

Брюс Тэйт (Bruce Tate) является отцом, горным байкером и байдарочником, проживающим в Austin, Texas. Он автор трех бестселлеров по языку Java, в том числе, победителя Jolt "Лучше, быстрее, легче Java". Недавно издал "За пределами Java"… Он работал 13 лет в IBM, а сейчас является основателем RapidRed consultancy, где специализируется на стратегиях и архитектурах облегченной разработки, основанных на технологии Java и Ruby on Rails.



13.06.2007

Как страстный велосипедист, я знаю о двух сообществах: горных байкерах и шоссейных байкерах. Традиционное мнение состоит в том, что езда по горам намного опаснее, но я не согласен с этим. Шоссейные байкеры должны учитывать намного более опасные препятствия, чем горы или деревья - автомобили. Аналогичные разногласия существуют между приверженцами двух стратегий персистенции для разработки объектно-ориентированных приложений.

В настоящее время персистентные интегрированные среды используют один из двух подходов: отображение или обертывание. Отображение позволяет вам создавать независимые схемы базы данных и объектные модели, а затем использовать один уровень программного обеспечения для управления различиями между ними. Решения с отображением стремятся к созданию объектной модели, которая очень совпадает со структурой схемы базы данных. В отличие от этого, решения с обертыванием используют объекты как обертки вокруг таблиц и строк базы данных для управления данными в базе данных. Традиционное мнение состоит в том, что отображение часто является более гибким, поскольку использующее его программное обеспечение может лучше справиться с изменениями в схеме или объектной модели. Но это мнение игнорирует самую важную часть уравнения: данные. Для эффективного управления любым изменением приложения, связанным с моделью персистентности, вы должны скоординировать изменения в данных, в схеме и модели. Большинство разработчиков не до конца понимают это.

Группы разработчиков обычно обрабатывают изменения в схеме путем генерирования новой версии схемы с нуля при помощи SQL-сценариев. Сценарий должен удалить все таблицы и добавить их снова. Такая стратегия удаляет все тестовые данные и, следовательно, она бесполезна в производственной эксплуатации. Иногда инструментальные средства могут создать сценарии, генерирующие разностные схемы или схемы, использующие такие SQL-команды как alter_table для изменения предыдущей версии схемы. Но очень мало разработчиков беспокоится о создании сценариев, которые отменяют изменения схемы, а еще меньше разработчиков заботится о создании автоматизированных сценариев, работающих с изменениями в данных. Короче говоря, традиционные стратегии отображения игнорируют автомобили на дороге: обратные изменения неудачной схемы и обработку данных.

О данной серии статей

В серии статей "Пересекая границы" ее автор Брюс Тэйт развивает мнение о том, что современные Java-программисты имеют широкие возможности для изучения других подходов и языков. Сфера программирования изменилась после того, как Java-технология стала очевидным лучшим выбором для всех разрабатываемых проектов. Другие интегрированные среды формируют способ построения Java-сред, а концепции, осваиваемые вами в других языках, могут дать новый импульс вашему Java-программированию. Написанный вами Python-код (или Ruby, или Smalltalk, или ...) может изменить способ вашего Java-кодирования.

Данная серия статей знакомит вас с концепциями и методиками программирования, которые радикально отличны от Java-разработки, но в то же время напрямую применимы к ней. В одних случаях вам понадобится интегрировать технологию для использования ее возможностей. В других вы будете способны применить концепции непосредственно. Конкретное инструментальное средство не так важно, как идея о том, что другие языки программирования и интегрированные среды могут оказать влияние на разработчиков, среды разработки и даже на фундаментальные подходы в Java-сообществе.

В данной статье детально рассматриваются миграции Ruby on Rails - Rails-решение для работы с изменениями в рабочей базе данных. Миграции объединяют мощь и простоту для координирования изменений схемы и изменений данных, используя подход с обертыванием (если вы не знакомы с Active Record, использующимся в Rails уровнем персистенции, я рекомендую вам сначала прочитать об этом в одной из предыдущих статей по Rails в серии "Пересекая границы").

Управление изменениями схемы в Java-программировании

Основанные на отображении интегрированные среды требуют наличия схемы, модели и карты. Такая архитектура может быть итерационной. Подумайте о том, сколько раз вы должны указать данный атрибут:

  • Метод getter в модели
  • Метод setter в модели
  • Переменную экземпляра в модели
  • Сторону "в" отображения
  • Сторону "из" отображения
  • Определение столбца

По правде говоря, интегрированные Java-среды, например Hibernate, защищают вас от многих таких повторений путем широкого использования генерирования кода. В то время как объектно-реляционные отображения позволяют вам работать с традиционными схемами, для новых схем баз данных вы можете сгенерировать схему непосредственно из вашей модели при помощи предоставляемых в Hibernate инструментальных средств, а также можете сгенерировать методы getters и setters при помощи IDE. Вы можете встроить ваше отображение в предметную модель (по моему мнению, частично аннулируя первичное предназначение карты) при помощи Java-аннотаций. Такая методика генерирования кода также служит еще одной цели - миграции схемы. Некоторые из этих инструментальных средств генерирования кода могут обнаруживать различия между вашей новой предметной моделью и старой схемой, а также генерировать SQL-сценарии для преодоления этих различий. Помните о том, что эти сценарии имеют дело со схемой, но не с данными.

Например, рассмотрим миграцию, которая объединяет столбцы базы данных first_name и last_name в один столбец с названием name. Инструментальные средства типичной интегрированной персистентной Java-среды не помогут администратору базы данных, поскольку они работают только с одной частью проблемы - изменениями в схеме. Когда вы делаете такое изменение схемы, вам необходимо также работать с существующими данными. Когда приходит время развернуть новую версию этого гипотетического приложения, администратор базы данных обычно должен вручную создать SQL-сценарии для выполнения следующих операций:

  • Создать новый столбец с названием name.
  • Извлечь данные из first_name и last_name и поместить в новый столбец.
  • Удалить столбцы first_name и last_name.

Если версия кода, вызывающая изменение схемы, по какой-либо причине не правильна, изменения часто должны отменяться (roll back) вручную. Лишь не многие разработчики практикуют интегрирование и автоматизацию изменений модели, схемы и данных.


Основы Rails-миграций

В Rails все изменения схемы, включая начальное создание схемы, происходят в миграции (migration). Каждое изменение в схеме базы данных имеет свой собственный объект migration, инкапсулирующий движение "вверх" (up) и "вниз" (down). В листинге 1 показана пустая миграция:

Листинг 1. Пустая миграция
class EmptyMigration < ActiveRecord::Migration
  def self.up
  end

  def self.down
  end
end

Я скоро покажу вам, как активировать миграцию, но пока взгляните на структуру миграции в листинге 1. В методе up этой миграции вы должны поместить весь код, необходимый для выполнения одного логического изменения базы данных. Вы должны также зафиксировать любое изменение для возможной отмены любых изменений схемы. Эти изменения базы данных могут включать:

Инкапсулируя up и down, инструментальные средства Rails-разработки и рабочие программы могут автоматизировать процесс развертывания и отмены любого изменения, затрагивающего персистентные объектные модели.

  • Добавление и удаление любых новых таблиц.
  • Добавление и удаление любых новых столбцов.
  • Изменение базы данных другими способами, включая добавление, удаление или изменение индексов или других ограничений.
  • Изменение данных.

Разрешая изменения данных, миграции значительно облегчают синхронизацию изменений в данных и схеме, которые часто происходят одновременно. Например, вы можете добавить новую таблицу поиска (lookup table), связывающую каждый штат и его двузначный ZIP-код. Внутри миграции вы можете заполнить таблицу базы данных, возможно, активизируя SQL-сценарий или загружая константы. Если ваши миграции корректны, каждая из них оставляет вашу базу данных в непротиворечивом состоянии без ручного вмешательства.

Имя файла для каждой миграции начинается с уникального номера. Это соглашение позволяет Rails поддерживать строгий порядок миграций. Используя данную стратегию, вы можете перемещаться вверх и вниз в любое логическое состояние схемы базы данных.


Использование миграций

Для использования миграций вам нужен только Rails-проект и база данных. Если вы хотите следовать по коду, установите менеджер реляционной базы данных (Ruby и Rails версии 1.1 или старше). Вы готовы начать работать. Выполните следующие действия для создания вашего Rails-проекта, использующего базу данных:

  1. Создайте Rails-проект с названием blog, введя rails blog.
  2. Создайте базу данных с названием blog_development. Используя MySQL, я просто ввел команду create database blog_development в командной строке MySQL.
  3. Настройте базу данных, как указано в config/database.yml, добавив ваш ID и пароль к базе данных.

Для того чтобы увидеть, как работает нумерация, сгенерируйте миграцию:

  1. Из каталога blog введите команду ruby script/generate migration create_blog. Если вы работаете в Unix, то можете опустить ruby. Именно так я и буду делать с этого момента.
  2. Введите команду script/generate migration create_user. Взгляните на файлы в blog/db/migrate. Вы увидите два последовательно пронумерованных файла. Этими номерами управляет генератор миграции.
  3. Удалите миграцию с названием 001_create_blog.rb и создайте ее снова при помощи команды script/generate migration create_blog. Вы обнаружите, что новая миграция создалась как 003_create_blog.rb (см. листинг 2):
Листинг 2. Генерирование миграций
> cd blog
> script/generate migration create_blog
      create  db/migrate
      create  db/migrate/001_create_blog.rb
> script/generate migration create_user
      exists  db/migrate
      create  db/migrate/002_create_user.rb
> ls db/migrate/  
001_create_blog.rb      002_create_user.rb
> rm db/migrate/001_create_blog.rb 
> script/generate migration create_blog
      exists  db/migrate
      create  db/migrate/003_create_blog.rb
> ls db/migrate/
002_create_user.rb      003_create_blog.rb

Вы можете увидеть числовой префикс для каждой миграции. Любая новая миграция получает значение увеличенного на единицу максимального префикса, имеющегося в каталоге. Такая стратегия гарантирует возможность повторного генерирования миграций (и последовательного выполнения) в нужном порядке. Любая миграция, зависящая от содержимого других миграций (например, при добавлении столбца к таблице, созданной другой миграцией), остается непротиворечивой. Механизм нумерации является простым, интуитивным и непротиворечивым.

Чтобы увидеть, как работают миграции в базе данных, удалите все миграции, имеющиеся в каталоге db/migrations. Сгенерируйте объект модели для Article и пустую миграцию, как, например, в листинге 1, выполнив команду script/generate model Article. Обратите внимание на то, что Rails генерирует ваши объекты модели и миграции для каждой статьи. Измените db/migrate/001_create_articles.rb так, как показано в листинге 3:

Листинг 3. Миграция для CreateArticles
class CreateArticles < ActiveRecord::Migration
  def self.up
    create_table :articles do |t|
      t.column :name, :string, :limit => 80
      t.column :author, :string, :limit => 40
      t.column :body, :text
      t.column :created_on, :datetime
    end
  end

  def self.down
    drop_table :articles
  end
end

Миграция вверх и вниз

Для того чтобы увидеть, что на самом деле делает миграция, просто запустите ее и посмотрите на базу данных. Из каталога blog выполните команду rake migrate. rake - это Ruby-эквивалент программы make платформы C или программы ant платформы Java. migrate - это одно задание rake.

Затем посмотрите на таблицы вашей базы данных. В MySQL просто перейдите в командную строку mysql>, выполните команды blog_development; и show tables; (см. листинг 4):

Листинг 4. Таблица schema_info, созданная Rails-миграциями
mysql> show tables;
+----------------------------+
| Tables_in_blog_development |
+----------------------------+
| articles                   |
| schema_info                |
+----------------------------+
2 rows in set (0.00 sec)

mysql> select * from schema_info;
+---------+
| version |
+---------+
|       1 |
+---------+
1 row in set (0.00 sec)

Обратите внимание на вторую таблицу schema_info. Моя миграция указывала таблицу articles, но команда rake migrate создала schema_info автоматически. Выполните команду select * from schema_info.

При выполнении команды rake migrate без параметров вы указываете Rails выполнить все миграции, которые должны быть применены. Rails выполняет следующее:

  • Создает таблицу schema_info, если она не существует.
  • Вставляет строку в schema_info со значением 0, если строки отсутствуют.
  • Выполняет методы up всех миграций, имеющих номер, больший текущей миграции. rake определяет номер текущей миграции, читая значение столбца version в таблице schema_info. rake выполняет методы up миграций в порядке возрастания номеров, а методы down - в обратном порядке.

Для миграции "вниз" просто выполните команду rake migrate с номером версии. При этом данные могут быть удалены, поэтому будьте внимательны. Некоторые операции, такие как удаление таблиц или столбцов, тоже удаляют данные. В листинге 5 показаны результаты миграции "вниз", а затем назад "вверх". Вы можете увидеть, что schema_info четко отслеживает номер текущей версии. Такой подход отлично работает, позволяя вам плавно перемещаться по схемам, которые представляют различные стадии процесса разработки.

Листинг 5. Миграция вниз
> rake migrate VERSION=0
(in /Users/batate/rails/blog)
== CreateArticles: reverting ==================================================
-- drop_table(:articles)
   -> 0.1320s
== CreateArticles: reverted (0.1322s) =========================================

> mysql -u root blog_development;
mysql> show tables;
+----------------------------+
| Tables_in_blog_development |
+----------------------------+
| schema_info                |
+----------------------------+
1 row in set (0.00 sec)

mysql> select * from schema_info;
+---------+
| version |
+---------+
|       0 |
+---------+
1 row in set (0.00 sec)

mysql> exit
Bye
> rake migrate
(in /Users/batate/rails/blog)
== CreateArticles: migrating ==================================================
-- create_table(:articles)
   -> 0.0879s
== CreateArticles: migrated (0.0881s) =========================================

> mysql -u root blog_development;
mysql> select * from schema_info;
+---------+
| version |
+---------+
|       1 |
+---------+
1 row in set (0.00 sec)

Теперь давайте откроем саму таблицу. Посмотрите опять на листинг 3 и определение таблицы. В MySQL вы можете выполнить команду show create table articles;, которая отображает информацию, показанную в листинге 6:

Листинг 6. Определение таблицы для articles
mysql> show create table articles;
+----------+...-----------------+
| Table    | Create Table |
+----------+...-----------------+
| articles | CREATE TABLE 'articles' (
  'id' int(11) NOT NULL auto_increment,
  'name' varchar(80) default NULL,
  'author' varchar(40) default NULL,
  'body' text,
  'created_on' datetime default NULL,
  PRIMARY KEY  ('id')
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
+----------+...-----------------+
1 row in set (0.00 sec)

Вы можете увидеть, что большая часть этого определения таблицы пришла непосредственно из миграции. Одним из ключевых преимуществ Rails-миграций является то, что вам не нужно напрямую использовать синтаксис SQL для создания ваших таблиц. Вы обрабатываете каждое изменение схемы в Ruby, поэтому получаемый SQL зависит от базы данных. Но обратите внимание на столбец id. Хотя вы не указывали этот столбец, Rails-миграция все равно его создала за вас с параметрами auto_increment и NOT NULL. Столбец id с этим конкретным определением следует соглашению Rails для столбца с идентификатором. Если бы вы хотели создать этот столбец без id, ваша миграция просто добавила бы параметр :id, как, например, в листинге 7:

Листинг 7. Создание таблицы без столбца id
  def up
    create_table :articles, :id => false do |t| 
      ...
    end
  end

Вы детально исследовали одиночную миграцию, но все еще не сделали ни одного изменения в схеме. Настало время создать еще одну таблицу, на этот раз для комментариев. Сгенерируйте модель с названием Comment, выполнив команду script/generate model Comment. Измените полученную миграцию в db/migrate/002_create_comments.rb так, как показано в листинге 8. Вам понадобится новая таблица с парой столбцов, и вы также воспользуетесь преимуществом способности Rails добавлять не пустые столбцы и значения по умолчанию.

Листинг 8. Вторая миграция, для комментариев
class CreateComments < ActiveRecord::Migration
  def self.up
    create_table :comments do |t|
      t.column :name, :string, :limit => 40, :null => false
      t.column :body, :text
      t.column :author, :string, :limit => 40, :default => 'Anonymous coward'
      t.column :article_id, :integer
    end
  end

  def self.down
    drop_table :comments
  end
end

Выполните эту миграцию. Если у вас при выполнении миграции возникает ошибка, просто вспомните, как работает миграция. Вы должны проверить значение строки в schema_info и посмотреть на состояние базы данных. Возможно, вам придется удалить некоторые таблицы вручную или изменить значение строки в schema_info после исправления вашего кода. Помните, никаких чудес не происходит. Rails выполняет методы up всех миграций, которые еще не запускались. Если вы добавляете уже существующую таблицу или столбец, операция потерпит неудачу, поэтому нужно проверить, что ваша миграция находится в непротиворечивом состоянии. А сейчас выполните команду rake migrate. В листинге 9 показан результат:

Листинг 9. Выполнение второй миграции
> rake migrate(in /Users/batate/rails/blog)
== CreateComments: migrating ==================================================
-- create_table(:comments)
   -> 0.0700s
== CreateComments: migrated (0.0702s) =========================================

> mysql -u root blog_development;
mysql> select * from schema_info;
+---------+
| version |
+---------+
|       2 |
+---------+
1 row in set (0.00 sec)

Миграции могут обрабатывать различные типы изменений схем. Вы можете добавлять и удалять индексы, изменять таблицы, удаляя, переименовывая или добавляя столбцы, и даже выполнить SQL-выражение при необходимости. Вы можете выполнить в миграции все, что можете сделать в SQL. Rails имеет обертки (wrappers) для большинства обычных операций, включая:

  • Создание таблицы (create_table)
  • Удаление таблицы (drop_table)
  • Добавление столбца к таблице (add_column)
  • Удаление столбца из таблицы (remove_column)
  • Переименование столбца (rename_column)
  • Изменение столбца (change_column)
  • Создание индекса (create_index)
  • Удаление индекса (drop_index)

Некоторые миграции изменяют более одного столбца для консолидации одного логического изменения в базе данных. Представьте себе миграцию, которая добавляла бы блог верхнего уровня со статьями, принадлежащими блогу. Вам потребовалось бы создать новую таблицу, а также добавить новый столбец, представляющий внешний ключ к каждой статье для указания блога. В листинге 10 показана полная миграция. Вы можете выполнить ее, введя команду rake migrate.

Листинг 10. Миграция, создающая таблицу и добавляющая столбец
class CreateBlogs < ActiveRecord::Migration
  def self.up
    create_table :blogs do |t|
      t.column :name, :string, :limit => 40;
    end
    add_column "articles", "blog_id", :integer
  end

  def self.down
    drop_table :blogs
    remove_column "articles", "blog_id"
  end
end

И данные тоже

Вы можете сделать в миграции все, что можете сделать в SQL.

До сих пор я рассматривал только изменения в схеме, но изменения в данных тоже важны. Некоторые изменения в базе данных требуют изменения данных вместе с изменениями схемы, а некоторые из этих изменений данных требуют логических изменений. Допустим, вы хотите создать новый комментарий в каждой статье блога для указания того, что статья открыта для комментариев. Реализуя изменение после того, как ваш блог уже был открыт, вы хотите добавить новый комментарий только в те статьи, которые еще не имеют комментариев. Вы можете легко сделать это изменение в миграции, поскольку миграция имеет доступ к объектам модели и может принимать логические решения на основе состояния модели. Выполните команду script/generate migration add_open_for_comments. Вы должны изменить comment для записи отношения belongs_to и записать новую миграцию. В листинге 11 показаны новый объект модели и новая миграция:

Листинг 11. Объект модели и новая миграция
class AddOpenForComments < ActiveRecord::Migration
  def self.up
    Article.find_all.each do |article|
      if article.comments.size == 0
        Comment.new do |comment|
          comment.name = 'Welcome.'
          comment.body = "Article '#{article.name}' is open for comments."
          article.comments << comment
          comment.save
          article.save
        end
      end
    end
  end

  def self.down
  end
end

Для миграции, показанной в листинге 11, вы принимаете тактическое решение. Вы считаете, что ваши пользователи не хотели бы видеть исчезновение приветственных сообщений после того, как они были добавлены, поэтому решаете не удалять какие-либо записи в миграции "вниз". Способность изменять данные внутри миграции является чрезвычайно ценной. Вы можете синхронизировать изменения в данных и схеме. Вы можете также выполнить изменения данных, которые затрагивают логические операции в ваших объектах модели.

Я показал вам большую часть того, что доступно в миграциях. Вы имеете в своем распоряжении и несколько других инструментальных средств. Если вы хотите начать использовать миграции с существующей базой данных, то можете сделать снимок вашей существующей схемы при помощи команды rake schema_dump. Это задание rake создает Ruby-схему с корректным синтаксисом миграций в db/schema.rb. Вы можете затем сгенерировать миграцию и скопировать схему, которую вы выгрузили в миграцию (см. раздел "Ресурсы" для получения более подробной информации). Я также не рассказал о тестовых средствах, которые могут быть полезны в настройке тестовых данных или наполнении базы данных. Более подробную информацию вы можете получить в одной из моих более ранних статьей серии "Пересекая границы", посвященной модульным тестам.


Окончательное сравнение

Миграции в Java-программировании не такие цельные. Некоторые продукты используют отдельные решения некоторых проблем миграций схем, но без систематического подхода для координации изменений в схеме (и "вверх", и "вниз") работа с изменениями в данных и модели объекта может быть трудной задачей. Rails-решения обладают следующими основными преимуществами:

  • Rails-миграции имеют свойство DRY (don't repeat yourself - не повторяй себя). В Rails вы указываете определение каждого столбца только один раз - в миграции. Некоторые схемы отображения требуют от вас указания таблицы в шести местах: в схеме, в методе getter, в методе setter, в переменной экземпляра модели, в отображении "из" и в отображении "в".
  • Rails-миграции разрешают миграцию данных, так же как и миграцию схемы.
  • Rails-миграции позволяют вам использовать логику модели с миграцией ваших данных, где SQL-сценарии этого делать не могут.
  • Rails-миграции независимы от базы данных, а SQL-сценарии - нет.
  • Rails-миграции позволяют выполнять прямые SQL-операции для неподдерживаемых расширений (таких как встроенные процедуры или ограничения), тогда как некоторые ORM-системы отображения - нет.

Судя по всем этим преимуществам, можно было бы ожидать использования сложного кода, но на самом деле миграции очень просты. Они имеют понятные имена и номера версий. Каждая миграция имеет методы up и down. Наконец, задание rake координирует их выполнение в определенном порядке. Эта простая стратегия тоже является революционной. Идея выражать каждое изменение схемы не в модели, а как отдельную миграцию, является сколь элегантной, столь и эффективной. Но самое интересное заключается в том, что эти идеи совершенно не зависят от языка программирования. Если вы создаете новую интегрированную Java-среду, было бы отличным решением реализовать миграции.

В следующей статье этой серии вы узнаете о новой интегрированной среде, позволяющей использовать Ajax и Web-службы, и которая использует все преимущества метапрограммирования в Rails. А пока будьте готовы к восприятию новых идей и продолжайте пересекать границы.

Ресурсы

Научиться

  • Оригинал статьи "Crossing borders: Rails migrations".
  • За пределами Java (Брюс Тэйт (Bruce Tate), O'Reilly, 2005): Книга автора о подъеме и становлении языка программирования Java, о технологиях, которые могут бросить вызов Java-платформе в некоторых областях.
  • "Обзор книги: Agile Web-разработка с Rails" (Даррен Торпэй (Darren Torpey), developerWorks, May май 2005): Обзор книги, которая углубляет понимание Rails и логическое обоснование agile-подходов к разработке.
  • Освоение миграций: Rails Wiki имеет хороший обзор миграций и самую свежую информацию, находящуюся вне исходного кода.
  • От Java к Ruby: Что должен знать каждый менеджер (Брюс Тэйт (Bruce Tate), Pragmatic Bookshelf, 2006): Книга автора о том, когда и где имеет смысл перейти от Java-программирования к Ruby on Rails, и как это сделать.
  • Программирование Ruby (Дэйв Томас (Dave Thomas) и др., Pragmatic Bookshelf, 2005): Популярная книга по программированию в Ruby.
  • Зона Java-технологий: Сотни статей по каждому аспекту Java-программирования.

Получить продукты и технологии

  • Ruby on Rails: Загрузите интегрированную Web-среду с открытым исходным кодом Ruby on Rails.
  • Ruby: Загрузите Ruby с Web-сайта проекта.

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=230476
ArticleTitle=Пересекая границы: Rails-миграции
publish-date=06132007