このブログにAmazonのリンクを貼りたくなったので、ASINを入力するとAmazonから書籍のサムネイルや著者情報などを抜き出す機能を実装するために、Amazon Product Advertising APIを使ってみることにしました。

■ インストール

gemなので、コマンド一発です。

gem install amazon-ecs


■ 基本設定とAPI呼び出し

require 'amazon/ecs'

Amazon::Ecs.options = {
  :associate_tag => '[your associate tag]', 
  :AWS_access_key_id => '[your developer token]', 
  :AWS_secret_key => '[your secret access key]'
}

# API呼び出し
res = Amazon::Ecs.item_lookup("4088767624", :response_group => 'Small, ItemAttributes, Images', :country => 'jp')

# 返ってきたXMLを表示(res.doc.to_sでも多分OK)
puts res.marshal_dump


item_lookupメソッドに限りませんが、response_groupの設定は注意が必要です。Imagesを入れないとサムネイルのURLが、ItemAttributesを入れないと出版日などの情報が取れませんでした。

■ 情報の読込

適当に取り出しています。商品ページのURLがやたらと長くなってDBに入れたくない感じになったので、適当にURLを作ってしまいました。一応、タグが機能しているかAmazonアソシエイトのリンクの動作確認ツールで確認してあります。

amazon_tag = '[your associate tag]'

res.items.each do |item|
  element = item.get_element('ItemAttributes')
  
  data = {
    :asin => item.get('ASIN'), 
    :title => element.get("Title"), 
    :page_url => "http://www.amazon.co.jp/dp/#{item.get('ASIN')}?tag=#{amazon_tag}", 
    :isbn => element.get("ISBN"), 
    :author => element.get_array("Author").join(", "), 
    :product_group => element.get("ProductGroup"), 
    :manufacturer => element.get("Manufacturer"), 
    :publication_date => element.get("PublicationDate"), 
    
    # URL, Width, Heightの要素を持っている
    :small_image => item.get_hash("SmallImage"), 
    :medium_image => item.get_hash("MediumImage"), 
    :large_image => item.get_hash("LargeImage")
  }
end

# dataを表示する(省略)


■ 結果

以下のような感じで表示してみました。皇国の守護者ですな。古い作品ですが、なんで打ち切りになってしまったのか・・・。この作家が今描いているシュトヘルも面白いんだけど、このころの勢いにはなかなか追いつかないしなぁ・・・。

[書籍データの取得に失敗しました]

■ 感想

プログラム的に難しい個所はありませんが、Amazon Product Advertising APIをある程度把握しておく必要があるでしょう。

APIの呼び出し回数の制限(1時間2000回? 収益による増減もあるので少し難しい)を考慮してキャッシュしたり、ProductGroupによってとれる情報が異なる(例:本では著者が取れるが、ゲームでは取れないなど)ので、そういった差異を吸収するような表示を検討したりと、ちゃんとしたものを作る場合はそれなりに手間がかかるかと思われます。

■ 参考情報


Posted by あかさた
複数のアプリから参照するライブラリを書きたくなったので、勉強がてらgemを作ってみることにしました。gemテンプレートを生成して、処理を記述して、rspecでテストを書くところまでを書いています。

■ テンプレートの生成とgemspecの記述

以下でテンプレートの生成ができます。

bundle gem hello-akasata


hello-akasata.gemspecの内容です。TODOと書いてあるところを書き換えましょう。

# -*- encoding: utf-8 -*-
require File.expand_path('../lib/hello-akasata/version', __FILE__)

Gem::Specification.new do |gem|
  gem.authors       = ["akasata"]
  gem.email         = ["*@*.*"]
  gem.description   = %q{Test Gem.}
  gem.summary       = %q{Hello World!}
  gem.homepage      = ""

  gem.files         = `git ls-files`.split($\)
  gem.executables   = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
  gem.test_files    = gem.files.grep(%r{^(test|spec|features)/})
  gem.name          = "hello-akasata"
  gem.require_paths = ["lib"]
  gem.version       = Hello::Akasata::VERSION
  
  # 依存するgemがある場合
  # gem.add_dependency('log4r', '>= 1.0.5')
  gem.add_development_dependency('rspec')
end


lib/hello-akasata/version.rbの内容は以下の通りです。内容を変更したらここを変更しましょう。

module Hello
  module Akasata
    VERSION = "0.0.1"
  end
end


■ 処理の記述

lib/hello-akasata.rbの内容を書き換えてください。とりあえず、hello worldです。

require "hello-akasata/version"

module Hello
  module Akasata
    def self.hello
      "Hello akasata World!"
    end
  end
end


■ ビルド

rakeでビルドを行うことができます。

rake build # ビルドします
rake install # ビルドしてシステムにインストールします
rake release # これでRubyGems.orgに公開されます。今回はやってませんw


■ rspec

spec/spec_helper.rb

require 'rubygems'
require 'bundler/setup'
require 'hello-akasata'

RSpec.configure do |config|
  config.mock_framework = :rspec
end


spec/hello-akasata_spec.rb

require 'spec_helper'

describe Hello::Akasata, "::hello" do
  it "should return 'Hello akasata World!'" do
    Hello::Akasata.hello.should eq("Hello akasata World!")
  end
end


以下、rspecの実行です。

rspec


■ 参考文献

ASCIIcasts - “Episode 245 - Bundlerでgemを作る”
testing - Setup RSpec to test a gem (not Rails) - Stack Overflow

Posted by あかさた
Ruby(Rails)からメールを送信する必要のある仕事があったので、本採用するかは決めていませんが、Amazon SESをaws-sdk-rubyから使う方法について調査してみました。

■ 準備

1. マネジメントコンソールでEmailアドレスとドメインを登録してください
(ドメインの登録の際は、DKIMの設定も出力すると便利です。Easy DKIMという機能なのですが、ドメインの認証が完了した後、マネジメントコンソールで有効化すると、送信メールにDKIM署名を追加してくれます。迷惑メール扱いになる確率がいくらか減るでしょう。)

※ Request Production Accessするまでは、送信元、送信先ともに登録が必要です。

2. aws-sdk-rubyをインストールする

gem install aws-sdk


■ 手順

単純なので一気に書きます。

# encoding: utf-8
require 'rubygems'
require 'aws-sdk'
require 'nkf'

ses = AWS::SimpleEmailService.new(
  :access_key_id => 'YOUR_ACCESS_KEY_ID',
  :secret_access_key => 'YOUR_SECRET_ACCESS_KEY')

body_text = NKF.nkf '-jw', <<-EOS
こんにちは。
このメールはテストメールです。

おしまい
EOS

subject = NKF.nkf('-Mw', 'サブジェクトです')

from_email = "FROM_EMAIL_ADDRESS"
to_email = "TO_EMAIL_ADDRESS"

p "Verified?(FROM Email): " + ses.identities[from_email].verified?.to_s
p "Verified?(TO Email): " + ses.identities[to_email].verified?.to_s
p "Verified?(Domain): " + ses.identities['YOUR_DOMAIN'].verified?.to_s

ses.send_email(
  :subject => subject,
  :to => to_email,
  :from => from_email,
  :body_text => body_text,
  :body_text_charset => 'ISO-2022-JP'
)


■ 参考情報

RubyからAmazon SESでメールを送る - プログラマになりたい

詳しいです。ただし、Easy DKIMに関する記載が無い(この記事時点ではEasy DKIMがなかったのかも)ので、DKIM関係は独自署名の方法になっています。これはこれで押さえておいた方がいい情報ですが、Easy DKIMを使う方が楽ではあると思います。

AWS SDK for RubyのAPIリファレンス
AmazonSES-Easy DKIM設定サポート資料

Posted by あかさた
仕事でRubyでAmazon S3にアクセスする必要があったので、公式のgemであるaws-sdkを使ってみました。このgemはS3だけではなく、一通りのサービスにアクセスできるようなので、使いこなせばかなり有用かと思われます。

この記事では、S3へのファイルのアップロードを中心に以下の内容を扱っています。

・インストール
・基本設定
・ファイルのアップロード
・ファイルの暗号化
・アクセスコントロールの設定

■ インストール

gem install aws-sdk


■ 基本設定

アクセスキーを指定して、S3のインスタンスを生成します。

require 'aws-sdk'

AWS.config(
  :access_key_id => 'YOUR_ACCESS_KEY_ID',
  :secret_access_key => 'YOUR_SECRET_ACCESS_KEY')

s3 = AWS::S3.new

# 以下も可
# s3 = AWS::S3.new(
#   :access_key_id => 'YOUR_ACCESS_KEY_ID',
#   :secret_access_key => 'YOUR_SECRET_ACCESS_KEY')


■ ファイルのアップロード

ファイルをS3に格納します。

bucket = s3.buckets['yourbucketname']

filename = "fullpath/filename.ext"
basename = File.basename(filename)
o = bucket.objects[basename]
o.write(:file => filename)


■ ファイルの暗号化(クライアントサイド)

サーバーサイド(S3)でもできますが、とりあえずクライアントサイトでも簡単なので紹介します。

# キーの生成
my_key = OpenSSL::Cipher.new("AES-256-ECB").random_key

# オブジェクトの取得
o = bucket.objects[name]

# 暗号化して書込
o.write("MY TEXT", :encryption_key => my_key)

# 復号化して読込
puts o.read(:encryption_key => my_key)


公開鍵暗号を使いたいなら以下の通り。公開鍵で暗号化して、秘密鍵で復号化します。

# キーの生成
my_key = OpenSSL::PKey::RSA.new(1024)

# オブジェクトの取得
o = bucket.objects[name]

# 暗号化して書込
o.write("MY TEXT", :encryption_key => my_key)

# 復号化して読込
puts o.read(:encryption_key => my_key)


■ アクセスコントロールの設定

S3Objectクラスのacl=メソッドを使うと、アクセスコントロールを設定してS3上のオブジェクトに反映することができます。

o = bucket.objects[name]

# Canned ACL
o.acl = :public_read

# writeメソッドに渡すことも可能
o.write(:file => filename, :acl => :public_read)


以下、渡せるアクセスコントロールポリシーです。

  • :private
  • :public_read
  • :public_read_write
  • :authenticated_read
  • :bucket_owner_read
  • :bucket_owner_full_control

acl=には、AccessControlListを代入することもできます。changeメソッドを使ったやり方もあります。

o.acl.change do |acl|
  acl.grant(:full_control).to(:canonical_user_id => "...")
end


S3ObjectのchangeメソッドはS3Objectが返すAccessControlListのインスタンスが持つ特異メソッドなので、注意してください。ブロックの処理が完了すると、S3上のオブジェクトに反映します。(具体的にはS3Objectのacl=メソッドを呼び出します。)

ACLに関しては以下を参照してください。

acl=メソッド(AWS::S3::S3Object)
http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/S3/S3Object.html#acl%3D-instance_method

AWS::S3::AccessControlList
http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/S3/AccessControlList.html

■ 参考情報

Getting Started with the AWS SDK for Ruby(AWS)
AWS SDK for RubyのAPIリファレンス
aws/aws-sdk-ruby(GitHub)
aws-sdkのファイルアップロードのサンプル
AWS SDK for RubyでS3の操作をする - プログラマになりたい(ファイルの取得関係が詳しい)

Posted by あかさた
Apacheモジュールのmod-spdyを入れてみました。
SPDYは、HTTPを拡張して単一のセッションで複数リクエストを送受信することができるプロトコルです。
利用にはHTTPS通信可能な環境が必要です。

インストールはRPMが配布されているため、それを使うと楽です。

mod_spdy - Google Developers
https://developers.google.com/speed/spdy/mod_spdy/

yum install at
wget https://dl-ssl.google.com/dl/linux/direct/mod-spdy-beta_current_x86_64.rpm
rpm -U mod-spdy-beta_current_x86_64.rpm


インストールすると、Apacheのconf.dディレクトリに設定ファイルができるので、確認するといいでしょう。

ハマったところは、ApacheのSSLRequireSSLディレクティブを利用していると403が出てしまうことでしょうか。

mod_spdy doesn't work with SSLRequireSSL
http://code.google.com/p/mod-spdy/issues/detail?id=31

ひとまず、SSLRequireSSLディレクティブを外して動作させてみました。

ChromeでSPDY通信が有効になっているか確認する場合は、「chrome://net-internals/#spdy」にアクセスするか、以下のエクステンションを使うのが楽です。

Chrome ウェブストア - SPDY indicator
https://chrome.google.com/webstore/detail/spdy-indicator/mpbpobfflnpcgagjijhmgnchggcjblin
(SPDYの時は、URLのわきに緑色の稲妻のようなマークが出ます。)

パフォーマンスの差は体感できませんでした。実運用しているサービスのテスト環境に入れたため、画像などのリソースが別ドメインにあるとかSPDYがあまり効かない条件がそろっていました。

今のところ実運用する必要性は感じていませんが、注目のプロトコルですので、引き続き、チェックはしたいと思います。

Posted by あかさた
facebook Commentsプラグインの幅をレイアウトに合わせて可変にしたかったので
やり方を調べていたところ、Stack Overflowにやり方があったので、やってみました。

javascript - How to make facebook comment box width 100%? - Stack Overflow
http://stackoverflow.com/questions/10862256/how-to-make-facebook-comment-box-width-100

.fb-comments, .fb-comments span, .fb-comments iframe {
    width: 100% !important; 
}


このブログは、レスポンシブにしてあるので、このまま動作するか不安でしたが
ぱっと見、問題なく動いていそうだったので、これで良しとすることにしました。

Posted by あかさた
以下の脆弱性がアナウンスされていたため、本ブログのRailsのアップグレードを実施しました。

JVNVU#94771138 Ruby on Rails に複数の脆弱性
http://jvn.jp/cert/JVNVU94771138/

(1) GemfileのRailsバージョンを変更
(2) route.rb, environments下のファイルを退避(上書きされるため)
(3) 以下のコマンドを実施

bundle update
rake rails:update


特にハマるところもなく終了。

Posted by あかさた
以下の記事を読むと、DCIはRubyでは遅いという指摘があったので、移譲で
実装すればいいと思ったので、下記記事のサンプルコードをもとに、
send/method_missing, forwardable, delegateを使ったサンプルを書いて
みました。

※ 上記記事では移譲を使ったアプローチにも言及しているし、改善するには
頑張らないといけないという程度の話と認識しました。

■ ベンチマークの結果(ruby 1.9.3p194)

         without dci  3024223.4 (±1.7%) i/s -   15139474 in   5.007655s
            with dci   768491.0 (±6.6%) i/s -    3833440 in   5.011732s
      with dci(send)  1586461.1 (±2.1%) i/s -    7977320 in   5.030680s
with dci(forwardable)
                      1712079.6 (±1.8%) i/s -    8580546 in   5.013597s
  with dci(delegate)  1134862.9 (±1.3%) i/s -    5701904 in   5.025205s


見にくくて済みません。コードは下の方にあります。

■ 思ったこと

元記事のベンチマークは、オブジェクトの生成コストくらいしかないような
処理にextendを突っ込んでいるため、そりゃ比較にならないだろうという
問題があります。

そもそも、処理が無いならDCIを入れる意味なんてほとんどないことを考えれば、
ここのコストは無視してもいいものなのですが、無視してもいいコストをいかに
削るかという、無駄に挑戦することも一興かなと思い、記事にしました。

fooメソッドの処理内容によっては(※)、extendを使った元記事のアプローチの
方が優秀な場合もあると思います。柔軟性などを考えれば、移譲の方が使い勝手が
いいだろうとも思いますが。

※ 単純な移譲にならずに、Double Dispatchになるとオーバーヘッドで遅くなる
可能性もあると思います。

さて、ベンチマーク結果に移ると、forwardableが意外と速い。先に書いた通り、
オブジェクト生成コストしかないような処理に対して、半分程度の速度低下で
DCIが実現できるということです。これは、RubyにおけるDCIの実用性を示す
ものではないでしょうか。

■ 余談

これ、DCIじゃなくてRoleObjectじゃんって突っ込みが入りそうですが、
DCIで設計したらRoleObjectで実装するのが定番かなと思ってます。あとは、
Decoratorかな。やったことないけど。

しかし、皆DCIに突っ込んでいくとか、勇敢すぎると思う。Coplienの名前が
出たら全力で逃げるところだろう。マルチパラダイムデザインの恐怖を
忘れたんだろうか。。。(ネタですよ。念のため。)

■ ソースコード

require 'rubygems'
require 'benchmark/ips'
require 'forwardable'
require 'delegate'

class ExampleClass
  def foo; 42; end
end

module ExampleMixin
  def foo; 43; end
end

class ExampleClassSend
  def initialize(role)
    @role = role
  end
  
  def method_missing(action, *args)
    @role.__send__(action, *args)
  end
end

class ExampleClassForwardable 
  extend Forwardable
  
  def initialize(role)
    @role = role
  end
  
  def_delegator :@role, :foo, :foo
end

class ExampleClassDelegate < SimpleDelegator
end

class ExampleRole
  def foo
    43; 
  end
end

@role = ExampleRole.new

Benchmark.ips do |bm|
  bm.report("without dci") { ExampleClass.new.foo }
  
  bm.report("with dci") do
    obj = ExampleClass.new
    obj.extend(ExampleMixin)
    obj.foo
  end
  
  bm.report("with dci(send)") do
    ExampleClassSend.new(@role).foo
  end
  
  bm.report("with dci(forwardable)") do
    ExampleClassForwardable.new(@role).foo
  end
  
  bm.report("with dci(delegate)") do
    ExampleClassDelegate.new(@role).foo
  end
end


Posted by あかさた
勉強がてら、Bootstrapを使ってブログの見た目を変えてみました。ついでに、Rails 1(!)から3.2に移行しました。

新しい方
http://akasata.com/

古い方
http://www.rmake-labo.com/akasata

見比べると全然違いますが、サイトの構造は、コメント、トラックバックなどのいくつかの機能を無くしたこと以外は変わっていません。BootstrapはレスポンシブWebデザインにも対応していることから、スマフォやタブレットでもそこそこ見えるようになっています。

ちなみに、コードのハイライトはgoogle-code-prettifyを使っています。

基本、はてなブログを使っているので、3年ほど更新していませんでしたが、こちらに乗り換えようかなと検討中。。。

Posted by あかさた
こちらに書くのを忘れていましたが、ブラウザ上でゲームを作成して共有できるサービス「Rmake」を開始しました。詳細は以下を参照してください。

ブラウザ上でゲームを作成して共有できるサービス「Rmake」を開始した - 平凡なエンジニアの独り言

Posted by あかさた