Ruby on Rails 2.1.x Tutorialをやってみる

Ruby on Rails 2.1.x Tutorialをやってみる。

インストールと動作確認

環境はDebian GNU/Linux Lenny。まずは、gemのアップデート。私の環境だとgemはaptでいれたのでgem1.8としてインストールされている。

% sudo gem1.8 update --system

次にRails2.1のインストール。

% sudo gem1.8 install rails

実際に動くかどうかをチェック。

% mkdir Rails2.1
% cd Rails21.
% rails demo
% cd demo
% ruby script/server

ブラウザでhttp://localhost:3000にアクセスしてみる。ちゃんと表示されて入れば動いている。

モデルの構築とMigration

チュートリアルではデータベースとしてMySQLを使い、かつ、例題用に新たなlibraryというプロジェクトを作っているがさっきつかったdemoをそのまま利用することにする。なお、Rails2.1のデータベースはデフォルトでSQLite3になっているので、別のデータベースを使いたい場合にはプロジェクト作成時に以下のようにデータベースを指定する必要があるらしい。

rails -d mysql demo

~/demo/config/database.ymlを以下の用にする(ほとんどデフォルトから変えていない)

# SQLite version 3.x
#   gem install sqlite3-ruby (not necessary on OS X Leopard)
development:
  adapter: sqlite3
  database: db/library.development.sqlite3
  timeout: 5000

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  adapter: sqlite3
  database: db/library.test.sqlite3
  timeout: 5000

production:
  adapter: sqlite3
  database: db/library.production.sqlite3
  timeout: 5000

次にモデルを構築。~/Rails2.1/demoに移動して、以下のコマンドを実行。

% ruby script/generate model Book
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/book.rb
      create  test/unit/book_test.rb
      create  test/fixtures/books.yml
      create  db/migrate
      create  db/migrate/20080806031037_create_books.rb
% ruby script/generate model Subject
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/subject.rb
      create  test/unit/subject_test.rb
      create  test/fixtures/subjects.yml
      exists  db/migrate
      create  db/migrate/20080806031046_create_subjects.rb

チュートリアル曰く「モデル作成の場合には単数形でかつ先頭の文字を大文字にすること。」

Railsにおけるモデル間(テーブル間)の関係は3種類あるとのこと。集合論でお馴染みの関係。

  • one-to-one(1対1関係)
  • one-to-many(1対多関係)
  • many-to-many(多対多関係)

この関係をhas_one, has_many, belongs_to,has_and_belongs_to_manyを使って明示する必要があるとのこと。app/model/book.rbとapp/model/subject.rbをそれぞれ以下のように編集する。

class Book < ActiveRecord::Base
  belongs_to :subject
end
class Subject < ActiveRecord::Base
  has_many :books
end

SubjectとBookの関係は1対多関係で、一つのSubjectが複数のBookを持つので「has_many :books」と複数形になるとのこと。Railsはここいら辺が難しいね。

次にデータベースにデータを格納する際の値チェックを用意する validates〜というもの。これはそれぞれのモデルの部分に書く。book.rbを以下の用に編集する。

class Book < ActiveRecord::Base
  belongs_to :subject
  validates_presence_of :title
  validates_numericality_of :price, :message=>"Error Message"
end

次にMigrationをする。データベースへの変更作業をスクリプト化しておきデータベースをそのスクリプトにしたがって管理する仕組みらしい。~/Rails2.1/demoに移動して以下のコマンドを実行する。

% ruby script/generate migration books
      exists  db/migrate
      create  db/migrate/20080806032900_books.rb
% ruby script/generate migration subjects
      exists  db/migrate
      create  db/migrate/20080806032916_subjects.rb

次にdb/migrate/20080806032900_books.rbを編集する。なお、20080806032900の部分は自動生成されるので上記コマンドを実行した環境によって違う。ここに日付が入るのはRails2.1の新機能らしい。

class Books < ActiveRecord::Migration
  def self.up
    create_table :books do |t|
      t.string :title, :limit => 32, :null => false
      t.float :price
      t.integer :subject_id
      t.text :description
      t.timestamp :created_at
    end
  end

  def self.down
    drop_table :books
  end
end

self.upでかかれている内容がMigrationをするときに実行される。Role back(前のデータベースの状態に戻す)場合にはself.downでかかれている内容が実行される。Railsのテーブルには必ずidというコラムが含まれているが、これは自動的に番号が割り振られる(auto increment)なので書く必要はないらしい。

次にdb/migrate/20080806032916_subjects.rbも以下のように編集する。

class Subjects < ActiveRecord::Migration
  def self.up
    create_table :subjects do |t|
      t.string :name
    end
    Subject.create :name => "Physics"
    Subject.create :name => "Mathematics"
    Subject.create :name => "Chemistry"
    Subject.create :name => "Pshchology"
    Subject.create :name => "Geography"
  end

  def self.down
    drop_table :subjects
  end
end

「Subject.create :name => "Physics"」という部分でレコードを追加している。テストデータはこうやって組み込むみたい。便利。

それで、Migration実行。

% rake db:migrate
(in /home/gotoh/Ruby/Rails2.1/demo)
== 20080806031037 CreateBooks: migrating ======================================
-- create_table(:books)
   -> 0.0070s
== 20080806031037 CreateBooks: migrated (0.0073s) =============================

== 20080806031046 CreateSubjects: migrating ===================================
-- create_table(:subjects)
   -> 0.0230s
== 20080806031046 CreateSubjects: migrated (0.0232s) ==========================

== 20080806032900 Books: migrating ============================================
-- create_table(:books)
rake aborted!
SQLite3::SQLException: table "books" already exists: CREATE TABLE "books" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar(32) NOT NULL, "price" float DEFAULT NULL NULL, "subject_id" integer DEFAULT NULL NULL, "description" text DEFAULT NULL NULL, "created_at" datetime DEFAULT NULL NULL) 

(See full trace by running task with --trace)

はい、ひっかかりました。理由は、20080806031037_create_books.rbがModel作ったときに存在しているから。20080806031037_create_books.rbの中身はこれ。

class CreateBooks < ActiveRecord::Migration
  def self.up
    create_table :books do |t|

      t.timestamps
    end
  end

  def self.down
    drop_table :books
  end
end

〜_create_books.rbと〜_create_subjects.rbに集約する。その後、一度初期状態までrolebackして再度Migration

% rake db:migrate VERSION=0
(in /home/gotoh/Ruby/Rails2.1/demo)
== 20080806031046 CreateSubjects: reverting ===================================
-- drop_table(:subjects)
   -> 0.0074s
== 20080806031046 CreateSubjects: reverted (0.0076s) ==========================

== 20080806031037 CreateBooks: reverting ======================================
-- drop_table(:books)
   -> 0.0060s
== 20080806031037 CreateBooks: reverted (0.0062s) =============================

% rake db:migrate 
(in /home/gotoh/Ruby/Rails2.1/demo)
== 20080806031037 CreateBooks: migrating ======================================
-- create_table(:books)
   -> 0.1617s
== 20080806031037 CreateBooks: migrated (0.1620s) =============================

== 20080806031046 CreateSubjects: migrating ===================================
-- create_table(:subjects)
   -> 0.0074s
== 20080806031046 CreateSubjects: migrated (0.1147s) ==========================

特に指定しなければconfig/database.ymlのdevelopment用のデータベースに処理が行われる。データベースを指定してMigrationする場合には、環境変数RAILS_ENVにproduction, test, developmentをそれぞれ指定する。Cシェル系ならばsetコマンドを使う。

% set RAILS_ENV=production

bash系ならば、exportコマンドを使う

% export RAILS_ENV=production

SQLiteの場合データベースはdb以下に作られる。今回の場合はこんな感じのデータベースになった。

% sqlite3 db/library.development.sqlite3
SQLite version 3.5.9
Enter ".help" for instructions
sqlite> .table
books              schema_migrations  subjects         
sqlite> .schema books
CREATE TABLE "books" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar(32) NOT NULL, "price" float DEFAULT NULL NULL, "subject_id" integer DEFAULT NULL NULL, "description" text DEFAULT NULL NULL, "created_at" datetime DEFAULT NULL NULL);
sqlite> .schema subjects
CREATE TABLE "subjects" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255) DEFAULT NULL NULL);
sqlite> .exit

コントローラーの作成

次にContorollerの作成に移る。

% ruby script/generate controller Book
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/book
      exists  test/functional/
      create  app/controllers/book_controller.rb
      create  test/functional/book_controller_test.rb
      create  app/helpers/book_helper.rb

app/controllers/book_controller.rbを以下のように編集する。ここの説明はチュートリアルを参照。

class BookController < ApplicationController
   def list
     @books = Book.find(:all)
   end
   def show
     @book = Book.find(params[:id])
   end
   def new
     @book = Book.new
     @subjects = Subject.find(:all)
   end
   def create
     @book = Book.new(params[:book])
     if @book.save
       redirect_to :action => 'list'
     else
       @subjects = Subject.find(:all)
       render :action => 'new'
     end

   end
   def edit
     @book = Book.find(params[:id])
     @subjects = Subject.find(:all)
   end
   def update
     @book = Book.find(params[:id])
     if @book.update_attributes(params[:book])
       redirect_to :action => 'show', :id => @book
     else
       @subjects = Subject.find(:all)
       render :action => 'edit'
     end
   end
   def delete
     Book.find(params[:id]).destroy
     redirect_to :action => 'list'
   end
   def show_subjects
     @subject = Subject.find(params[:id])
   end
end

よく分からないのは、インスタンス変数とローカル変数の使い分け。確か、View部分でインスタンス変数を参照できるのだけど、ここの仕組みがいまいち理解できない。あとはハッシュへのアクセスの仕方。文字列を使うときと:idのようにコロンを使ったときの区別がわからない。だんだん、Rails 0.9ぐらいでプログラム書いていたときの疑問点を思い出してきた。

ビューの作成

次に各メソッドごとにViewを用意する。Viewファイルは「メソッド名.html.erb」とするらしい。まず、listメソッドのViewをapp/view/book/list.html.erbとして用意する。中身は以下のとおり。

<% if @books.blank? %>
<p>There are not any books currently in the system.</p>
<% else %>
<p>These are the current books in our system</p>
<ul id="books">
<% @books.each do |c| %>
<li><%= link_to c.title, {:action => 'show', :id => c.id} -%></li>
<% end %>
</ul>
<% end %>
<p><%= link_to "Add new Book", {:action => 'new' }%></p>

ここまでで、http://localhost:3000/book/listにアクセスすると、newメソッドへのリンクが張られたページが閲覧でき、newメソッド部分(http://localhost:3000/book/new)で本を登録するとlistメソッド部分に登録した本が表示される。さすがRails。こういうのを見れるようにするのは早い。

次にshowメソッドのViewをapp/view/book/show.html.erbとして用意する。中身は以下のとおり。

<h1><%= @book.title %></h1>
<p><strong>Price: </strong> $<%= @book.price %><br />
<strong>Subject :</strong> <%= @book.subject.name %><br />
<strong>Created Date:</strong> <%= @book.created_at %><br />
</p>
<p><%= @book.description %></p>
<hr />
<%= link_to 'Back', {:action => 'list'} %>

@book.subject.nameというので、subjectsテーブルのデータを一緒に引っ張ってこれるのはとても便利。perlDBIx::Classでもできるらしいのだけど、ハッシュでアクセスするよりもオブジェクトのメソッド呼び出し(アクセッサーっていうんだっけ?)で呼び出すのは綺麗なような気がする。ここはRailsというかActionRecordの好きなところ。

次はapp/view/book/edit.html.erb。中身は以下のとおり。

<h1>Edit Book Detail</h1>
<% form_tag :action => 'update', :id => @book do %>
<p><label for="book_title">Title</label>:
<%= text_field 'book', 'title' %></p>
<p><label for="book_price">Price</label>:
<%= text_field 'book', 'price' %></p>
<p><label for="book_subject">Subject</label>:
<%= collection_select(:book, :subject_id,
                         @subjects, :id, :name) %></p>
<p><label for="book_description">Description</label><br/>
<%= text_area 'book', 'description' %></p>
<%= submit_tag "Save changes" %>
<% end %>
<%= link_to 'Back', {:action => 'list' } %>

new.html.erbとの違いは、form_tagの飛ばし先がupdateメソッドであることと、デフォルトの値として:id => @bookが指定されていること。ここいらへんのインスタンス変数とHTMLヘルパーの連携がちょっとわかりづらい。

これにともないapp/view/book/list.html.erbを修正。

<% if @books.blank? %>
<p>There are not any books currently in the system.</p>
<% else %>
<p>These are the current books in our system</p>
<ul id="books">
<% @books.each do |c| %>
<li><%= link_to c.title, {:action => 'show', :id => c.id} -%>
<b> <%= link_to 'Edit', {:action => "edit", :id => c.id} %></b></li>
<% end %>
</ul>
<% end %>
<p><%= link_to "Add new Book", {:action => 'new' }%></p>

deleteメソッドを呼び出すために、app/view/book/list.html.erbを修正。

<% if @books.blank? %>
<p>There are not any books currently in the system.</p>
<% else %>
<p>These are the current books in our system</p>
<ul id="books">
<% @books.each do |c| %>
<li><%= link_to c.title, {:action => 'show', :id => c.id} -%>
<b> <%= link_to 'Edit', {:action => "edit", :id => c.id} %></b>
<b> <%= link_to "Delete", {:action => 'delete', :id => c.id},
:confirm => "Are you sure you want to delete this item?" %></li>
<% end %>
</ul>
<% end %>
<p><%= link_to "Add new Book", {:action => 'new' }%></p>

show_subjectsメソッドのViewをapp/view/book/show_subjects.html.erbとして用意。

<h1><%= @subject.name -%></h1>
<ul>
<% @subject.books.each do |c| %>
<li><%= link_to c.title, :action => "show", :id => c.id -%></li>
<% end %>
</ul>

そして、app/view/book/show.html.erbを修正。

<h1><%= @book.title %></h1>
<p><strong>Price: </strong> $<%= @book.price %><br />
<strong>Subject :</strong> <%= link_to @book.subject.name,
:action => "show_subjects", :id => @book.subject.id %><br />
<strong>Created Date:</strong> <%= @book.created_at %><br />
</p>
<p><%= @book.description %></p>
<hr />
<%= link_to 'Back', {:action => 'list'} %>

list.html.erbも修正。

<ul id="subjects">
<% Subject.find(:all).each do |c| %>
<li><%= link_to c.name, :action => "show_subjects",
:id => c.id %></li>
<% end %>
</ul>
<% if @books.blank? %>
<p>There are not any books currently in the system.</p>
<% else %>
<p>These are the current books in our system</p>
<ul id="books">
<% @books.each do |c| %>
<li><%= link_to c.title, {:action => 'show', :id => c.id} -%>
<b> <%= link_to 'Edit', {:action => "edit", :id => c.id} %></b>
<b> <%= link_to "Delete", {:action => 'delete', :id => c.id},
:confirm => "Are you sure you want to delete this item?" %></li>
<% end %>
</ul>
<% end %>
<p><%= link_to "Add new Book", {:action => 'new' }%></p>

レイアウトとCSS

メソッドで共通のHTMLのヘッダ部分やフッタ部分はapp/view/layoutに置くことができる。app/view/layout/standard.html.erbを作成し、中身を以下とする。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html;.
charset=iso-8859-1" />
<meta http-equiv="Content-Language" content="en-us" />
<title>Library Info System</title>
<%= stylesheet_link_tag "style" %>
</head>
<body id="library">
<div id="container">
<div id="header">
<h1>Library Info System</h1>
<h3>Library powered by Ruby on Rails</h3>
</div>
<div id="content">
<%= yield -%>
</div>
<div id="sidebar"></div>
</div>
</body>
</html>

yieldの部分にメソッドごとのViewが入る。app/controller/book_controller.rbで使用するレイアウトファイルを定義する。

class BookController < ApplicationController
  layout 'standard'
   def list
     @books = Book.find(:all)
   end
〜以下略〜

スタイルシートは~/Rails2.1/demo/public/stylesheets以下に置く。style.cssを以下の中身でこのディレクトリに置く。

body {
   font-family: Helvetica, Geneva, Arial, sans-serif;
   font-size: small;
   font-color: #000;
   background-color: #fff;
}
a:link, a:active, a:visited {
   color: #CD0000;
}
input { 
   margin-bottom: 5px;
}
p { 
   line-height: 150%;
}
div#container {
   width: 760px;
   margin: 0 auto;
}
div#header {
   text-align: center;
   padding-bottom: 15px;
}
div#content {
   float: left;
   width: 450px;
   padding: 10px;
}
div#content h3 {
   margin-top: 15px;
}
ul#books {
   list-style-type: none;
}
ul#books li {
   line-height: 140%;
}
div#sidebar {
   width: 200px;
   margin-left: 480px;
}
ul#subjects {
   width: 700px;
   text-align: center;
   padding: 5px;
   background-color: #ececec;
   border: 1px solid #ccc;
   margin-bottom: 20px;
}
ul#subjects li {
   display: inline;
   padding-left: 5px;
}

Ajax

scaffoldの項は飛ばし、Ajaxを使ってみる。

目的は、先ほど作った本のリストアプリケーションのSubjectをAjaxを用いて編集すること。まず、Subject用のコントローラーを用意する。

% ruby script/generate controller Subject
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/subject
      exists  test/functional/
      create  app/controllers/subject_controller.rb
      create  test/functional/subject_controller_test.rb
      create  app/helpers/subject_helper.rb

app/controllers/subject_controller.rbを以下のように編集。

class SubjectController < ApplicationController
   layout 'standard'
   def list
     @subjects = Subject.find(:all)
   end
   def show
     @subject = Subject.find(params[:id])
   end
   def create
     @subject = Subject.new(params[:subject])
     if @subject.save
       render :partial => 'subject', :object=> @subject
     end
   end
end

次にビューを作成する。app/view/subject/list.html.erbを以下のように用意する。

<h1>Listing Subjects</h1>
<ul id="subject_list">
<% @subjects.each do |c| %>
<li><%= link_to c.name, :action => 'show', :id => c.id %>
<%= "(#{c.books.count})" -%></li>
<% end %>
</ul>

app/model/subject.rbで、「has_many :books」と定義しているので、booksテーブルの情報をc.books.countで持ってこれるらしい、なるほど。

次にshow.html.erbを用意

<h1><%= @subject.name -%></h1>
<ul>
<% @subject.books.each do |c| %>
<%= link_to c.title, :controller => "book",
                        :action => "show",:id => c.id -%>
<% end %>
</ul>

次に、Ajaxを使ってcreateメソッドを実行するためにjavascriptライブラリを読み込む。Rails付属のjavascriptライブラリを読み込むためには、app/views/layaout/standard.html.erbを修正する。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html;.
charset=iso-8859-1" />
<meta http-equiv="Content-Language" content="en-us" />
<title>Library Info System</title>
<%= stylesheet_link_tag "style" %>
<%= javascript_include_tag :defaults %>
</head>

〜以下略〜

この「<%= javascript_include_tag :defaults %>」が読み込み命令。そして、app/views/subject/list.html.erbを編集する。

<h1>Listing Subjects</h1>
<ul id="subject_list">
<% @subjects.each do |c| %>
<li><%= link_to c.name, :action => 'show', :id => c.id %>
<%= "(#{c.books.count})" -%></li>
<% end %>
</ul>
<p id="add_link"><%= link_to_function("Add a Subject",
"Element.remove('add_link'); Element.show('add_subject')")%></p>
<div id="add_subject" style="display:none;">
<% form_remote_tag(:url => {:action => 'create'},
    :update => "subject_list", :position => :bottom,
    :html => {:id => 'subject_form'}) do %>
Name: <%= text_field "subject", "name" %>
<%= submit_tag 'Add' %>
<% end %>
</div>

付け加えた最初の部分で、リンクを表示する。

<p id="add_link"><%= link_to_function("Add a Subject",
"Element.remove('add_link'); Element.show('add_subject')")%></p>

このリンクがクリックされると以下の部分が表示される。

<div id="add_subject" style="display:none;">
<% form_remote_tag(:url => {:action => 'create'},
    :update => "subject_list", :position => :bottom,
    :html => {:id => 'subject_form'}) do %>
Name: <%= text_field "subject", "name" %>
<%= submit_tag 'Add' %>
<% end %>
</div>

ここに入力された内容は、createメソッドへ飛ばされる。createメソッドはpartialメソッドでページ全体ではなくページの一部に出力を返す。その出力部分をapp/views/subject/_subject.html.erbとして以下のように置く。partialメソッドで呼び出されるViewファイルはアンダーバーで始まらなければならない規則があるとのこと。

<li id="subject_<%= subject.id %>">
<%= link_to subject.name, :action => 'show', :id => subject.id %>
<%= "(#{subject.books.count})" -%>
</li>

ファイルのアップロード

新たにプロジェクトを用意する。

% cd ~/Rails2.1
% rails upload

db/database.ymlはデフォルトで使用する。次にアップロードファイルを置くディレクトリを作成する。

% cd upload
% mkdir -p public/data

DataFileモデルを用意する。ただし、このモデルはデータベース上にはなく、アップロードするデータファイルを扱うモデルとのこと。

% ruby script/generate model DataFile
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/data_file.rb
      create  test/unit/data_file_test.rb
      create  test/fixtures/data_files.yml
      create  db/migrate
      create  db/migrate/20080806063836_create_data_files.rb

app/models/data_file.rbを編集。

class DataFile < ActiveRecord::Base
  def self.save(upload)
    name = upload['datafile'].original_filename
    directory = "public/data"
    # create the file path
    path = File.join(directory,name)
    # write the file
    File.open(path,"wb") {|f| f.write(upload['datafile'].read) }
  end
end

ここいらへんはRubyの基礎知識が足りないとわかりづらい。「File.open(path,"wb") {|f| f.write(upload['datafile'].read) }」のfが何だかよくわからない。ファイルハンドラ?そして、upload['datafile'].readでアップロードしたファイルを読み込んでその読み込んだ結果をfに書き込んでいるということかな。チュートリアルの方を読んでもあんまりわからない。

今度はコントローラーを作る。

% ruby script/generate controller Upload
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/upload
      exists  test/functional/
      create  app/controllers/upload_controller.rb
      create  test/functional/upload_controller_test.rb
      create  app/helpers/upload_helper.rb

app/controllers/upload_controller.rbを編集。

class UploadController < ApplicationController
  def index
    render :file => 'app/views/upload/uploadfile.html.erb'
  end
  def uploadFile
    post = DataFile.save(params[:upload])
    render :text => "File has been uploaded successfully"
  end
end

renderメソッドの引数でファイルを呼ぶか、テキストメッセージを表示するか指定できるのか。

次にビューを用意する。app/views/upload/uploadfile.html.erbの中身を以下とする。

<h1>File Upload</h1>
<% form_tag ({:action => 'uploadFile'},
                        :multipart => true) do %>
<p><label for="upload_file">Select File</label> :
<%= file_field 'upload', 'datafile' %></p>
<%= submit_tag "Upload" %>
<% end %>

WEBrickを立ち上げて、ファイルをアップロードしたらエラーがでた。

undefined method `save' for DataFile(Table doesn't exist):Class

言われてみればデータベースを用意していない。Migrationする。

% rake db:migrate

そして、もう一度チャンレンジする。それでもだめ。

undefined method `save' for DataFile(id: integer, created_at: datetime, updated_at: datetime):Class

app/models/data_file.rbを以下のように(ApplicationControllerを継承)したらうまくいった。

class DataFile < ApplicationController
  def self.save(upload)
    name = upload['datafile'].original_filename
    directory = "public/data"
    # create the file path
    path = File.join(directory,name)
    # write the file
    File.open(path,"wb") {|f| f.write(upload['datafile'].read) }
  end
end

メールを送る

新たなプロジェクトemailsを作成する

% rails emails

次にconfig/environment.rbの末尾に以下を付け加える。

ActionMailer::Base.delivery_method = :sendmail

SMTPサーバを使う場合は:smtpUnix環境などで/usr/lib/sendmailなどが使える場合は:sendmailを使う。今回はsendmailを使う。

次にメーラーを作成する。

% ruby script/generate mailer Emailer
      exists  app/models/
      create  app/views/emailer
      exists  test/unit/
      create  test/fixtures/emailer
      create  app/models/emailer.rb
      create  test/unit/emailer_test.rb

app/models/emailer.rbを編集。

class Emailer < ActionMailer::Base
  def contact(recipient, subject, message, sent_at = Time.now)
    @subject = subject
    @recipients = recipient
    @from = 'no-reply@yourdomain.com'
    @sent_on = sent_at
    @body['title'] = 'This is title'
    @body["email"] = 'sender@yourdomain.com'
    @body["message"] = message
    @headers = { }
  end
end

次にコントローラーを作成。

% ruby script/generate controller Emailer
      exists  app/controllers/
      exists  app/helpers/
      exists  app/views/emailer
      exists  test/functional/
      create  app/controllers/emailer_controller.rb
      create  test/functional/emailer_controller_test.rb
      create  app/helpers/emailer_helper.rb

app/controllers/emailer_controller.rbを編集。

class EmailerController < ApplicationController
  def sendmail
    recipient = params[:email]
    subject = params[:subject]
    message = params[:message]
    Emailer.deliver_contact(recipient, subject, message)
    return if request.xhr?
    render :text => 'Message sent successfully'
  end
  def index
    render :file => 'app/views/emailer/index.html.erb'
  end
end

ビューを作る。app/views/emailer/index.html.erbを以下の内容で作成。

<h1>Send Email</h1>
<% form_tag :action => 'sendmail' do %>
<p><label for="email_subject">Subject</label>:
<%= text_field 'email', 'subject' %></p>
<p><label for="email_recipient">Recipient</label>:
<%= text_field 'email', 'recipient' %></p>
<p><label for="email_message">Message</label><br/>
<%= text_area 'email', 'message' %></p>
<%= submit_tag "Send" %>
<% end %>

app/views/emailer/contact.html.erbを以下の内容で作成する。

Hi!

You are having one email message from <%= @email %> with a title

<%= @title %>
and following is the message:
<%= @message %>

Thanks

WEBrickで試してみる。私の環境ではメールはうまく送れなかった。log/development.logにメールの内容があった。

終わりに

Railsチュートリアルの後が難しい。チュートリアルを終えてのメモ。

今回チュートリアルをやってよかったこと。

  • Migrationがとても便利そう。個々のデータベース管理システムから独立してくれているのはありがたい
  • データベースの中身をオブジェクトとしてもってくるのはやっぱり便利
  • SQLiteとWEBricのおかげで開発環境を整えるのが楽、その分公開環境を整えるのは面倒でしんどいけど。

疑問点

  • timezoneがUTCになっているのでJSTに直す方法を探す必要あり
  • エラーメッセージの日本語化の方法を探すを探す必要あり
  • GetTextと組み合わせて使う方法を練習する必要あり
  • ログイン認証とセッション管理の方法を調べる必要あり
  • 日本語メールを送れるようにする方法を調べる必要あり
  • 画像などを表示する時にはどこにおいて、どうやってアクセスすれば良いのかを調べる
  • 既存のデータベースのデータをMigrationと合わせるにはどうすればよいの?

そもそもとして、

  • ハッシュへのアクセスの仕方params[:id]の:idが何なのか調べる