model のインスタンスを hash化する
使う機会はあまり無い気もするが、メモしておく*1
結論から書くと、モデルインスタンス#attributes とするだけ。
model_instance = Model.new attributes_hash = model_instance.attributes # {"attr1" => "val1", "attr2" => "val2", ...} p attributes_hash.class # --> Hash
どんなとこで使ったのかというと、
同じ名前のカラムを持っている2つの異なるモデルがあるとする。
そのカラムを一方からもう一方にコピーしたいときに、どうやるのがスマートかな、という話し。
# name, attr1 を持つモデルA と name, attr1, attr2 を持つモデルB があったとする model_a = ModelA.first model_b = ModelB.new # 1つ1つ入れるのは、カラム数が少なければいいけど、多いと・・・ model_b.name = model_a.name model_b.attr1 = model_a.attr1 # ループもいまいち・・ ModelA.column_names.each do |attr_name| model_b[attr_name] = model_a[attr_name] end # attributes なら一発 model_b = ModelB.new(model_a.attributes)
まぁ、そもそもこんなコピーをしないといけないモデル設計が微妙な可能性もあり。。。
ちなみに、上では「いまいち」とかって書いちゃってるけど、
Model.column_names で全カラム名の入った配列が取れるのも最初は知りませんでしたorz...
p ModelA.column_names # --> ["name", "attr1"]
ActiveSalesforce を使ってみたメモ
http://activesfdc.rubyforge.org/ を少し使う機会があったので、そのメモを。
ちなみに、Salesforce の知識はほぼ無い状態で触ってますのであしからず・・・。
なお、rails のバージョンは 2.3.5 です。
Setup と HelloWorld みたいなもの
1. install
公式の通りに
$ gem install activerecord-activesalesforce-adapter
とすると、activerecord-activesalesforce-adapter-2.0.0 がインストールされます*1が、
これだとrails 2.3.5ではパッチ(http://timothynjones.wordpress.com/2008/12/03/patching-activesalesforce-for-rails-222/)を当てないと正しく動きません*2。
git(GitHub - oldfartdeveloper/activerecord-activesalesforce-adapter: An ActiveRecord adapter for the Salesforce.com API)には、
gem install で入るものより新しい activerecord-activesalesforce-adapter-2.2.2 があり、
こちらの場合はパッチを当てる必要はありませんでした。
インストール方法は、上記ページでも確認できますが、
$ git clone git://github.com/oldfartdeveloper/activerecord-activesalesforce-adapter.git
$ cd activerecord-activesalesforce-adapter/
$ gem build activerecord-activesalesforce-adapter.gemspec
$ gem install activerecord-activesalesforce-adapter-2.2.2.gem
これでインストールできます。
2. config/database.yml
development: adapter: activesalesforce username: <salesforce-username> password: <salesforce-password><salesforce-security_token>
password は、「salesforceにログインするときのパスワード」と「セキュリティトークン」を隙間無く並べた文字列です。
また、gitのページでは上記の他に「url:」の項目が説明されていますが、これは記述しなくても動きます。
記述の有無による違いはわかりませんorz
3. モデルを作ります
$ ruby script/generate model Account ―skip-migration
公式でもgitでも書いてない気がしますが、作らないと動きませんでした。
(作らなくても良い方法があるなら教えて下さいorz)
4. 適当なコントローラで Account.all とかやるとデータが取れます
リレーションの無いfind
Salesforce はAPIで叩く際には、カラム名が「__c」付きになるっぽいので、そこは気をつける必要があります。
# column_a と column_b で引っかける Account.find(:first, :conditions => ["column_a__c = ? AND column_b__c = ?", param_a, param_b]) # こっちの書き方でも動きます Account.find_by_column_a__c_and_column_b__c(param_a, param_b)
で、ここがちょっと微妙なところですが、単に Account.all としても、最大200件しか取ってきません。
でも、limit を大きく設定してあげると200件以上でも取ってきてくれます。
(内部的には、200件ずつループを回して取ってきているようですが。)
# Account に500件のデータが入っていた場合 account = Account.all p account.length # --> 200 account = Account.find(:all, :limit => 1000) p account.length # --> 500
リレーション(belongs_to, has_many)
belongs_to, has_many は普通に使えます。
しかも、自分でモデルに書かなくても、勝手に作ってくれます。
# log/development.log Processing AccountsController#index (for ***.***.***.*** at 2010-06-01 13:24:35) [GET] Created one-to-one relationship 'master_record' from Account to Account using master_record_id Created one-to-one relationship 'parent' from Account to Account using parent_id Created one-to-one relationship 'owner' from Account to User using owner_id Created one-to-one relationship 'created_by' from Account to User using created_by_id Created one-to-one relationship 'last_modified_by' from Account to User using last_modified_by_id Created one-to-many relationship 'account_contact_roles' from Account to AccountContactRole using account_id Created one-to-many relationship 'histories' from Account to AccountHistory using account_id ...
見方としては、
Created one-to-<has_manyならmany / belongs_toならone> relationship '<リレーション名>' from <リレーション元モデル名> to <リレーション先モデル名> using <foreign key>
なので、
例えば、上から3つ目の「Created one-to-one relationship 'owner' from Account to User using owner_id」は、
class Account < ActiveRecord::Base belongs_to :owner, :class_name => "User", :foreign_key => "owner_id" end
と同じで、Account.first.owner とかで使えます。
また、一番下の「Created one-to-many relationship 'histories' from Account to AccountHistory using account_id」は、
class Account < ActiveRecord::Base has_many :histories, :class_name => "AccountHistory", :foreign_key => "account_id", :dependent => :nullify end
と同じで、やはり Account.first.histories で使えます。
上のような勝手に作ってくれるリレーションも使えますが、
自分でモデルに書いても正しく動きます。
(foreign_key などにはやはり API名を指定する必要があります)
ただし、SOQLでは、「モデル名__r.カラム名」とかでリレーション先のカラムを条件式に入れたりできるようですが、
これはActiceSalesforceでは使えないようです?*3
例えば、Account と MyCustomObj に 1 対 多 のリレーションが張られているとします。
Account の column1 が 指定した値の MyCustomObj を取得したい場合、
SOQL的にconditions を書くとたぶん下のようになります。
(Salesforce全然わかってないので、間違ってたらごめんなさい。。。)
MyCustomObj.find(:all, :conditions => ["MyCustomObj__r.column1 = ?", column1_param])
でも、これは動きません。
ライブラリの中身を少し見ましたが、別テーブルへのエイリアスは削除しているっぽいです。
# /usr/local/lib/ruby/gems/1.8/gems/activerecord-activesalesforce-adapter-2.2.2/lib/active_record/connection_adapters/activesalesforce_adapter.rb の 308, 309行目 # strip away any table alias column_name.sub!(/\w+\./, '')
この column_name に上でいうと「MyCustomObj__r.column1」が入ってくるので、
column_name はただの「column1」になり、MyCustomObj には column1 というカラムはないので、
そんなカラム無いよということでエラーになってしまいます。
Column not found for #{column_name}!
これが上手いこと動くととても使いやすい気がするんですがね。。。
insert
これも普通に動きます。
newして値をセットして、saveするだけです。
(値をセットする際に、各カラム名をAPI名にしないといけない点だけ注意が必要です)
account = Account.new
account.column1__c = value1
account.column2__c = value2
...
account.save!
とりあえず、実際に使ってみたのはこれくらいですが、
「update」とか「delete」も定義されてるので、同様に使えるのではないかと思います。
参考
- ActiveSalesforce 公式: http://activesfdc.rubyforge.org/
- ActiveSalesforce patch for rails2.3.5: http://timothynjones.wordpress.com/2008/12/03/patching-activesalesforce-for-rails-222/
- 参考ブログ1: http://htakeuchi.offtheball.jp/archives/18
- 参考ブログ2: 脈絡のない備忘録: ActiveSalesforce
- Salesforce – Web Services API Developer’s Guide: Salesforce Developers
- git: GitHub - oldfartdeveloper/activerecord-activesalesforce-adapter: An ActiveRecord adapter for the Salesforce.com API
html5 の File API を使って、ローカルのテキストファイルを読み込んでみる
とっても簡単です(Firefox 3.6 で動作を確認)。
html
fileのアップロードフォームにonchangeを仕込んでおきます。
あとは、読み込んだファイルの内容を表示するtextareaを用意。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <input type="file" onchange="read(this)" /> <hr /> <textarea id="text" cols="80" rows="20" wrap="off"></textarea> </body> </html>
javascript
function read (ele) { if (!ele.files.length) return; var file = ele.files[0]; if (!/^text\//.test(file.type)) return; // text/plain, text/html, ... var fr = new FileReader(); fr.onload = function () { document.getElementById('text').value = fr.result; // 読み込み結果をtextareaに }; fr.readAsText(file); // ファイルをテキストとして読み込む }
readAsTextの2つ目の引数で文字コードを指定できます*1が、
指定しなくても上手いこと推定してくれるっぽいです。
(UTF-8, SJIS, EUC は普通に読み込めました。)
file フォームに multiple をセットしておけば、
複数ファイルの中身をマージした結果とかも簡単に出せちゃいそうですね
html5の File API を使って、アップロード無しで画像プレビュー
画像をプレビューするために、サーバへアップロードする必要がなくなります*1。
html
fileのアップロードフォームにonchangeを仕込んでおきます。
あとは、プレビュー表示用の要素だけ準備。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <form> <input type="file" name="file" onchange="preview(this)" /> </form> <hr /> <b>preview:</b><br /> <div id="preview_field"></div> </body> </html>
javascript
function preview(ele) { if (!ele.files.length) return; // ファイル未選択 var file = ele.files[0]; if (!/^image\/(png|jpeg|gif)$/.test(file.type)) return; // typeプロパティでMIMEタイプを参照 var img = document.createElement('img'); var fr = new FileReader(); fr.onload = function() { img.src = fr.result; // 読み込んだ画像データをsrcにセット document.getElementById('preview_field').appendChild(img); } fr.readAsDataURL(file); // 画像読み込み // 画像名・MIMEタイプ・ファイルサイズ document.getElementById('preview_field').innerHTML = 'file name: ' + file.name + '<br />' + 'file type: ' + file.type + '<br />' + 'file size: ' + file.size + '<br />'; }
スバラシイ。
html5で複数ファイルアップロード&保存
とても簡単。
html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <form action="save.cgi" method="POST" enctype="multipart/form-data"> <input type="file" name="files" multiple /> <input type="submit" value="submit" /> </form> </body> </html>
input type="file" で multiple属性を追加するだけ。
オブジェクトのプロパティの存在確認方法メモ
1.単純に obj.property で確認すると・・・
var obj = {}; if (obj.hoge) alert('not exist'); obj.hoge = false; if (obj.hoge) alert('exist false'); obj.hoge = null; if (obj.hoge) alert('exist null'); obj.hoge = undefined; if (obj.hoge) alert('exist undefined'); obj.hoge = ""; if (obj.hoge) alert('exist ""'); obj.hoge = 0; if (obj.hoge) alert('exist 0'); obj.hoge = NaN; if (obj.hoge) alert('exist NaN');
1番上のif以外は、hogeプロパティが存在しているので、
alert が表示されて欲しいが、1つも出ない
2.undefined かどうかで判定
基本的に、プロパティが存在しない場合には undefined が返ってくるので、
var obj = {}; alert(typeof obj.hoge); // "undefined"
「undefined でない」イコール「プロパティが存在している」とするのであれば、
var obj = {}; if (obj.hoge !== undefined) alert('not exist'); obj.hoge = false; if (obj.hoge !== undefined) alert('exist false'); obj.hoge = null; if (obj.hoge !== undefined) alert('exist null'); obj.hoge = undefined; if (obj.hoge !== undefined) alert('exist undefined'); obj.hoge = ""; if (obj.hoge !== undefined) alert('exist ""'); obj.hoge = 0; if (obj.hoge !== undefined) alert('exist 0'); obj.hoge = NaN; if (obj.hoge !== undefined) alert('exist NaN');
これで、false, null, "", 0, NaN を値として取るプロパティも判定可能
(typeof obj.hoge !== "undefined" でもOK)
ただ、当然ながら undefined を値として取るプロパティは判定不可能
3.undefined を値として取るプロパティも判定
var obj = {}; if ("hoge" in obj) alert('not exist'); obj.hoge = false; if ("hoge" in obj) alert('exist false'); obj.hoge = null; if ("hoge" in obj) alert('exist null'); obj.hoge = undefined; if ("hoge" in obj) alert('exist undefined'); obj.hoge = ""; if ("hoge" in obj) alert('exist ""'); obj.hoge = 0; if ("hoge" in obj) alert('exist 0'); obj.hoge = NaN; if ("hoge" in obj) alert('exist NaN');
とすると、false, null, undefined, "", 0, NaN の存在確認が可能
4.おまけ
2の例で「!==」ではなく「!=」とすると、
var obj = {}; if (obj.hoge != undefined) alert('not exist'); obj.hoge = false; if (obj.hoge != undefined) alert('exist false'); obj.hoge = null; if (obj.hoge != undefined) alert('exist null'); obj.hoge = undefined; if (obj.hoge != undefined) alert('exist undefined'); obj.hoge = ""; if (obj.hoge != undefined) alert('exist ""'); obj.hoge = 0; if (obj.hoge != undefined) alert('exist 0'); obj.hoge = NaN; if (obj.hoge != undefined) alert('exist NaN');
null が外れて、false, "", 0, NaN のみになる
public配下の任意の静的ファイルへのURLを作成するメモ
ローカルでは、http://localhost:3000/ で開発をしているが、
本番サーバで動かすときは、http://domain/prefix/ で動かしたい場合がある。
viewでリンクなどのURLを直書きしていると、相対パスなら大丈夫かもしれないが、
絶対パスは prefix があるせいで、本番サーバに持って行ったときに意図する動作(URL)にならない。
# viewファイル(直書きの例) <a href="/controller/action">link</a> <script type="text/javascript" src="/javascripts/prototype.js"></script>
上の例のように、aタグリンクであったり、
public配下であっても javascripts,stylesheets,images 下のファイルであれば、
railsが用意してくれている link_to, url_forや、
javascript_include_tag, stylesheet_link_tag, image_tag などを使うことで、
prefixの有無に関わらずrailsがよろしくURLを作ってくれる。
# viewファイル <%= link_to "link", :controller => "controller", :action => "action" %> <%= javascript_include_tag "prototype" %>
でも、それ以外の静的ファイルのURLに関して、どうするのがいいかちょっと困ったのでメモする。
# viewファイル # 例えば、public/swf/example.swf とか <embed src="/swf/examle.swf">
結論としては、当たり前のことかもしれないけど、
root_path を取得して、public配下のパスをくっつけるという方法しか思いつかなかった。
で、root_pathの取得の仕方は、 config/routes.rb で map.root を設定しているなら、
そのまま root_path で参照できる。
# viewファイル <embed src="<%= "#{root_path}swf/examle.swf" %>">
これで、root_path には、ローカルなら '/' が、
本番サーバなら '/prefix/' が入るので、どちらの環境でも正しく動作する。
config/routes.rb で map.root を設定していない場合は、
次のような helper を作ると、同じことができる。
# helperファイル def my_root_path return "#{ActionController::Base.relative_url_root}/" end # viewファイル <embed src="<%= "#{my_root_path}swf/examle.swf" %>">
どうせなら、
# helperファイル def url_for_public(path) return "#{ActionController::Base.relative_url_root}/#{path}" end # viewファイル <embed src="<%= url_for_public "swf/examle.swf" %>">
ここまでやった方がきれいかもしれない。
(※) ちなみに、railsのversionは 2.3.5 です。
古いversionだと、ActionController::Base.relative_url_root ではなく、
request.relative_url_root で参照するようです。