2011年7月13日水曜日

Ruby Mix-inのタイミング(覚書)

Railsのプラグインを作成中に遭遇した問題を覚書にしておきます。

t.timestampsのような、任意の複数のカラムをまとめて作成する
処理を書いてしていました。

テーブルの新規作成時は
ActiveRecord::ConnectionAdapters::TableDefinition クラスに
独自のメソッドを追加すれば、db:migrate するときにカラムが追加されます。
これは問題ありません。

テーブル定義を変更する場合、例えばtimestampsは
ActiveRecord::ConnectionAdapters::SchemaStatements モジュールに実装されている
add_timestamps メソッドを呼び出しています。

ただし、この時のレシーバのクラスは
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
でした。(DBはPostgreSQLを使用しているので)

これと同じ処理(メソッド)を、上記のSchemaStatementsに追加したのですが、
メソッドがない・・・method_missing とおこられてしまった・・・

継承関係は下記の通り
>> ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.ancestors
=> [ActiveRecord::ConnectionAdapters::PostgreSQLAdapter, ActiveRecord::ConnectionAdapters::AbstractAdapter, ActiveSupport::Callbacks, ActiveRecord::ConnectionAdapters::QueryCache, ActiveRecord::ConnectionAdapters::DatabaseLimits, ActiveRecord::ConnectionAdapters::Quoting, ActiveRecord::ConnectionAdapters::DatabaseStatements, ActiveRecord::ConnectionAdapters::SchemaStatements, Object, JSON::Ext::Generator::GeneratorMethods::Object, ActiveSupport::Dependencies::Loadable, Base64::Deprecated, Base64, Kernel]



試しに以下のようなプログラムを書いてみると・・・

#モジュールを2つ用意
>> module A
>> def a
>>  p "a"
>> end
>> end
=> nil
>> module B
>> def b
>>  p "b"
>> end
>> end
=> nil

#クラスを1つ用意 モジュールAをインクルード
>> class C
>> include A
>> end

>> c = C.new
=> #
>> c.a
"a"
=> nil
>> c.b
NoMethodError: undefined method `b' for #
 from (irb):39

#ここまでは特に問題なし

# モジュールAにモジュールBをインクルード
>> A.class_eval do
?> include B
>> end
=> A
>> A.instance_methods
=> ["b", "a"]

#Aのインスタンスメソッドに b が追加されました。


>> C.instance_methods.grep /^a/
=> ["a", "acts_like?", "as_json"]
>> C.instance_methods.grep /^b/
=> ["blank?", "breakpoint", "b64encode"]
#ところが、既にモジュールAをMix-inしてしまっていた クラスCには
メソッドb がありません・・・

>> class C
>> include A
>> end
=> C
>> C.instance_methods.grep /^b/
=> ["b", "blank?", "breakpoint", "b64encode"]

#あらためてモジュールAをMix-inすると、メソッドbが追加されました。
#Mix-inはインクルードするタイミングによって、期待したメソッドが追加されない
可能性があるようです。

ちなみに、Rubyのバージョンは 1.8.7 です。
・・・さて、どうしたものか・・・

2011年6月9日木曜日

Rails:ファイルの添付の覚書(DBへではなく、ファイルを保存)その2

前回はプラグインを使わず、ファイルアップロードを行いました。

今回は、carrierwave を利用した場合の例です。

まず、carrierwave をインストールします。
尚、Railsのバージョンは2.3.11です。
現時点のcarrierwaveの最新バージョンは 0.5.4 ですが、こちらはRails3用のようです。
以下の処理でインストールされたのは 0.4.10 でした。

#{RAILS_ROOT}/Gemfile
source :rubygems

gem 'carrierwave', '~>0.4.0'

$ bundle
Fetching source index for http://rubygems.org/
Installing carrierwave (0.4.10) 
Using bundler (1.0.10) 
Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.

少々待ちましたが、インストールされました。

$ ruby script/generate uploader attachment

app/uploaders/attachment_uploader.rb
が作成されます。

store_dirメソッドが自動生成されています。
こちらがファイルのアップロード先になります。
今回修正しました。

ちなみにSettingsは共通パラメータを設定するのに便利なsettingslogicをプラグインを導入し、外部のyamlファイルの設定を読み込めるようにしたものです。

 attr_accessor :sub_dir # 追加

  storage :file

  def store_dir
    #"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
    Settings.upload_dir + "/#{sub_dir}"  # 追加
  end

Controller

私の場合、AttachmentUploaderクラスを利用するファイルで
carrierwaveをrequireする必要がありました。

require 'carrierwave'

  :

  uploader = AttachmentUploader.new
  uploader.sub_dir = @message.id.to_s
  uploader.store!(upload_file)

  :

Rails:ファイルの添付の覚書(DBへではなく、ファイルを保存)その1

Railsでファイルのアップロードの覚書

プラグイン(attacument_fu,paperclipedなど)を使わない方法

下記のコード例は、1画面で複数ファイルをアップロードする場合の例です。

VIEW

_from.html.erb



<% form_for @message , :url => { :action => action }, :html => {  :multipart => true, :id => 'message_form', :name => :message_form } do |f| %>
     :
      <% @message.attachments.each do |attach| %>
        <% if attach.id %>
          <% f.fields_for :attachments, attach do |attachment_form| %>
            <%= attachment_form.hidden_field :id %>
            <%= attachment_form.text_field :name %>
            Delete:<%= attachment_form.check_box :_destroy %><br />
          <% end %>
        <% else %>
          <% f.fields_for :attachments, attach do |attachment_form| %>
            <p>
            <%= attachment_form.hidden_field :id %>
            <%= attachment_form.label :path %>
            <%= attachment_form.file_field(:upload_data) %>
            </p>
          <% end %>
        <% end %>
      <% end %>
      :


Controller

    upload_files ||= []
    params[:message][:attachments_attributes].values.each do |attach|
      upload_file = attach[:upload_data]

      if upload_file
        upload_files << upload_file
      end

    end if params[:message][:attachments_attributes]

    respond_to do |format|
      if @message.save

        Dir.mkdir("#{Settings.upload_dir}#{@message.id}") if upload_files.count > 0
        upload_files.each do |upload_file|
          File.open("#{Settings.upload_dir}#{@message.id}/#{upload_file.original_filename}", "w+") { |f| f.write upload_file.read }
      end

2011年5月14日土曜日

herokuを使ってみる(覚書)

herokuとgit 初トライです。

Mackportsのアップデート
$ sudo port -d selfupdate

gitのインストール(Macの場合です)

$ sudo port install git-core

バージョン確認
$ git --version

Railsアプリのディレクトリに移動
$ cd

$ git init
カレントディレクトリに .git ディレクトリが作成される

ソースをコミット
$ git add .
$ git commit -am "first commit"

herokuのサイトでサインアップ
届いたメールでアクティベーション実施

heroku のインストール
$ sudo gem install heroku

heroku 上にアプリ作成
$ heroku create
※ちなみにアプリケーション名にアンダーバー _ は使えないようです

既に秘密鍵を持っていれば
$ heroku keys:add
で鍵を登録

なければ事前に
$ ssh-keygen -t ras
を実行しておく

git に remote を登録
$ git remote add heroku git@heroku.com:.git

これで、次のコマンドで heroku に push できるようになるはず

$ git push heroku masuter

$ heroku db:migrate
$ heroku open

本家のここを見ればほぼ分かると思います
http://devcenter.heroku.com/articles/quickstart

以上

2011年5月5日木曜日

Railsで簡易予約システムを考える(3)

今回はセッションをDBで管理するための設定を行います。

$ rake db:sessions:create

      exists  db/migrate
      create  db/migrate/20110505041507_create_sessions.rb

いくつか設定を変更する必要があります。今回Railsのバージョン 2.3.11 で行っていますので、古いバージョンの場合とファイル名などが一部異なる可能性があります。

config/enviroment.rbのRAILS_GEM_VERSIONパラメータが指定のバージョンになっているか。
(いくつかの古いバージョンの場合、バグなのか設定方法の違いなのかわかりませんが、
 CSRFの対策がされている箇所でエラーが発生することがあります。)

RAILS_GEM_VERSION = '2.3.11' unless defined? RAILS_GEM_VERSION

app/contorollers/application_controller.rbのprotect_from_forgery に
コメントがあれば外します。(私の環境では、はじめからコメントされていませんでした)
protect_from_forgery # See ActionController::RequestForgeryProtection for details

続いて、config/initializers/session_store.rbの以下の箇所のコメントを外します。

ActionController::Base.session_store = :active_record_store

では、はじめに実行したコマンドで、sessionsテーブルのマイグレーションファイルができていますので、rakeタスクを実行します。

$ rake db:migrate

PostgreSQLの場合、session_id列がstring型で作成されますが、デフォルトだと255byteとなるため、セッションのレコード作成時に、長さが足らず、エラーとなることがあるようです。
私の場合、手っ取り早くstring型をtextにしてしまいました。
(※その後、string型で、 :limit => 1024 に変更しました)

これで、セッション情報をデータベースで管理するようになるそうです。

2011年4月17日日曜日

Railsで簡易予約システムを考える(2)

前回に続いて、簡易予約システムを作成していきます。

予約状況がわかる画面を作成します。
予約情報はModel bookingに対応しますが、自動生成された画面をこのまま使うのはユーザビリティの面で少々難がありそうです。

まずは一週間の予約状況が一覧で確認できるような画面をイメージしてみます。
縦方向には、予約対象が並び、横方向(左から右へ)に今日から7日間分の予約状況が表示されるようにしてみたいと思います。

画面名(コントローラ名)はweekly_infoにしてみます。

$ ruby script/generate controller weekly_info index

最後のindexは、URLの指定をcontroller名まで指定した場合、デフォルトのアクションがindexとなっているため指定しておきました。
$ ruby script/generate controller weekly_info index
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/weekly_info
      exists  test/functional/
      exists  test/unit/helpers/
      create  app/controllers/weekly_info_controller.rb
      create  test/functional/weekly_info_controller_test.rb
      create  app/helpers/weekly_info_helper.rb
   identical  test/unit/helpers/weekly_info_helper_test.rb
      create  app/views/weekly_info/index.html.erb

最後に、Viewのindex(画面)が作成されています。対応しているモデルがないので、中身はこんな感じです。

WeeklyInfo#index

Find me in app/views/weekly_info/index.html.erb


さて、いままで、ジェネレータに大分助けていただきましたが、ここからは、コーディングが必要になってしまいます。

ここで、考えが変わって、このページの名称(Controller)をweekly_info から booking_info に変更しました。
自動生成されたファイルの名称を全て変更すれば、問題ないようです。また、サーバを起動中の場合は、再起動しないと反映されないかも知れません。

それから、データベースのカレンダーのViewを作成しておきたいと思います。
おそらくいずれ何かの役に立つと思いますので。

$ ruby script/generate migration create_utility_numbers 
を実行し、マイグレーションファイルを作成します。
これを次のように編集します。

class CreateUtilityNumbers < ActiveRecord::Migration
  def self.up
    create_table :utility_numbers do |t|
      t.integer :num
    end

    (1..100).each do |i|
      UtilityNumber.create(:num => i)
    end
  end

  def self.down
    drop_table :utility_numbers
  end
end
このテーブルは1〜100までを持っているだけのテーブルです。

いよいよVIEWの作成ですが、今回はPostgreSQLを使用しています。
RDBMSによって、方言があるので、ここはDBによってそれぞれ用意するしかないと思います。
良い方法があれば、ぜひご教示を。

class CreateWeeklyCalendarView < ActiveRecord::Migration
  def self.up
    execute %{
      CREATE OR REPLACE VIEW weekly_carender_view AS
      select date_val as sun,
        date_val + 1 as mon,
        date_val + 2 as tue,
        date_val + 3 as wed,
        date_val + 4 as thu,
        date_val + 5 as fry,
        date_val + 6 as sat
      from (
        select to_date(y_val || m_val || d_val, 'YYYYMMDD') as date_val from
          (select to_char(num, '00') as d_val
          from utility_numbers
          where num < 32) td,
          (select to_char(num, '00') as m_val
          from utility_numbers
          where num < 13) tm,
          (select to_char(num + 1970, '0000') as y_val
          from utility_numbers) ty
      ) t
      where date_part('dow', date_val) = '0'
      order by date_val;
    }
  end

  def self.down
    execute %{
      DROP VIEW weekly_carender_view;
    }
  end
end

2011年4月10日日曜日

Railsで簡易予約システムを考える

3.11の後、しばらく更新していませんでしたが、久々にメモがわりに書いています。

Railsで簡易予約システムを考えてみます。

エンティティ

• リソース系
予約対象:item

予約対象グループ:item_group
仕様:specification
仕様カテゴリ:spec_category

予約状況:booking_status

• イベント系

予約:booking

その他、基本的なユーザ、カレンダー、料金が発生する場合は、利用料などを管理するマスタ(対照表)、承認が必要なら・・・など、考えれば考えるほど出てきてしまうのですが、簡易予約システムという(言い訳)ことで・・・とりあえずここ辺からスタート。

リソース系(マスタ)が多いので、すべてscaffoldで一旦作成します。

その前にアプリケーション名はbookingとしてみます。

プロジェクトの作成

$ rails --database=postgresql booking

データベースの作成
今回、PostgreSQLを利用しています。

database.yml のユーザ、パスワード等を指定し、

$ rake db:create

を実行します。

続いてscaffoldでひと通りジェネレート
$ ruby script/generate scaffold item name:string item_group_id:integer spec_id01:integer spec_id02:integer description:text deleted:boolean ins_user:string upd_user:string
$ ruby script/generate scaffold  item_group name:string spec_category_id01:integer spec_category_id02:integer description:text deleted:boolean ins_user:string upd_user:string
$ ruby script/generate scaffold specification name:string spec_category_id:integer deleted:boolean ins_user:string upd_user:string
$ ruby script/generate scaffold spec_category name:string description:text deleted:boolean ins_user:string upd_user:string
$ ruby script/generate scaffold booking_status name:string deleted:boolean ins_user:string upd_user:string
$ ruby script/generate scaffold booking user:string item_id:integer start_date:date end_date:date status:integer comment:text

デフォルトで作成されたModelにデータの関連を追加します。
主な関連は次のとおり

item [n] - [1]item_group
item_group[n] - [1]spec_category
spec_category[1] - [n]specification
item[n]-[1]specification

itemには仕様(specification)を2つまで指定でき、仕様の種類は、itemのグループによって決めたいと思います。そのため、
item[n]-[1]specification
としていても、条件で絞りこまなければならないのですが、そのへんは後で考えたいと思います。

続いて、使い勝手を少し改善しましょう。
画面にIDがそのまま出てしまうのは、人間が操作するのに厳しいので、名称が出るようにMode、View、Controllerを修正します。

Modelには、has_manyや、belongs_toを指定します。
また、itemのように1つのモデルに2つまで関連する項目を指定するときは、belongs_to にアソシエーション名をつけ、:class_name、:foreign_keyのオプションを指定します。
Viewはテキストフィールド(f.text_field)の箇所を、プルダウン(f.select)に変更します。

Model


class Item < ActiveRecord::Base
  
belongs_to :item_group
  
belongs_to :specification01,
  
  :class_name => 'Specification',
    
:foreign_key => 'spec_id01'
  ・・・

View
<=% f.select(:item_group_id, ItemGroupsController.item_group_list) %>

Controllerに追加するクラスメソッド(self. が付きます)
def self.item_group_list
    ItemGroup.find(:all).map do |item_group|
      [item_group.name, item_group.id]
    end
  end

item の一覧画面と、登録画面は次のような感じでできました。




次は予約画面の作成を行って見ます。