Uniвсячина

понемножку о Linux и программировании

Эмуляция модели в 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/.

Я ненавижу MySQL

Я ненавижу MySQL. Сегодня на ровном месте у него сломалась таблица mysql.user. В итоге, сотни повисших коннектов в состоянии login и никаких диагностических сообщений в логах. Мне понадобился час, чтобы догадаться сделать на всякий случай REPAIR TABLE user. Я ненавижу MySQL.

Реализация исключений через 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, используя макросы. Но суть останется та же.