Why?
なぜ今さら Rfm?と思うでしょうが、今回は、FileMakerのカスタムWeb公開機能でデータベース単体として使えば(トラフィック制限はあるけど)接続クライアント数のライセンス費用はかからないので、Railsとの組み合わせはどんなもんなのか、この眼で確かめてみたかったというのが目的でした。RESTでやってみたかったけれど、Railsに匹敵するようなフレームワークを探せなかった。
コメント欄参照していただきたいのですが、HAZIさんより情報いただきました。Rfm/ginjo はプルリクに反応なく、ruby2.6以降で警告多発し、FileMakerServer19で不安定とのことです。私もこの記事での動作確認までで、これ以上のことはしていません。
画面サンプル
何の特徴もない画面ですが、自分には動いたということがまずささやかな実績。
一覧画面(デフォルト表示画面) |
詳細画面(一覧でタイトルをクリックした後の画面) |
編集画面(詳細画面で編集ボタンをクリックした後の画面) |
編集(スクロール後の状態) |
検索結果(一覧画面で検索ボタンをクリックした後の画面) |
Rails
Railsは初だったのでこの本で勉強。
※リンクは正誤表などが掲載されているサポートサイトです。
Rails5.2対応とあり、実際はRails6でやろうとしてたのでパスかなと思いましたが、本を手にとって見ると、共通化やチーム作業などに触れられており、全体のバランスが良さそうだったのでこれに決めました。Bootstrap もこの本の手順に沿って入れました。
Rails 6.0.2.2
Rfm/ginjo
ginjo/rfm 3.08
github https://github.com/ginjo/rfm
"Latest commit on 16 Dec 2017 " とあるので、2年ほど前が最後のコミットですね。
issueが20件あります。私も1件あげました。検索の結果が0件の場合に割り込みが発生しないという報告と改善案です。(ginjoさん見てくれるだろうか。)
Download & Installationの通りに実行してインストール。
gem install 'ginjo-rfm'
FileMaker Server
FileMaker Server 18.0.4
もう19が出てしまいましたが。
ポート3000番をFileMakerServerで使っているので、Railsはポート変えて立ち上げる必要があります。
ここではFileMaker Serverについてのインストール解説はしません。知ってる前提。
AdminConsoleの例 |
データベースの例 |
environment.rb
# Load the Rails application.
require_relative 'application'
# Initialize the Rails application.
Rails.application.initialize!
# FileMaker Server Object config
FM_CONFIG = {
:host => "127.0.0.1",
# :port => 16014,
:port => 16020,
:account_name => "admin",
:password => "XXXXXXXXXX",
:database => "FMServer_Sample",
:raise_on_401 => true,
:ssl => false
}
models
art.rb
require 'active_model'
class Art < Rfm::Base
config :layout => 'English_Form_View',
:host => "127.0.0.1",
:port => 16020,
:account_name => "admin",
:password => "XXXXXXXXXX",
:database => "FMServer_Sample",
:raise_401 => true,
:ssl => false,
:FMSdomain => "http://localhost"
end
views
list.html.slim
h1 一覧
.mb-3
.nav.justify-content-end
= form_tag(:action => :search, :id => params[:id]) do
.form-inline
.div.form-row
= text_field_tag "words[Search]", "", {class: 'form-control'}
|
= submit_tag "検索", class: 'btn btn-primary'
.mb-3
= link_to '新規登録', {:action => :new} , class: 'btn btn-primary'
.mb-3
table.table.table-hover
thread.thead-default
tr
th= "Title"
th= "Cover Photo Credit"
th= "Status"
th[align="right"]= "Quantity in Stock"
tbody
- @records.each_with_index do |record,i|
tr
td= link_to((record["Title"]), :action => :show, :id => record.record_id)
td= record["Cover Photo Credit"]
td= record["Status"]
td[align="right"]= record["Quantity in Stock"].to_i
show.html.slim
h1 詳細
.mb-3
.nav.justify-content-end
= link_to '一覧', {:action => :list}, class: 'btn btn-primary'
.mb-3
table.table.table-hover
tbody
tr
th= "Img"
td= image_tag @record["Img"] ,:width => '256' if @record["Img"]
tr
th= "Title"
td= h @record["Title"]
tr
th= "Status"
td= h @record["Status"]
tr
th= "Author"
td= h @record["Author"]
tr
th= "Publisher"
td= h @record["Publisher"]
tr
th= "Photo Credit"
td= h @record["Cover Photo Credit"]
tr
th= "Quantity in Stock"
td= h @record["Quantity in Stock"].to_i
tr
th= "Number of Pages"
td= h @record["Number of Pages"].to_i
tr
th= "Description"
td= simple_format(h(@record["Description"]), {}, sanitize: false, wrapper_tag: "div")
= link_to '編集', {:action => :detail, :id => @record.record_id}, class: "btn btn-primary mr-3"
detail.html.slim
h1 編集
.nav.justify-content-end
= link_to "一覧", {:action => :list}, class: "btn btn-primary"
.mb-3
table.table.table-hover
tbody
tr
th= "Img"
td= image_tag @record["Img"] ,:width => '256' if @record["Img"]
= form_with model: @art, url: {controller: 'art', action: 'save', :id => params[:id] } do |f|
.form-group
= f.label :Title
= f.text_field :Title, class: 'form-control', id: 'art_Title', value: "#{h @record["Title"]}"
.form-group
= f.label :Status
= f.text_field :Status, class: 'form-control', id: 'art_Status', value: "#{h @record["Status"]}"
.form-group
= f.label :Author
= f.text_field :Author, class: 'form-control', id: 'art_Author', value: "#{h @record["Author"]}"
.form-group
= f.label :Publisher
= f.text_field :Publisher, class: 'form-control', id: 'art_Publisher', value: "#{h @record["Publisher"]}"
.form-group
= f.label :'Cover Photo Credit'
= f.text_field :'Cover Photo Credit', class: 'form-control', id: 'art_Cover Photo Credit', value: "#{h @record["Cover Photo Credit"]}"
.form-group
= f.label :'Quantity in Stock'
= f.text_field :'Quantity in Stock', class: 'form-control', id: 'art_Quantity in Stock', value: "#{h @record["Quantity in Stock"]}"
.form-group
= f.label :'Number of Pages'
= f.text_field :'Number of Pages', class: 'form-control', id: 'art_Number of Pages', value: "#{h @record["Number of Pages"]}"
.form-group
= f.label :'Description'
= f.text_area :'Description', class: 'form-control', id: 'art_Description', value: "#{h @record["Description"]}"
= f.submit nil, class: 'btn btn-primary'
|
= link_to 'キャンセル', {:action => :list }, class: 'btn btn-secondary'
|
= link_to "削除", {:action => :destroy ,:id => params[:id]}, class: "btn btn-danger",data: {confirm: "削除しますか?"}
search.html.slim
h1 一覧
.mb-3
.nav.justify-content-end
= form_tag(:action => :search, :id => params[:id]) do
.form-inline
.div.form-row
= text_field_tag "rec[Title]", "", {class: 'form-control'}
|
= submit_tag "検索", class: 'btn btn-primary'
.mb-3
= link_to '新規登録', {:action => :new} , class: 'btn btn-primary'
.mb-3
table.table.table-hover
thread.thead-default
tr
th= "Title"
th= "Cover Photo Credit"
th= "Status"
th[align="right"]= "Quantity in Stock"
tbody
- @records.each_with_index do |record,i|
tr
td= link_to((record["Title"]), :action => :show, :id => record.record_id)
td= record["Cover Photo Credit"]
td= record["Status"]
td[align="right"]= record["Quantity in Stock"].to_i
new.html.slim
h1 新規作成
.nav.justify-content-end
= link_to '一覧', {:action => :list}, class: 'btn btn-primary'
= form_with model: @art, url: {controller: 'art', action: 'create' } do |f|
.form-group
= f.label :Title
= f.text_field :Title, class: 'form-control', id: 'art_Title', value: 'タイトル'
.form-group
= f.label :Status
= f.text_field :Status, class: 'form-control', id: 'art_Status', value: '状況'
.form-group
= f.label :Author
= f.text_field :Author, class: 'form-control', id: 'art_Author', value: '作者'
.form-group
= f.label :Publisher
= f.text_field :Publisher, class: 'form-control', id: 'art_Publisher', value: '提供者'
.form-group
= f.label :'Cover Photo Credit'
= f.text_field :'Cover Photo Credit', class: 'form-control', id: 'art_Cover Photo Credit', value: '提供'
.form-group
= f.label :'Quantity in Stock'
= f.text_field :'Quantity in Stock', class: 'form-control', id: 'art_Quantity in Stock', value: '在庫'
.form-group
= f.label :'Number of Pages'
= f.text_field :'Number of Pages', class: 'form-control', id: 'art_Number of Pages', value: 'ページ数'
.form-group
= f.label :'Description'
= f.text_area :'Description', class: 'form-control', id: 'art_Description', value: '詳細'
= f.submit nil, class: 'btn btn-primary'
|
= link_to 'キャンセル', {:action => :list }, class: 'btn btn-secondary'
controllers
art_controller.rb
require "rfm"
class ArtController < ApplicationController
def list
begin
# flash[:notice] = ""
if params[:id] == nil && ( params[:Search] == nil || params[:Search] == "")
@records = Art.all
elsif params[:id] != nil && ( params[:Search] == nil || params[:Search] == "")
@records = Art.all :sort_field => params[:id]
elsif params[:Search] != ""
@records = Art.find "TextForSearch" => params[:Search]
flash[:notice] = "'#{params[:Search]}' で検索しました。"
end
rescue Rfm::Error::NoRecordsFoundError
flash[:notice] = "'#{params[:Search]}' で検索しましたが、対象は0件でした。"
@records = Art.all
end
end
def search
redirect_to :action => :list, :words["Search"] => search_params
end
def new
@art = Art.new
end
def create
begin
Art.create( edit_params.to_h )
flash[:notice] = "レコードを作成しました。"
@records = Art.all
redirect_to :action => :list
rescue Rfm::Error::ValidationError
render_text "A record with id '#{params[:id]}' ValidationError.", :status => 0
end
end
def show
begin
@record = Art.find params[:id]
@record["Img"] = Art.config[:FMSdomain].to_s + ":" + Art.config[:port].to_s + "#{@record["Img"]}"
if @record == nil
flash[:notice] = "該当レコードがありません。"
@records = Art.all
redirect_to :action => :list
end
rescue Rfm::Error::NoRecordsFoundError
render_text "A record with id '#{params[:id]}' could not be found.", :status => 401
end
end
def detail
begin
@art = Art.new
@record = Art.find(params[:id])
@record["Img"] = Art.config[:FMSdomain].to_s + ":" + Art.config[:port].to_s + "#{@record["Img"]}"
rescue Rfm::Error::RecordMissingError
render_text "A record with id '#{params[:id]}' could not be found.", :status => 404
end
end
def save
Art.edit params[:id], edit_params
flash[:notice] = "更新しました。"
redirect_to(:action => :detail, :id => params[:id])
end
def destroy
begin
@record = Art.find params[:id]
Art.delete params[:id]
flash[:notice] = "レコードを削除しました。"
rescue Rfm::Error::RecordMissingError
flash[:notice] = "レコードを削除できませんでした。"
end
redirect_to :action => :list
end
private
def edit_params
params.require(:art).permit(:"Title", :"Status", :"Author", :"Publisher", :"Cover Photo Credit", :"Quantity in Stock", :"Number of Pages", :"Description", :"Img")
end
def search_params
params.require(:words).permit(:"Search")
end
end
感想
初めてのRails体験は率直に面白かった。MVCの思想はシステムの構成だけでなく,開発者のチーム編成、役割分担にもシンクロするだろう。
FileMakerの使い方としては、データ参照のみの人がたくさんいる場合にブラウズはRailsで、データ入力、加工、運用する人がごく一部ならFileMakerのクライアントまたはユーザーライセンスを使う形態といったところでしょうか。
しかし、これだけの基本的な動きも、FileMakerをそのまま使うならもっと簡単ですね。改めてFileMakerの強力な機能を思い知りました。
参考にした情報
Rfm
- Quiita:FileMaker API for Ruby (ginjo-rfm) を初めて触る人のために
- Quiita:FileMaker API for Ruby (Rfm) について
- @corselia 様
- やはり動いたという実績はありがたいです。
- Hatena:Rfm 1.4をインストールするには
- matsuo_atsushi 様
- lardawge版Rfmですが比較できてありがたかったです。
ありがとうございました!!!
以上
つい最近 Rfm を捨てたので思わずコメントします。
返信削除Rfm 開発止まっててPR投げてもマージしてくれない状況です(そんな状況なのでruby 2.6 以降では警告が大量に出ます)。あと、FileMaker Server 19 では Custom Web(XML) は微妙に不安定です。一定量を超えるリクエストを投げるとステータス200なのにデータが空で返ってきて Rfm がエラーはきます。
なので、業務で使う場合はもう容量制限はありますが Data API (JSON) を使うしかないのかなという状況です。fmrest gem を使うと Rfmと似たような感じでData APIを扱えます。
https://github.com/beezwax/fmrest-ruby
あ~そうですか。残るはRESTしかないか。本文にも注意書きしておきます。ありがとうございました。
返信削除