2012年12月4日火曜日

Processing.jsでSVG画像をその場で回転させるサンプル

ProcessingでSVG画像を読み込んで回転させようとすると、その場で回転せずに、下図のように画像の左上の頂点を中心にして回転してしまう。


同じ場所で回転させるためには、(1)の移動の際には、右方向に1マス分、(2)の移動の際には、右方向と下方向に1マス分という具合に、回転した後で画像自体を動かす必要があるようだ。

Processing の関数内で実装すると下記のようになる。
void moveRect2() {
  int xAdjust = 0;
  int yAdjust = 0;
  background(200);
  fill(200);
  myicon.resetMatrix();
  if (currentAngle == TWO_PI) {
    currentAngle = 0;
  } else {
    currentAngle += HALF_PI;
  }
  myicon.rotate(currentAngle);
  if (currentAngle == HALF_PI) {
    xAdjust = 30;
    yAdjust = 0;
  } else if (currentAngle == PI) {
    xAdjust = 30;
    yAdjust = 30;
  } else if (currentAngle == (PI + HALF_PI)) {
    xAdjust = 0;
    yAdjust = 30;
  } else {
    xAdjust = 0;
    yAdjust = 0;
  }
  shape(myicon, 50 + xAdjust, 50 + yAdjust, 30, 30);
}

実際に動作するサンプルはこちら。
"Rotate1"ボタンを押すと通常の回転、"Rotate2"ボタンを押すとその場から動かずに回転する。


ソースコード全文はこちら。
https://gist.github.com/4204624

2012年11月19日月曜日

画面オープン直後にProcessing.jsのスクリプトを実行するサンプル

jQueryで画面のオープン直後に処理を実行する場合は、$(document).ready を利用することが一般的なようだが、このイベントに合わせてProcessingのスクリプトを実行しようとしてもうまくいかない。

具体的には、下記のようなソースコードでは、
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<script type="text/javascript" src="jquery.js"></script>
<script src="processing-1.4.1.js" type="text/javascript"></script>
<script type="text/processing" data-processing-target="mycanvas">
void setup()
{
  size(200, 200);
  colorMode(RGB, 100);
  noLoop();
}

void draw()
{
  fill(90);
  ellipse(70, 70, 100, 100);
}

void addEllipse()
{
  fill(60);
  ellipse(90, 90, 70, 70);
}
</script>
<script type="text/javascript">
$(document).ready(function(){
  var p= Processing.getInstanceById("mycanvas");
  p.addEllipse();
})
</script>
</head>
<body>
  <p>
  <canvas id="mycanvas"></canvas>
  </p>
  </body>
</html>
次のようなエラーメッセ―ジが表示されてしまう。


$(document).ready イベントでは、画面上のオブジェクトの読み込みがすべて完了した時点ではなく、画面上のDOM(Document Object Model)の読み込みが完了した時点、つまり、HTMLのソースコードの読み込みが完了した時点となる。

このタイミングでは、Processingの画面描画が完了していないので、Processingの関数を呼ぼうとしても、エラーになってしまうようだ。

$(document).ready の代わりに、$(window).load を使ったらうまくいった。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<script type="text/javascript" src="jquery.js"></script>
<script src="processing-1.4.1.js" type="text/javascript"></script>
<script type="text/processing" data-processing-target="mycanvas">
void setup()
{
  size(200, 200);
  colorMode(RGB, 100);
  noLoop();
}

void draw()
{
  fill(90);
  ellipse(70, 70, 100, 100);
}

void addEllipse()
{
  fill(60);
  ellipse(90, 90, 70, 70);
}
</script>
<script type="text/javascript">
$(window).load(function(){
  var p= Processing.getInstanceById("mycanvas");
  p.addEllipse();
})
</script>
</head>
<body>
  <p>
  <canvas id="mycanvas"></canvas>
  </p>
  </body>
</html>

2012年11月12日月曜日

Railsでリストボックスを使って親子テーブルを同時に更新するサンプル(インタフェース改良版)


前回作成したサンプルで、一応リストボックスとしては機能しているものの、登録件数が多くなると何が選択されていて、何が選択されていないかがわかりにくくなる。

下記のようなインタフェースで、左側のリストボックスが未登録、右側のリストボックスが登録済となるように分割して表示したい。


まずは再改良版のGameクラスとLineupクラスのscaffold。(実行結果は省略。)
rails g scaffold bestgame date:date
rails g scaffold bestlineup bestgame:references player:references
rake db:migrate
Gameクラスの_form.html.erbは、下記のように修正する。
<%= form_for(@bestgame) do |f| %>
  <% if @bestgame.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@bestgame.errors.count, "error") %> prohibited this bestgame from being saved:</h2>

      <ul>
      <% @bestgame.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :date %><br />
    <%= f.date_select :date, :use_month_numbers => true %>
  </div>
  <div class="field">
    <table>
      <tr><td align="center">
        <%= f.label "Bench Lineups" %><br />
        <%= f.select(:non_lineup_ids, @non_lineup_select_data, {}, {:multiple => true, :size => Player.all.size}) %>
      </td><td>
        <input id="btnMoveRight" type="button" value="->" onClick="moveItems('right')" />  
        <br />  
        <input id="btnMoveLeft" type="button" value="<-" onClick="moveItems('left')" />
        </td><td align="center">
        <%= f.label "Starting Lineups" %><br />
        <%= f.select(:player_ids, @lineup_select_data, {}, {:multiple => true, :size => Player.all.size}) %>
      </td></tr>
      </table>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>
左側のリストボックスは、Lineupクラスに登録されていないPlayerを表示する。>
右側のリストボックスは、Lineupクラスに登録されているPlayerを表示する。
右側のリストボックスの送信データのみ、model側で扱うようにする。

左右のリストボックスのデータの移動は、矢印のボタンを押すと、下記のJavaScriptの関数が呼ばれるようにする。
// リストボックスの項目選択
function moveItems(direction) {
  
  var leftBox = document.getElementById("bestgame_non_lineup_ids");  
  var rightBox = document.getElementById("bestgame_player_ids");   
  var fromBox, toBox;  

  if (direction == "right") {  
    fromBox = leftBox; toBox = rightBox;  
  }   
  else if (direction == "left") {  
    fromBox = rightBox; toBox = leftBox;  
  }  
    
  if ((fromBox != null) && (toBox != null)) {   
    if(fromBox.length < 1) {  
      alert("リストボックスにアイテムがありません!");  
      return false;  
    }  
    if(fromBox.selectedIndex == -1) {  
      alert("移動するアイテムを選択してください!");  
      return false;  
    }  
    while ( fromBox.selectedIndex >= 0 ) {   
      var newOption = new Option();   
      newOption.text = fromBox.options[fromBox.selectedIndex].text;   
      newOption.value = fromBox.options[fromBox.selectedIndex].value;   
      toBox.options[toBox.length] = newOption;  
      fromBox.remove(fromBox.selectedIndex);   
    }   
  }  
  return false;
}

// Submit時のリストボックスの項目自動選択
jQuery(function(){
  $('[id*=bestgame]').submit(function(){
    
    var selectedIDs = new Array();
    $('#bestgame_player_ids option:not(:selected)').each(function() {  
      selectedIDs.push(this.value)
    });
    $('#bestgame_player_ids').val(selectedIDs)
  });
});
moveItemsの関数では、選択されたリストボックスの値と表示テキストを移動先に追加、移動元から削除している。データがない場合とデータ未選択の場合のエラーチェックも行なっている。

$('[id*=bestgame]').submitの関数では、フォームのsubmit時に、右側のリストボックスの値を自動的に全選択するようにしている。

右側のリストボックスにデータを移しただけでは、フォームに値が送信されない。リストボックスは、あくまで選択された値だけがフォームに送信されるからである。

要素名を部分一致にしているのは、railsの場合、newのフォームとeditのフォームで、名前が異なるため。

modeには、下記のメソッドを追加する。
  # Lineupクラスに存在しないPlayerのIDを配列でget/setできる属性を追加
  def non_lineup_ids
    @non_lineup_ids || players.collect{|p| p.id} - bestlineups.collect{|p| p.id}
  end
 
  def non_lineup_ids=(id_array)
    @non_lineup_ids = id_array.collect{|id| id.to_i};
  end
Lineupに存在しないプレイヤーを、playersとlineupsの配列の引き算で算出している。ただし、Controllerからは使用していない。

最後に、Controllerの修正は下記の通りとなる。(new~update部分のみ)
  # GET /bestgames/new
  # GET /bestgames/new.json
  def new
    @bestgame = Bestgame.new
    @non_lineup_select_data = Player.all.collect{|p| [p.name, p.id]}
    @lineup_select_data = ""

    respond_to do |format|
      format.html # new.html.erb
      format.json { render json: @bestgame }
    end
  end

  # GET /bestgames/1/edit
  def edit
    @bestgame = Bestgame.find(params[:id])
    @non_lineup_select_data = Player.all.collect{|p| [p.name, p.id]} - Bestlineup.find_all_by_bestgame_id(params[:id]).collect{|l| [l.player.name, l.player.id]}
    @lineup_select_data = Bestlineup.find_all_by_bestgame_id(params[:id]).collect{|l| [l.player.name, l.player.id]}
  end
新規追加の場合は、登録済のデータが存在しないので、lineup_select_dataの配列を空で初期化している。
修正の場合は、未登録のデータを、playersとlineupsの配列を引き算することで導き出している。modelクラスの関数を使わずに、ここで計算しているのは、modelクラスの関数はidのみ扱っていて、名前を保持していないため。

これで、2つのリストボックスでデータを移動できるインタフェースが完成した。


今回は下記のホームページが参考になった。

リストボックス間でアイテムを移動
http://jsajax.com/Articles/listbox/339

2012年11月8日木曜日

RubyでExcelを操作するサンプル

FXのシステムトレードのブログを運用している。

不労所得を確保セヨ!!シストレ24戦記
http://sysken.blogspot.jp/

FX会社から提供される取引報告のExcelファイルから、HTMLの表組みを手作業で行なっていたのだが、件数が多いと時間がかかる。

そこで、下記の記事を参考にして、RubyでExcelファイルから値を抜き出してHTMLに変換するプログラムを作成してみた。

VBA より便利で手軽 Excel 操作スクリプト言語「Ruby」へのお誘い (前編)
http://jp.rubyist.net/magazine/?0027-ExcellentRuby

Excelで値を取り出してしまえば、後は通常のRubyのプログラムと同様に値を扱えば良い。

数字をカンマ区切りにするのは、下記の記事を参考にさせていただいた。

Re: 金額カンマ編集について
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/37580

以下、作成したソースコード。
# encoding: CP932
require 'win32ole'

# ストラテジー別の最終取り引き回数(手動で変更)
RIKIWIKITAVYFX_USDJPY_LAST = 173
PMINVESTCAPITAL_EURGBP_LAST = 95
DBSWING_EURUSD_LAST = 28
T_Follow_EURUSD_LAST = 10

# 表組み用HTML
TABLE_HEADER = '<table border="1" bordercolor="#888" cellspacing="0" style="border-collapse: collapse; border-color: rgb(136, 136, 136); border-width: 1px;"><tbody>' + "\n"
TABLE_FOOTER = '</tbody></table>' + "\n"
FIRST_TAG = '<tr><td style="width: 150px;">'
SECOND_TAG = '</td><td style="text-align: right; width: 100px;"><b><span style="color: blue;">'
THIRD_TAG = '</span></b></td><td style="text-align: right; width: 100px;"><b><span style="color: blue;">'
FOURTH_TAG = '</span></b></td></tr>' + "\n"

# ExcelファイルのOpen
app = WIN32OLE.new('Excel.Application')
book = app.Workbooks.Open(app.GetOpenFilename)

# Excelファイルからのデータ抽出
rowIndex = 0
rikiwikitavyfxUSDJPYrowArray = Array.new
pminvestcapitalEURGBProwArray = Array.new
dbswingEURUSDrowArray = Array.new
tfollowEURUSDrowArray = Array.new

for row in book.ActiveSheet.UsedRange.Rows do
  if rowIndex > 0
    colIndex = 0
    colText = ""
    colArray = Array.new
    colArray << ''
    for cell in row.Columns do
      if colIndex == 2 || colIndex == 4 || colIndex.between?(10,12)
        colArray << cell.Value
      end
      colIndex += 1
    end
    if colArray[1] == "RikiTikiTavi FX" && colArray[2] == "USDJPY"
      rikiwikitavyfxUSDJPYrowArray << colArray 
    elsif colArray[1] == "Pminvestcapital" && colArray[2] == "EURGBP"
      pminvestcapitalEURGBProwArray << colArray 
    elsif colArray[1] == "DBSwing" && colArray[2] == "EURUSD"
      dbswingEURUSDrowArray << colArray 
    elsif colArray[1] == "T Follow" && colArray[2] == "EURUSD"
      tfollowEURUSDrowArray << colArray 
    end
  end
  rowIndex += 1 
end

# HTML表組み用関数
def createTable(rowArray, serial, labelSerial)
  
  sumPips = 0
  sumMoney = 0

  table = "<b>" + labelSerial.to_s + ". "
  table += rowArray[0][1] + ":"
  table += rowArray[0][2].slice(0, 3) + "/" + rowArray[0][2].slice(3, 3)
  table += "</b><br />\n"
  table += "<br />\n"
  table += "<br />\n"
  table += "<br />\n"
  table += TABLE_HEADER
  rowArray.reverse!
  for row in rowArray do
    table += FIRST_TAG
    table += serial.to_s + "回目の取り引き"
    if row[4] < 0
      table += SECOND_TAG.sub("blue", "red")
    else
      table += SECOND_TAG
    end
    table += row[4].to_s.reverse.gsub( /(\d{3})(?=\d)/, '\1,' ).reverse + "pips"
    if row[5] < 0
      table += THIRD_TAG.sub("blue", "red")
    else
      table += THIRD_TAG
    end
    table += row[5].round.to_s.reverse.gsub( /(\d{3})(?=\d)/, '\1,' ).reverse + "円"
    table += FOURTH_TAG
    serial += 1
    sumPips += row[4] * 10
    sumMoney += row[5]
  end
  table += FIRST_TAG
  table += "合 計"
  if sumPips < 0
    table += SECOND_TAG.sub("blue", "red")
  else
    table += SECOND_TAG
  end
  table += (sumPips / 10).to_s.reverse.gsub( /(\d{3})(?=\d)/, '\1,' ).reverse + "pips"
  if sumMoney < 0
    table += THIRD_TAG.sub("blue", "red")
  else
    table += THIRD_TAG
  end
  table += sumMoney.round.to_s.reverse.gsub( /(\d{3})(?=\d)/, '\1,' ).reverse + "円"
  table += FOURTH_TAG
  table += TABLE_FOOTER
  table += "<br />\n"
  table += "<br />\n"
  table += "<br />\n"
  return table
end

# ストラテジー別HTML作成
labelSerial = 1
outputFile = ''

if dbswingEURUSDrowArray.size > 0
  outputFile += createTable(dbswingEURUSDrowArray, DBSWING_EURUSD_LAST, labelSerial) 
  labelSerial += 1
end

if tfollowEURUSDrowArray.size > 0
  outputFile += createTable(tfollowEURUSDrowArray, T_Follow_EURUSD_LAST, labelSerial) 
  labelSerial += 1
end

if pminvestcapitalEURGBProwArray.size > 0
  outputFile += createTable(pminvestcapitalEURGBProwArray, PMINVESTCAPITAL_EURGBP_LAST, labelSerial) 
  labelSerial += 1
end

if rikiwikitavyfxUSDJPYrowArray.size > 0
  outputFile += createTable(rikiwikitavyfxUSDJPYrowArray, RIKIWIKITAVYFX_USDJPY_LAST, labelSerial) 
  labelSerial += 1
end

# HTMLファイルの書き出し
outputHTMLFile = "<html><body>\n" 
outputHTMLFile += outputFile 
outputHTMLFile += "</body></html>\n"

open("シストレ24成績.html", "w") {|f| f.write outputFile}

book.close(false)
app.quit

Railsでリストボックスを使って親子テーブルを同時に更新するサンプル(複数選択対応版)

前回作成したサンプルでは、リストボックスなのに複数選択に対応していない。HTML的に複数選択に対応しても、DBにうまくinsertされない。

しかも、手動でGameクラスにLineupクラスを複数追加すると、下図のようにリストボックスが複数表示されてしまって見栄えが悪い。


下記のサイトで同じ課題に取り組んでいる人がいたので、参考にさせていただいた。


Complex Forms for Many-to-Many Relationships
http://beacon.wharton.upenn.edu/learning/2012/02/complex-forms-for-many-to-many-relationships/

まずは改良版のGameクラスとLineupクラスのscaffold。(実行結果は省略。)
rails g scaffold bettergame date:date
rails g scaffold betterlineup bettergame:references player:references
rake db:migrate
次にbetterゲームクラスのmodelを下記のように修正する。
# encoding: utf-8

class Bettergame < ActiveRecord::Base
  has_many :players, :through => :betterlineups
  has_many :betterlineups
  accepts_nested_attributes_for :betterlineups

  def player_names
    players.collect{|p| p.name}.join(', ')
  end

  # PlayerクラスのIDを配列でget/setできる属性を追加
  def player_ids
    @player_ids || players.collect{|p| p.id}
  end
 
  def player_ids=(id_array)
    @player_ids = id_array.collect{|id| id.to_i};
  end

  after_save :assign_players

  private

  # PlayerクラスのIDの配列から、Betterlineupクラスを追加/編集/削除する。
  def assign_players
    if @player_ids
      new_ids = @player_ids
      old_ids = players.collect{|p| p.id}
      ids_to_delete = old_ids - (old_ids & new_ids)
      ids_to_add = new_ids - (old_ids & new_ids)
      bettergame_id = id
 
      ids_to_delete.each do |player_id|
        Betterlineup.destroy_all(:bettergame_id => bettergame_id, :player_id => player_id)
      end
 
      ids_to_add.each do |player_id|
        Betterlineup.create(:bettergame_id => bettergame_id, :player_id => player_id)
      end
    end
  end
end
リストボックスから複数送信されるPlayerのIDを配列としてまとめて扱うためのメソッドと、送信されたPlayerのIDとLineupクラスで既に保持しているPlayerのIDを比較して、追加/更新/削除を行うメソッドの2つが追加されている。

次に、viewの_form.html.erbを下記のように修正する。
<%= form_for(@bettergame) do |f| %>
  <% if @bettergame.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@bettergame.errors.count, "error") %> prohibited this bettergame from being saved:</h2>

      <ul>
      <% @bettergame.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :date %><br />
    <%= f.date_select :date, :use_month_numbers => true %>
  </div>
  <div class="field">
    <%= f.label :lineups %><br />
    <%= f.select(:player_ids, @player_select_data, {}, {:multiple => true, :size => Player.all.size}) %>
  </div>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>
セレクトボックスのヘルパーには、先ほど modelクラスに追加した「:player_ids」を指定している。 その次の「@player_select_data」は、リストボックスに表示する値の配列となる。後で Controllerに追加する。 複数選択できるように、「:multiple => true」として、リストボックスの表示サイズは、Player全員とするために、「:size => Player.all.size」としている。

最後に、Controllerの修正は下記の通りとなる。(new~update部分のみ)
  # GET /bettergames/new
  # GET /bettergames/new.json
  def new
    @bettergame = Bettergame.new
    @player_select_data = Player.all.collect{|p| [p.name, p.id]}

    respond_to do |format|
      format.html # new.html.erb
      format.json { render json: @bettergame }
    end
  end

  # GET /bettergames/1/edit
  def edit
    @bettergame = Bettergame.find(params[:id])
    @player_select_data = Player.all.collect{|p| [p.name, p.id]}
  end

  # POST /bettergames
  # POST /bettergames.json
  def create
    @bettergame = Bettergame.new(params[:bettergame])

    respond_to do |format|
      if @bettergame.save
        format.html { redirect_to @bettergame, notice: 'Bettergame was successfully created.' }
        format.json { render json: @bettergame, status: :created, location: @bettergame }
      else
        format.html { render action: "new" }
        format.json { render json: @bettergame.errors, status: :unprocessable_entity }
      end
    end
  end

  # PUT /bettergames/1
  # PUT /bettergames/1.json
  def update
    @bettergame = Bettergame.find(params[:id])

    respond_to do |format|
      if @bettergame.update_attributes(params[:bettergame])
        format.html { redirect_to @bettergame, notice: 'Bettergame was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: "edit" }
        format.json { render json: @bettergame.errors, status: :unprocessable_entity }
      end
    end
  end
修正箇所は、newとeditのメソッド内で、「@player_select_data」の設定(Playerクラスの全データ取得)を行なっているのみ。

これで、複数選択の追加/編集/削除がリストボックス上から行えるようになった。


2012年11月7日水曜日

Railsでリストボックスを使って親子テーブルを同時に更新するサンプル


野球やサッカーなどで、手持ちの選手から試合ごとのメンバーを決める場合のクラス図は上図のようになる。Lineupという中間クラスを通して、GameクラスとPlayerクラスが多対多の関係になっている。

こういう構造の場合に、Gameクラスにデータを追加→Playerクラスにデータを追加→Lineupクラスにデータを追加というように、順々に画面を操作していくのは面倒である。

下図のように、GameクラスとLineupクラスを一画面で同時に追加することはできないだろうか?


まずはscaffold。(実行結果は省略。)
rails g scaffold game date:date
rails g scaffold player name:string
rails g scaffold lineup game:references player:references
rake db:migrate
次に、modelにクラス間のアソシエーションを設定する。
class Game < ActiveRecord::Base
  has_many :players, :through => :lineups
  has_many :lineups
  accepts_nested_attributes_for :lineups
end

class Lineup < ActiveRecord::Base
  belongs_to :game
  belongs_to :player
end
「has_many 子クラス :through 中間クラス」と書くことで、中間クラスを通じて子クラスの属性にアクセスすることができるようなる。

つまり、下記のように中間クラスLineupを意識せずに、子クラスPlayerを扱えるようになる。
irb(main):001:0> Game.find(1).players[0].name
=> "陽岱鋼"
「accepts_nested_attributes_for 中間クラス」と書くことで、子クラスの変更を親クラスで保存できるようになる。
irb(main):002:0> Game.find(1).players << Player.find(2)
irb(main):003:0> Game.find(1).save
irb(main):004:0> Game.find(1).players.last.name
=> "今浪"
view側の変更は下記のようになる。
<%= form_for(@game) do |f| %>
  <% if @game.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@game.errors.count, "error") %> prohibited this game from being saved:</h2>

      <ul>
      <% @game.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :date %><br />
    <%= f.date_select :date, :use_month_numbers => true  %>
  </div>
  <div class="field">
    <%= f.label :lineups %><br />
    <%= f.fields_for :lineups do |lf| %>
      <%= lf.hidden_field :id %>
      <%= lf.select :player_id, Player.all.map {|player| [player.name, player.id]}, {:selected=>lf.object.player_id}, :size => Player.all.size %>
    <% end %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>
「fields_for」の内側に子クラスのform要素を記述する。
これで1つのview内に複数のmodelを扱えるようになる。

「:selected=>lf.object.player_id」は、こう記述しておくと、表示しているPlayerクラスの一覧から、Lineupクラスに登録されているものが選択された状態になる。

次に、controller側の変更は下記のようになる。
  # GET /games/new
  # GET /games/new.json
  def new
    @game = Game.new
    @game.lineups.build

    respond_to do |format|
      format.html # new.html.erb
      format.json { render json: @game }
    end
  end
「@game.lineups.build」の行を追加したのみ。
ここでbuildをしておかないと、view側のfields_forで指定している:lineupsが空になってしまうために、下図のようにリストボックスが表示されなくなってしまう。


model、view、controllerの修正後は、下図のようにGameクラスの画面から、子クラスPlayerの追加、修正ができるようになる。


しかしながら、現状では、せっかくリストボックスを使っているのに複数選択に対応していない。
実質的にコンボボックスと同じ機能になってしまっている。

複数選択の対応は次回の課題としたい。

以下は、今回参考にさせていただいたブログ。

has_manyな関連先をまとめてINSERTする
http://aerial.st/archive/2011/06/11/insert-has-many-relations/

has_manyな関連で、1アクションで複数のモデルを同時に保存するには?
http://d.hatena.ne.jp/zariganitosh/20080104/1199437301

[Rails]accepts_nested_attributes_forでネストした別モデルのフォーム[Ruby]
http://shasou.blogspot.jp/2011/05/railsacceptsnestedattributesforruby.html

Nested AttributesとNested Model Formsを使って親子オブジェクトを一括で登録/変更するには
http://www.everyleaf.com/tech/ror_tips/nested-attributesnested-model-forms/



2012年11月2日金曜日

Railsで正規化されていないIDから関連テーブルの情報を取得するサンプル


上図の例のように、正規化せずに一つのテーブル内に複数の外部キーを持った場合に、外部のテーブルの情報を参照(Food.name)するにはどうすれば良いだろうか?

なにはともあれscaffold。(実行結果は省略。)
rails g scaffold food name:string
rails g scaffold person name:string first_favfood_id:integer second_favfood_id:integer third_favfood_id:integer
rake db:migrate
FoodとPersonに適当にデータを入力しておく。



通常は、下記のように参照先のテーブルをblongs_toで指定することになるのだが、
class Person < ActiveRecord::Base
  belongs_to :food
end
こうすると外部キーのIDが、food_idに限定されてしまう。

下記のようにbelongs_toのオプションを指定すると、food_idの外部キーを個別に指定できるようだ。
class Person < ActiveRecord::Base
  belongs_to :first_favfood, :class_name => 'Food', :foreign_key => 'first_favfood_id'
  belongs_to :second_favfood, :class_name => 'Food', :foreign_key => 'second_favfood_id'
  belongs_to :third_favfood, :class_name => 'Food', :foreign_key => 'third_favfood_id'
end
その後、viewのindex.html.erbを下記のように変更すると、Food.nameが参照できるようになった。
<h1>Listing people</h1>

<table>
  <tr>
    <th>Name</th>
    <th>First favfood</th>
    <th>Second favfood</th>
    <th>Third favfood</th>
    <th></th>
    <th></th>
    <th></th>
  </tr>

<% @people.each do |person| %>
  <tr>
    <td><%= person.name %></td>
    <td><%= person.first_favfood.name %></td>
    <td><%= person.second_favfood.name %></td>
    <td><%= person.third_favfood.name %></td>
    <td><%= link_to 'Show', person %></td>
    <td><%= link_to 'Edit', edit_person_path(person) %></td>
    <td><%= link_to 'Destroy', person, confirm: 'Are you sure?', method: :delete %></td>
  </tr>
<% end %>
</table>

<br />

<%= link_to 'New Person', new_person_path %>

下記のブログが参考になった。

【Ruby on Rails】1つのテーブルと2フィールドでリレーションを持たせる方法(作成者と最終更新者の情報を持たせる)
http://kagayoshito.com/posts/show/102

2012年10月31日水曜日

Railsでコンボボックスを連動させるサンプル


コンボボックスで都道府県を選んだ後に、選んだ都道府県下の市町村を絞りこんで表示させたい。

Rails3になって、observe_fieldというフィールドの更新を検知する関数がなくなったり、Ajax周りの関数が変更になっていたりで、そのまま利用できるサンプルは少なかった。

最も参考になったのは、下記のブログ記事。

[Rails3]jQueryでobserve_field的なことを。

まずは、都道府県と市町村、その他住所クラスのscaffoldを作成する。(実行結果は省略。)
rails g scaffold todofuken name:string
rails g scaffold shichoson todofuken:references name:string
rails g scaffold jusho todofuken:references shichoson:references sonotajusho:string
scaffoldで作成された_form.html.erbは、TodofukenとShichosonがテキストフィールドになっているので、下記のようにコンボボックスに変更する。
<%= form_for(@jusho) do |f| %>
  <% if @jusho.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@jusho.errors.count, "error") %> prohibited this jusho from being saved:</h2>

      <ul>
      <% @jusho.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :todofuken %><br />
    <%= f.collection_select(:todofuken_id, Todofuken.find(:all), :id, :name) %>
  </div>
  <div class="field">
    <%= f.label :shichoson %><br />
    <%= f.collection_select(:shichoson_id, Shichoson.find(:all), :id, :name) %>
  </div>
  <div class="field">
    <%= f.label :sonotajusho %><br />
    <%= f.text_field :sonotajusho %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

次に、市町村の部分のhtmlをAjaxで変更できるように外出しにする。

_todoufuken_select.html.erb
<%= f.select(:shichoson_id, @shichoson) %>
_form.html.erbからは、下記のようにrenderで呼び出す。
<%= render 'todoufuken_select', {:f => f} %>
{:f => f}と指定しておくと、_form.html.erbのFormの変数(:shichoson)が、_todoufuken_select.html.erb内でも使えるようになる。

次に、都道府県のコンボボックスの変更を検知するために、JavaScriptを記述する。

application.js
jQuery(function(){
  $('#jusho_todofuken_id').change(function(){
  var todofuken_id = $("#jusho_todofuken_id").val();
  $.get("todoufuken_select.js?todofuken_id=" + todofuken_id);
});
todoufuken_select.js の部分は、URLとしては/jushos/todoufuken_select.js になる。
scaffoldを実行した直後だと、/jushos/XXXX のリクエストは、すべてコントローラーのshowメソッドで処理されてしまうので、ルーティングを下記のように追加する。

routes.rb
Mytest::Application.routes.draw do

  match '/jushos/todoufuken_select'
  resources :jushos
match~の部分をresources :jushosの先に記述する必要がある。 ※showメソッドの/jushos/XXXXのルーティングの方が範囲が広いので、先にヒットしてしまう。
次に、コントローラーを修正する。

jushos_controller.rb
  # GET /jushos/new
  # GET /jushos/new.json
  def new
    @jusho = Jusho.new
    @shichoson = [["都道府県を選択してください。", "0"]]

    respond_to do |format|
      format.html # new.html.erb
      format.json { render json: @jusho }
    end
  end
  # GET /jushos/todoufuken_select
  def todoufuken_select
    @shichoson = Shichoson.find_all_by_todofuken_id(params[:todofuken_id])
    render
  end
変更点は2箇所。
既存のnewメソッドに、市町村のコンボボックスの初期値を設定したことと、todoufuken_selectメソッドの新規追加。

次に、todoufuken_selectメソッドが呼び出すことになる_todoufuken_select.html.erbを作成する。
$("#jusho_shichoson_id").html("<%= escape_javascript(options_for_select(@shichoson.map{ |shichoson| [shichoson.name, shichoson.id] })) %>");
options_for_selectは、selectタグのoprionの部分を作成する関数だが、ここにDBの検索結果の配列(@shichoson)をそのまま渡してもうまく表示されないので、上記のように、DBの列を指定して渡すようにする。

これで完成。

初期状態

都道府県で愛知県を選択

愛知県の市町村が表示された。

2012年10月25日木曜日

Windows環境でのRuby on railsコンソールの日本語表示

Windows のコマンドプロンプトで、Ruby on rails のコンソールを動かすと、日本語の表示でエラーが発生してしまう。

具体的には、下記クラスのputsメソッドを実行すると、
class Jpmsg
  def self.prt
    puts "テスト"
  end
end
下記のようなエラーが発生する。
irb(main):001:0> Jpmsg.prt
SyntaxError: E:/Sites/mytest/app/models/jpmsg.rb:3: invalid multibyte char (US-A
SCII)
E:/Sites/mytest/app/models/jpmsg.rb:3: invalid multibyte char (US-ASCII)
E:/Sites/mytest/app/models/jpmsg.rb:3: syntax error, unexpected $end, expecting
keyword_end
    puts "���������"
            ^
(以下略)
Rubyでは内部的にはUTF-8を利用している。
一方で、WindowsのコマンドプロントはShift-JIS。

コマンドプロントの文字コードは、「chcp 65001」と入力することで変更できるのだが、変更後は自動的に欧文フォントになってしまう。

下記のブログでは、レジストリの値を変更して、UTF-8で日本語フォントを利用する方法が紹介されているものの、自分の環境ではうまくいかなかった。

コンソール(cmd.exe)の文字コードを UTF-8 に

試行錯誤した結果、コマンドプロンプトの問題ではなく、ソースコードで利用する文字コードを明示的に指定しなければならないという問題のようである。

下記のように、文字コードを指定する一文を追加したことでちゃんと表示されるようになった。
# coding: utf-8
class Jpmsg
  def self.prt
    puts "テスト"
  end
end
irb(main):002:0> Jpmsg.prt
テスト
=> nil
下記のブログが参考になった。情報公開に感謝。

OSとRubyスクリプトの文字コードが異なる場合の対策
http://pgnote.net/?p=211

2012年10月7日日曜日

Railsで1対多の関係を設定するサンプル~既存モデルの修正

以前の記事では、RteamクラスとRmemberクラスに1対多の関係を設定した。

今回は、Rteamのクラスの上位にRleagueというクラスを作成して、さらに1対多の関係を設定してみたい。


以前の記事の場合と異なり、外部クラスへの参照を最初から維持するクラスを新規に作成するわけではなく、既存のクラスに後から追加することになる。

まずは Rleagueクラスの scaffoldを作成する。(実行結果は省略。)
rails g scaffold rleague name:string
次に、Rteamクラスに Rleagueクラスへの参照を追加するための migarationファイルの generateを行う。
E:\Sites\mytest>rails g migration add_rleagueref_rteammodell
      invoke  active_record
      create    db/migrate/20121005010409_add_rleagueref_rteammodell.rb
作成された migarationファイルに、Rleagueへのreferenceのカラムを追加する。
class AddRleaguerefRteammodell < ActiveRecord::Migration
  def up
    add_column :rteams, :rleague, :reference
  end

  def down
    remove_column  :rteams, :rleague
  end
end
migration を実行する。
E:\Sites\mytest>rake db:migrate
==  AddRleaguerefRteammodell: migrating =======================================
-- add_column(:rteams, :rleague, :reference)
   -> 0.0010s
==  AddRleaguerefRteammodell: migrated (0.0030s) ==============================

==  CreateRleagues: migrating =================================================
-- create_table(:rleagues)
   -> 0.1100s
==  CreateRleagues: migrated (0.1100s) ========================================
しかしながら、db/schema.rb を確認すると、rteamsテーブルにはエラーが発生している。
"reference" という型は後付けでは追加できないようだ。

"reference" 型を利用する代わりに、参照先のクラスのIDをクラス内に保持することになるらしい。

そこで、RleagueクラスのIDのカラムを追加するために、migrationファイルの generateを再度実行する。 (実行結果は省略。)
rails g migration add_rleagueid_rteammodel
作成された migarationファイルに、rleague_id というカラムを追加する。
class AddRleagueidRteammodel < ActiveRecord::Migration
  def up
    add_column :rteams, :rleague_id, :integer
    add_index :rteams, :rleague_id
  end

  def down
    remove_index :rteams, :rleague_id
    remove_column :rteams, :rleague_id
  end
end
以前の記事でも、"reference" 型を指定して作成されたテーブルには、rteam_id という名前でinteger型のカラムが追加されていたので、"reference" 型というのは実体のある型ではなく、単なるシンタックスシュガーなのかもしれない。

migrartionを再度実行する。
E:\Sites\mytest>rake db:migrate
==  AddRleagueidRteammodel: migrating =========================================
-- add_column(:rteams, :rleague_id, :integer)
   -> 0.0020s
-- add_index(:rteams, :rleague_id)
   -> 0.0280s
==  AddRleagueidRteammodel: migrated (0.0320s) ================================
今回は問題なく作成できたようだ。

Rleagueクラスの has_many :rteams と Rteamクラスの belongs_to :rleague は手動で追加する。
class Rleague < ActiveRecord::Base
  has_many :rteams
end

class Rteam < ActiveRecord::Base
  has_many :rmembers
  belongs_to :rleague
end
これで model側の修正は完了。
view側も修正しておく。

▽変更後の_form.html.erb
<%= form_for(@rteam) do |f| %>
  <% if @rteam.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@rteam.errors.count, "error") %> prohibited this rteam from being saved:</h2>

      <ul>
      <% @rteam.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :rleague %><br />
    <%= f.collection_select(:rleague_id, Rleague.find(:all), :id, :name) %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>
▽editの実行結果



▽変更後のshow.html.erb
<p id="notice"><%= notice %></p>

<p>
  <b>Name:</b>
  <%= @rteam.name %>
</p>

<p>
  <b>Member's name:</b><br />
  <% @rteam.rmembers.each do |rmember| %>
    <%= rmember.name %><br />
  <% end %>
</p>

<p>
  <b>League:</b>
  <%= @rteam.rleague.name %>
</p>

<%= link_to 'Edit', edit_rteam_path(@rteam) %> |
<%= link_to 'Back', rteams_path %>
▽showの実行結果


2012年10月4日木曜日

Railsで1対多の関係を設定するサンプル~画面表示


前回の記事で作成した Rteamクラスと Rmemberクラスの1対多の関係を画面から参照/更新できるようにしたい。

まずは、Rmemberクラスの Scaffoldで作成された画面から見てみる。

▽現状のindexページ


現状の indexページでは、Rteam のフィールドにインスタンスのハッシュ値が表示されているので、名前を表示するように変更する。

▽変更後のindex.html.erb
<h1>Listing rmembers</h1>

<table>
  <tr>
    <th>Name</th>
    <th>Rteam</th>
    <th></th>
    <th></th>
    <th></th>
  </tr>

<% @rmembers.each do |rmember| %>
  <tr>
    <td><%= rmember.name %></td>
    <td><%= rmember.rteam.name %></td>
    <td><%= link_to 'Show', rmember %></td>
    <td><%= link_to 'Edit', edit_rmember_path(rmember) %></td>
    <td><%= link_to 'Destroy', rmember, confirm: 'Are you sure?', method: :delete %></td>
  </tr>
<% end %>
</table>
▽変更後のindexページ


showページの方も同様にインスタンスのハッシュ値が表示されているので、名前を表示するように変更。

▽現状のshowページ


▽変更後のshow.html.erb
<p id="notice"><%= notice %></p>

<p>
  <b>Name:</b>
  <%= @rmember.name %>
</p>

<p>
  <b>Rteam:</b>
  <%= @rmember.rteam.name %>
</p>


<%= link_to 'Edit', edit_rmember_path(@rmember) %> |
<%= link_to 'Back', rmembers_path %>

▽変更後のshowページ


editページはテキストフィールドにインスタンスのハッシュ値が表示されていて何とも使いにくい。
どうやってインスタンスのハッシュ値を調べれば良いのか?

▽現状のeditページ


ハッシュ値の代わりに名前の一覧をコンボボックスで表示するように変更する。

▽現状のedit.html.erb
<h1>Editing rmember</h1>

<%= render 'form' %>

<%= link_to 'Show', @rmember %> |
<%= link_to 'Back', rmembers_path %>
edit.html.erb では、外部のファイルをレンダリングしているので、_form.html.erb の方を変更した。

▽変更後の_form.html.erb
<%= form_for(@rmember) do |f| %>
  <% if @rmember.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@rmember.errors.count, "error") %> prohibited this rmember from being saved:</h2>

      <ul>
      <% @rmember.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :rteam %><br />
    <%= f.collection_select(:rteam_id, Rteam.find(:all), :id, :name) %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>
▽変更後のeditページ


editの実行も問題なく修了した。

▽editの実行後


newページでもeditと同じファイルをレンダリングしているので、既にnewページの方でもコンボボックスが表示されるようになっている。

▽変更後のnewページ



次に、Rteamクラスの方は、下記のように showページで Rmemberの一覧を表示するように変更する。

現状のshowページ


▽変更後のshow.html.erb
<p id="notice"><%= notice %></p>

<p>
  <b>Name:</b>
  <%= @rteam.name %>
</p>

<p>
  <b>Member's name:</b><br />
  <% @rteam.rmembers.each do |rmember| %>
    <%= rmember.name %><br />
  <% end %>
</p>


<%= link_to 'Edit', edit_rteam_path(@rteam) %> |
<%= link_to 'Back', rteams_path %>
変更後のshowページ


editページと newページに関しては、ページ内でRmemberのリストを表示し、リストへの追加/変更/削除を実装する必要があって少々複雑なので、今回は見送りとした。

Railsで1対多の関係を設定するサンプル

Rails の場合はSQLを直接使わないので、下図のような1対多の関係を設定するにはどうすれば良いのだろうか?


まずはRails付属のSQLiteのSQLでやってみる。

▽テーブルの作成
E:\Sites\mytest\db>sqlite3 development.sqlite3
SQLite version 3.7.3
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> create table team(id, name);
sqlite> create table member(id, team_id, name);
sqlite> insert into team values(1, 'team1');
sqlite> insert into member values(1, 1, 'member1');
sqlite> insert into member values(2, 1, 'member2');
▽データの取得
sqlite> select team.id, team.name, member.id, member.name from team, member wher
e team.id = member.team_id;
1|team1|1|member1
1|team1|2|member2
こんな感じで、簡単にできる。(外部キーは未設定。)

Rails の ActiveRecord では、references という型を指定することで、他のクラスへの外部参照を設定できるようである。

まずは sacffold を作成してみる。

先程作成したテーブルと同じ名前のテーブルは作成できないので、rteam と rmember で作成する。(長いので実行結果は省略。)
rails g scaffold rteam name:string
rails g scaffold rmember name:string rteam:references
Rmember のクラスには、"belongs_to :rteam" という属性が追加されている。
class Rmember < ActiveRecord::Base
  belongs_to :rteam
end
Rmember のクラスは何も変更されていない。
"has_many :rmember" を手動で追加する必要があるようだ。

▽修正前
class Rteam < ActiveRecord::Base
end
▽修正後
class Rteam < ActiveRecord::Base
   has_many :rmembers
end
修正後にデータベースの Migration を実行。
E:\Sites\mytest>rake db:migrate
==  AddThirdnameOnecolumnmodel: migrating =====================================
-- add_column(:onecolumnmodels, :thirdname, :string)
   -> 0.0020s
==  AddThirdnameOnecolumnmodel: migrated (0.0020s) ============================

==  CreateRteams: migrating ===================================================
-- create_table(:rteams)
   -> 0.1510s
==  CreateRteams: migrated (0.1520s) ==========================================

==  CreateRmembers: migrating =================================================
-- create_table(:rmembers)
   -> 0.0020s
-- add_index(:rmembers, :rteam_id)
   -> 0.0350s
==  CreateRmembers: migrated (0.0390s) ========================================
db/schema.rb を見ると、rteam_id が rmembers テーブルに追加されている。
index も追加されているようだ。
  create_table "rmembers", :force => true do |t|
    t.string   "name"
    t.integer  "rteam_id"
    t.datetime "created_at", :null => false
    t.datetime "updated_at", :null => false
  end

  add_index "rmembers", ["rteam_id"], :name => "index_rmembers_on_rteam_id"

  create_table "rteams", :force => true do |t|
    t.string   "name"
    t.datetime "created_at", :null => false
    t.datetime "updated_at", :null => false
  end
自動生成されたテストデータを読み込でおく。
E:\Sites\mytest>rake db:fixtures:load

rails consoleから、Rteamクラスのデータを確認してみる。
現時点では、Rteam に関連付けられた Rmember は空の状態。
E:\Sites\mytest>rails console
Loading development environment (Rails 3.2.1)
irb(main):001:0> rteam1=Rteam.find(980190962)
=> #<Rteam id: 980190962, name: "MyString", created_at: "2012-10-02 04:13:28", u
pdated_at: "2012-10-02 04:13:28">
irb(main):002:0> rteam1.rmembers
=> []
rmembers の配列に Rmember から値を抽出して追加。
irb(main):003:0> rteam1.rmembers << Rmember.find(980190962)
=> [#<Rmember id: 980190962, name: "MyString", rteam_id: 980190962, created_at:
"2012-10-02 04:13:28", updated_at: "2012-10-02 05:52:04">]
Rmember 側の rteam_id も自動で追加された。
irb(main):005:0> rteam1.rmembers
=> [#<Rmember id: 980190962, name: "MyString", rteam_id: 980190962, created_at:
"2012-10-02 04:13:28", updated_at: "2012-10-02 05:52:04">]

2012年10月1日月曜日

SVG画像を読み込んでProcessing.jsで操作するサンプル


Processing の描画機能は高機能なのだが、スクリプト上で複雑な画像をゼロから描くのは面倒である。
そこで、Illustrator などのツールで作成したSVG画像を読み込んで表示するサンプルを作成してみた。

▽作成したHTML
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
    <script src="processing-1.4.1.js" type="text/javascript"></script>
  </head>
  <body>
    <p>
    <canvas id="mycanvas"></canvas>
    </p>
    <button onClick="sketchProc2()">Move</ button>
<script type="text/processing" data-processing-target="mycanvas">
PShape s;
int cy;
boolean down;

void setup() {
  size(320, 320);
  noLoop();
  s = loadShape("helicopter.svg");
  cy = 10;
  down = true;
}

void draw() {
  background(255); // clear
  shape(s, 80, cy, 100, 52);
}

void moveSVG() {

  background(255); // clear
  if (cy <= 10) {
    down = true;
  } else if (250 <= cy) {
    down = false;
  }

  if (down) {
    cy += (random(10) + 10);
  } else {
    cy -= (random(10) + 10);
  }

  shape(s, 80, cy, 100, 52);
}
</script>

<script id="script1" type="text/javascript">

function sketchProc2() {
  var p2 = Processing.getInstanceById("mycanvas");
  p2.moveSVG();
}
</script>

  </body>
</html>
▽実行結果
Move! ボタンを押すとヘリコプターのSVG画像が上下に移動する。

※ヘリコプターのSVG画像は、下記のサイトのアイコンを利用。

The Noun Project
http://thenounproject.com/

テーブルにカラムの追加と削除を行う最小サンプル


一度作成したテーブルにカラムを追加してみる。

▽現状のSchema
  create_table "onecolumnmodels", :force => true do |t|
    t.string   "name"
    t.datetime "created_at", :null => false
    t.datetime "updated_at", :null => false
  end
まずは空の migration ファイルを作成する。
G:\Sites\mytest>rails g migration add_thirdname_onecolumnmodel
      invoke  active_record
      create    db/migrate/20120930223614_add_thirdname_onecolumnmodel.rb
"rails generate" は "rails g" で代用できるようだ。
作成されたmigrationファイルを開くと下記のようになっている。
class AddThirdnameOnecolumnmodel < ActiveRecord::Migration
  def up
  end

  def down
  end
end
def up のところに追加したい内容を、def down のところに元に戻す内容を追加する。
class AddThirdnameOnecolumnmodel < ActiveRecord::Migration
  def up
    add_column :onecolumnmodels, :thirdname, :string
  end

  def down
    remove_column  :onecolumnmodels, :thirdname
  end
end
この状態で、migration を実行。
G:\Sites\mytest>rake db:migrate
==  AddThirdnameOnecolumnmodel: migrating =====================================
-- add_column(:onecolumnmodels, :thirdname, :string)
   -> 0.0070s
==  AddThirdnameOnecolumnmodel: migrated (0.0080s) ============================
実行後に、db/schema.rb の内容を確認すると、thirdname が追加されている。
  create_table "onecolumnmodels", :force => true do |t|
    t.string   "name"
    t.datetime "created_at", :null => false
    t.datetime "updated_at", :null => false
    t.string   "thirdname"
  end
元に戻したい場合は、下記のように version を指定して migration を実行する。
rake db:migrate VERSION=1
ではどこに version が記載されているかというと、railsのデータベース内の schema_migrations というテーブルに記録されているようだ。


最後の行が直近で追加したバージョン番号(migrationファイルについているシリアル番号と同じ)なので、一つ前のバージョンを指定して migration してみる。
G:\Sites\mytest>rake db:migrate VERSION=20120928013904
==  AddThirdnameOnecolumnmodel: reverting =====================================
-- remove_column(:onecolumnmodels, :thirdname)
   -> 0.2440s
==  AddThirdnameOnecolumnmodel: reverted (0.2460s) ============================
実行後に、db/schema.rb の内容を確認すると、thirdname が削除されている。
  create_table "onecolumnmodels", :force => true do |t|
    t.string   "name"
    t.datetime "created_at", :null => false
    t.datetime "updated_at", :null => false
  end

2012年9月27日木曜日

テーブルに存在しないカラムをActiveRecordに追加するサンプル


「正しいMVCではModelが太る」と言われている。

俺が勝手に考える正しいMVCの実装。モデルはデータAPI!
http://kyoro353.hatenablog.com/entry/20111223/1324589389

Railsでは具体的にどうすれば良いだろうか?
apps/modelsフォルダ内の ActiveRecord を継承したクラスをカスタマイズすることだろうか。

初めの一歩として、テーブルに存在しないカラムを ActiveRecord に追加してみる。

▽現状のMigrationクラス
class CreateOnecolumnmodels < ActiveRecord::Migration
  def change
    create_table :onecolumnmodels do |t|
      t.string :name

      t.timestamps
    end
  end
end
▽現状のActiveRecordクラス
class Onecolumnmodel < ActiveRecord::Base
end
▽現状の表示結果

試しに、ActiveRecord クラスに attribute を追加してみるが、うまくいかない。

▽attributeを追加したActiveRecordクラス
class Onecolumnmodel < ActiveRecord::Base

  def initialize
    @secondname = "2nd name"
  end

  attr_accessor :secondname
end
▽追加したattributeを表示するshow.html.erb
<p id="notice"><%= notice %></p>

<p>
  <b>Name:</b>
  <%= @onecolumnmodel.name %>
</p>

<p>
  <b>Second Name:</b>
  <%= @onecolumnmodel.secondname %>
</p>


<%= link_to 'Edit', edit_onecolumnmodel_path(@onecolumnmodel) %> |
<%= link_to 'Back', onecolumnmodels_path %>
どうも ActiveRecord ではアクセサが機能しないようだ。
下記のように関数として実装したらうまくいった。

▽関数を追加したActiveRecordクラス
class Onecolumnmodel < ActiveRecord::Base

  def secondname
    return "2nd name"
  end
end
▽関数追加後の表示結果

2012年9月25日火曜日

Processing.js で作成した画像をJavaScriptから操作する最小サンプル


Processing.js で生成する画像は、Processing の文法で描画できる。
一度作成した画像を JavaScript で操作する最小サンプルを作ってみた。

▽作成したHTML
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
    <script src="processing-1.4.1.js" type="text/javascript"></script>
    <script type="text/processing" data-processing-target="mycanvas">
    void setup() {
      size(200, 200);
      noLoop();
    }
    void draw() {
      background(0);
      fill(255);
      rect(10, 10, 20, 20);
    }
    void moveRect() {
      background(0);
      fill(255);
      rect(random(180), random(180), 20, 20);
    }
    </script>
  </head>
  <body>
    <p>
    <canvas id="mycanvas"></canvas>
    </p>
    <button onClick="moveRect()">Move!</ button>

    <script id="script1" type="text/javascript">
    function moveRect() {
      var p = Processing.getInstanceById("mycanvas");
      p.moveRect();
    }
    </script>

  </body>
</html>

▽実行結果

Move! ボタンを押すと白い四角形がランダムに移動する。

Processing.js の最小サンプル

画像処理プログラミング言語の Processing を JavaScript から利用できる Processing.js の最小サンプルを作ってみた。

▽作成したHTML
<pre class="prettyprint"> 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
    <script src="processing-1.4.1.js" type="text/javascript"></script>
    <script type="text/processing" data-processing-target="mycanvas">
    void setup() {
      size(200, 200);
    }
    void draw() {
      background(0);
      fill(255);
      rect(10, 10, 20, 20);
    }
    </script>
  </head>
  <body>
    <p>
    <canvas id="mycanvas"></canvas>
    </p>
  </body>
</html>
</pre>

▽実行結果



2012年9月22日土曜日

Railsのテーブルにカラム名"type"は使えない。

Railsのテーブルにカラム名"type"を使うとエラーが発生するようだ。

仕方がないので下記のMigrationファイルを使って解決した。

class Moretinymap < ActiveRecord::Migration
  def up
    add_column :moretinymaps, :category, :integer
  end

  def down
    remove_column :moretinymaps, :type, :integer
  end
end

2012年9月16日日曜日

Ruby on RailsのRJSを利用したXMLHttpRequestの最小サンプル:その2

XMLHttpRequestを使えるようにRailsが作成したファイルを変更してみる。

▽tests_controller.rb の変更

変更前
  # GET /tests/1
  # GET /tests/1.json
  def show
    @test = Test.find(params[:id])

    respond_to do |format|
      format.html # show.html.erb
      format.json { render json: @test }
    end
  end
変更後
  # GET /tests/1
  # GET /tests/1.json
  def show
    @tests = Test.find(:all)
    @test = Test.find(params[:id])
    render

  end
▽show.html.erbの変更

変更前
<p id="notice"><%= notice %></p>

<p>
  <b>Name:</b>
  <%= @test.name %>
</p>


<%= link_to 'Edit', edit_test_path(@test) %> |
<%= link_to 'Back', tests_path %>
変更後
<p id="notice"><%= notice %></p>

<ul id='tests'>
  <%= render({:partial => "test", :collection => @tests}) %>
</ul>

<p>
  <b>Name:</b>
  <span id="test_name">AAAAA</span>
</p>


<%= link_to 'Edit', edit_test_path(@test) %> |
<%= link_to 'Back', tests_path %>
▽_test.html.erbの新規追加
<li><%= link_to test.name, test_path(test)+".js", :remote => true %></li>
▽show.js.erbの新規追加
$("#test_name").html("<%= @test.name %>");
▽サンプルデータの表示の確認

初期状態

クリック後

"XXYYZZ"のリンクをクリックすると、再読み込みなしで、Nameが "XXYYZZ"に変更された。

Ruby on RailsのRJSを利用したXMLHttpRequestの最小サンプル:その1

まずは Ruby on Rails の準備から。
Windows上の RailsInsaller を利用してみた。

▽scaffoldの実行
C:\Sites\ajaxsample\script>rails generate scaffold test name:string
      invoke  active_record
      create    db/migrate/20120914020008_create_tests.rb
      create    app/models/test.rb
      invoke    test_unit
      create      test/unit/test_test.rb
      create      test/fixtures/tests.yml
       route  resources :tests
      invoke  scaffold_controller
      create    app/controllers/tests_controller.rb
      invoke    erb
      create      app/views/tests
      create      app/views/tests/index.html.erb
      create      app/views/tests/edit.html.erb
      create      app/views/tests/show.html.erb
      create      app/views/tests/new.html.erb
      create      app/views/tests/_form.html.erb
      invoke    test_unit
      create      test/functional/tests_controller_test.rb
      invoke    helper
      create      app/helpers/tests_helper.rb
      invoke      test_unit
      create        test/unit/helpers/tests_helper_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/tests.js.coffee
      invoke    scss
      create      app/assets/stylesheets/tests.css.scss
      invoke  scss
   identical    app/assets/stylesheets/scaffolds.css.scss
これでコントローラーを含めた必要なファイル一式を作成してもらえる。

▽DBマイグレーションの実行
C:\Sites\ajaxsample\script>rake db:migrate
(in C:/Sites/ajaxsample)
==  CreateTests: migrating ====================================================
-- create_table(:tests)
   -> 0.0940s
==  CreateTests: migrated (0.0960s) ===========================================
▽初期状態の表示の確認
http://localhost:3000/tests


▽サンプルデータの作成
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html

one:
  name: XXYYZZ
▽サンプルデータの登録
C:\Sites\ajaxsample\script>rake db:fixtures:load
(in C:/Sites/ajaxsample)
▽サンプルデータの表示の確認
http://localhost:3000/tests

今回はここまで。

2012年9月15日土曜日

jQueryを利用したXMLHttpRequestの最小サンプル

jQueryを利用したXMLHttpRequestの最小サンプルを作ってみた。

▽HTML側
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
    <script type="text/javascript" src="jquery.js"></script>
    <script type="text/javascript">
      $("body").ready(function(){
        $("#disp").load("test.xml");
      })
    </script>
  </head>
  <body>
    <div id="disp"></div>
  </body>
</html>
▽取得するXML
<?xml version="1.0" encoding="UTF-8"?>
<test>GHIJK</test>
▽実行結果
GHIJK
同じことをやるのにフレームワークなしのJavaScriptの場合よりもずいぶん短くなった。

2012年9月2日日曜日

XMLHttpRequestの最小サンプル

XMLHttpRequestの最小サンプルを作ってみた。

▽HTML側
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    
    <script type="text/javascript">
    //<![CDATA[
    function onLoad() {
      var xmlhttp = false;
      if(typeof ActiveXObject != "undefined"){
        try {
          xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
        } catch (e) {
          xmlhttp = false;
        }
      }
      if(!xmlhttp && typeof XMLHttpRequest != "undefined") {
        xmlhttp = new XMLHttpRequest();
      }

      xmlhttp.open("GET", "./test.xml");
      xmlhttp.onreadystatechange = function() {
        if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
          var disp = document.getElementById("disp");
          var xmlDoc = xmlhttp.responseXML;
          disp.innerHTML = xmlDoc.getElementsByTagName('test')[0].firstChild.nodeValue;
        }
      }
      xmlhttp.send(null);
    }
    //]]>
    </script>
  </head>
  <body onload="onLoad()">
    <div id="disp">
</div>
</body>
</html>
▽取得するXML
<?xml version="1.0" encoding="UTF-8"?>
<test>ABCDEF</test>
▽実行結果
ABCDEF
▽課題
1.  ローカルのフォルダにファイルを置いただけでは動かない。Webサーバへのアップロードが必要。
2. XMLの値を変更してHTMLファイルをリロードしても、キャッシュのせいで値が更新されない。