Игорь Олемской — практические заметки по системному администрированию Linux CentOS

Архив тега ‘Программирование’

Foreign keys для ActiveRecord (перепечатка)

Комментариев нет

Делаю очередной проект на Ruby on Rails. Как обычно, в миграциях понадобились foreign keys на уровне БД. Окинул взглядом все плагины, которые смог найти в google и на github.com. Ни один из них не умеет делать FK, используя ActiveRecord::ConnectionAdapters::Table#references и ActiveRecord::ConnectionAdapters::TableDefinition#references.

Поэтому сел и написал свой плагин: active_record_foreign_keys.

Установка:

Rails::Initializer.run do |config|
  ...
  config.gem "active_record_foreign_keys", :source => "http://gemcutter.org"
  ...
end
$ rake gems:install

Использование:

def self.up
  # create reference table
  create_table :users do |t|
  end
  # create referencing table
  create_table :a_examples do |t|
    t.references :user, :foreign_key => true
  end
  # or
  create_table :b_examples do |t|
    t.references :user, :foreign_key => { :o n_update => :cascade, :o n_delete => :restrict }
  end
  # or
  create_table :c_examples do |t|
  end
  add_foreign_key :c_examples, :user_id, :users, :id, :o n_update => :no_action, :o n_delete => :set_null
  # or change existing table
  change_table :d_examples do |t|
    t.references :user, :foreign_key => true
  end
end
def self.down
  # remove constraint
  remove_foreign_key :examples, :user_id, :users, :id
end

Плагин тестировался только под PostgreSQL, но по идее должен работать и под MySQL, и под Sqlite.

Эмуляция модели в Ruby on Rails (перепечатка)

Комментариев нет

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

Можно сэмулировать стандартную модель использую такой базовый класс-заглушку:

module ActiveRecord
  class Model
    def id; nil; end
    def new_record?; true; end
    def save; nil; end
    def save!; nil; end
    class << self
      def human_name(options = {})
        defaults = self_and_descendants_from_active_record.map do |klass|
          :"#{klass.name.underscore}"
        end
        defaults << self.name.humanize
        I18n.translate(defaults.shift, {:scope => [:activerecord, :models], :count => 1, :default => defaults}.merge(options))
      end
      def human_attribute_name(attribute_key_name, options = {})
        defaults = self_and_descendants_from_active_record.map do |klass|
          :"#{klass.name.underscore}.#{attribute_key_name}"
        end
        defaults << options[:default] if options[:default]
        defaults.flatten!
        defaults << attribute_key_name.humanize
        options[:count] ||= 1
        I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => [:activerecord, :attributes]))
      end
      def self_and_descendants_from_active_record
        klass = self
        classes = [klass]
        while klass != klass.base_class
          classes << klass = klass.superclass
        end
        classes
      rescue
        [self]
      end
    end
    def initialize(params = nil)
      unless params.nil?
        params.each do |k,v|
          self.send("#{k}=", v)
        end
      end
    end
    include ActiveRecord::Validations
  end
end

Класс псевдомодели для формы обратной связи будет выглядить так:

class Message < ActiveRecord::Model
  attr_accessor :name
  attr_accessor :email
  attr_accessor :subject
  attr_accessor :body
  validates_presence_of :name
  validates_length_of :name, :within => 2..16, :allow_blank => true
  validates_presence_of :email
  validates_length_of :email, :within => 6..32, :allow_blank => true
  validates_format_of :email, :with => Authlogic::Regex.email, :allow_blank => true
  validates_presence_of :subject
  validates_length_of :subject, :within => 2..32, :allow_blank => true
  validates_presence_of :body
  validates_length_of :body, :within => 5..4096, :allow_blank => true
  def send_to(recipient)
    # send message to recipient
  end
end

Такую псевдомодель можно использовать в шаблонах с хелпером form_for как обычную. Интернационализация сообщений об ошибках работает так же, как если бы это была обычная модель ActiveRecord::Base.

Пример использования:

class ContactsController < ApplicationController
  def new
    @message = Message.new
  end
  def create
    @message = Message.new(params[:message])
    if @message.valid?
      @message.send_to(configatron.contacts.email)
      flash[:notice] = "Сообщение успешно отправлено"
      redirect_to new_contact_path
    else
      render :action => "new"
    end
  end
end

Ruby 1.9 on Rails: несовместимость кодировок (перепечатка)

Комментариев нет

Я активно начал пробовать завести rails-приложения на ruby 1.9.1. В целом, все неплохо работает, но мелкие косяки бывают. Например, в will_paginate.

Для работы с PostgreSQL пришлось доработать гем postgres. Теперь гем компилируется как под ruby 1.8, так и под ruby 1.9. Кроме того, всем строковым данным, которые возвращает БД, навешивается кодировка из Encoding.external_encoding. Поставить доработанный гем можно командой:

sudo gem install antage-postgres --source=http://gems.github.com/

Исходники гема на гитхабе.

Но я хотел о другом написать. Об ошибке несовместимости кодировок ASCII-8BIT и UTF-8: ActionView::TemplateError (incompatible character encodings: ASCII-8BIT and UTF-8). Такая ошибка появляется при запуске Rails на ruby 1.9.1. В edge-версии rails ошибка все еще не исправлена. Для исправления нужно закинуть monkey-patch в config/initializers/.

Реализация исключений через continuations (перепечатка)

Комментариев нет

Прочитал мнение, что если язык поддерживает first-class continuations, то через них можно реализовать все остальные структуры. Не знаю, как насчет всех-всех, но систему исключений реализовать вполне можно. Вот пример на Scheme:

(let ((exception-handler (lambda (exit arg)
                           (display "Outer handler") (newline)
                           (display arg) (newline)
                           (exit #f))))
  (display "Pre-A") (newline)
  (call/cc (lambda (context)
             (let ((exception-handler (lambda (exit arg)
                                        (display "Inner handler") (newline)
                                        (display arg) (newline)
                                        (exception-handler context arg))))
               (call/cc (lambda (context)
                          (display "A") (newline)
                          (exception-handler context "Exception!")
                          (display "B") (newline))))))
  (display "Post-B") (newline))

Выхлоп программы:

Pre-A
A
Inner handler
Exception!
Outer handler
Exception!
Post-B

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

(let ((exception-handler (lambda (exit arg)
                           (display "Outer handler") (newline)
                           (display arg) (newline)
                           (exit #f))))
  (display "Pre-A") (newline)
  (call/cc (lambda (context)
             (let ((exception-handler (lambda (exit arg)
                                        (display "Inner handler") (newline)
                                        (display arg) (newline)
                                        (exit #f))))
               (call/cc (lambda (context)
                          (display "A") (newline)
                          (exception-handler context "Exception!")
                          (display "B") (newline))))))
  (display "Post-B") (newline))

Выхлоп программы:

Pre-A
A
Inner handler
Exception!
Post-B

Ну и конечно, все это можно завернуть в красивый синтаксис а-ля try/catch, используя макросы. Но суть останется та же.