Ruby で Google Analytics API

平成22年7月9日(金) 11時41分18秒
区分
Google
報告者:
tahara

こんにちは!! tahara です。 Ruby で Google Analytics API をたたいてみました。

といっても Garb を使えば簡単です。 ユーザID(email)とパスワードでも認証ができるのですが、今回は OAuth を使います。

インストール

gem install garb oauth

まずは Google Analytics, OAuth and Ruby. Oh, my. | everburning を参考に OAuth します。 あらかじめ https://www.google.com/accounts/ManageDomains から CONSUMER_KEY と CONSUMER_SECRET を取得しておく必要があります。

# -*- coding: utf-8 -*-

require 'oauth'

CONSUMER_KEY = "xxxxxx"
CONSUMER_SECRET ="xxxxxxxxx"

consumer = OAuth::Consumer.new CONSUMER_KEY, CONSUMER_SECRET, {
      :signature_method   => 'HMAC-SHA1',
      :site               => 'https://www.google.com',
      :request_token_path => '/accounts/OAuthGetRequestToken',
      :authorize_path     => '/accounts/OAuthAuthorizeToken',
      :access_token_path  => '/accounts/OAuthGetAccessToken',
    }

request_token = consumer.
  get_request_token({}, :scope => "https://www.google.com/analytics/feeds/")

# 次の URL をブラウザでアクセスし、確認コードを取得する。
p request_token.authorize_url

# 取得した確認コード
ACCESS_CODE = "xxxxxxxxx"

# 確認コードからアクセストークンを取得
access_token = request_token.get_access_token(:oauth_verifier => ACCESS_CODE)

# access_token.token と access_token.secret を取得する。
p access_token.token
p access_token.secret
ACCESS_TOKEN = access_token.token
ACCESS_SECRET = access_token.secret

# 次回からは次のようにしてアクセストークンを生成する。
access_token = OAuth::AccessToken.new(consumer, ACCESS_TOKEN, ACCESS_SECRET)

CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_SECRET がそろったので準備完了です。 Garb を使ってみます。 次は正規表現 ^/facilities/[0-9]+$ にマッチするページのページビューを取得するコードです。

# -*- coding: utf-8 -*-
=begin
http://github.com/vigetlabs/garb
=end

require "garb"
require "oauth"

CONSUMER_KEY = "xxxxx"
CONSUMER_SECRET ="xxxxxx"
ACCESS_TOKEN = "xxxxx"
ACCESS_SECRET = "xxxxx"

consumer = OAuth::Consumer.new CONSUMER_KEY, CONSUMER_SECRET, {
      :signature_method   => 'HMAC-SHA1',
      :site               => 'https://www.google.com',
      :request_token_path => '/accounts/OAuthGetRequestToken',
      :authorize_path     => '/accounts/OAuthAuthorizeToken',
      :access_token_path  => '/accounts/OAuthGetAccessToken',
    }
access_token = OAuth::AccessToken.new(consumer, ACCESS_TOKEN, ACCESS_SECRET)

Garb::Session.access_token = access_token

# プロファイル を指定
profile = Garb::Profile.first('UA-xxxxxxx-x')

class PageView
  extend Garb::Resource

  # 横に並ぶ項目。複数指定可能
  metrics :pageviews
  # 縦に並ぶ項目。複数指定可能
  dimensions :page_path
  # 並び順。複数指定可能。降順は後に .desc をつける。
  sort :pageviews.desc

  # フィルタ
  filters do
    # 正規表現で指定可能
    # http://code.google.com/intl/ja/apis/analytics/docs/gdata/gdataReferenceDataFeed.html#filters
    # http://www.google.com/support/analytics/bin/answer.py?answer=55582
    contains(:page_path, '^/facilities/[0-9]+$')
  end
end

# OpenStruct の配列で結果を取得。最大 10000 件取得できる。:offset で取得開始位置も指定可能。
res = PageView.results(profile, :start_date => '2010-07-01'.to_date, :end_date => '2010-07-07'.to_date, :limit => 10000)
# => [#<OpenStruct page_path="/facilities/159", pageviews="1237">, #<OpenStruct page_path="/facilities/164", pageviews="1061">, ...]

Data Feed Query Explorer - Google Analytics - Google Code ではブラウザから Analytics Data Export API をたたけるようになっていますので、 このページを参考にしながら、 metrics や dimensions の設定をいろいろかえると面白いことができるかもしれません。

>View Comments          このページの上へ戻る

Lisp on Rails 第9回 〜 ビュー

平成22年6月5日(土) 17時15分45秒
区分
Lisp on Rails
報告者:
tahara

こんにちは!! tahara です。 Objective-C づけになり、すっかりこぶさたしておりましたが Lisp on Rails 第9回です!

今回はビューです。 Common Lisp で ERB 相当を実装します。 Common Lisp で実装するからにはリードテーブルを使い、 HTML ファイルを関数にコンパイルしたいと思います。

HTML ファイルを関数にコンパイルソースは http://github.com/quek/lisp-on-rails/blob/master/action-pack/ecl.lisp です。

ところどころ説明させていただきます。 html-defun-readtable ではビューファイルの最初の1文字をマクロキャラクタにして、 先頭に (in-package :xxxx) を追加し、全体を (defun xxxx () ...) でくるむようにしています。 動的にリーダをカスタマイズしているのです。 これでビューファイルを1つの関数として読み込むことができるようになります。

(defun html-defun-readtable (fname pathspec)
  (let ((*readtable* (basic-readtable)))
    (set-macro-character
     (first-char pathspec)
     (let ((in-package t))
       (lambda (stream char)
         (unread-char char stream)
         (print
          (if in-package
              (progn
                (setf in-package nil)
                `(in-package ,(package-name action-controller:*app-package*)))
              `(defun ,fname ()
                 ,(body-code stream char)))))))
    *readtable*))

Rails ではコントローラからビューへの値の受け渡しは @foo のようなインスタンス変数が使われます。 それに対応するためビューファイルの中に @ で始まるシンボルがあれば、 コントローラのスロット値へのアクセスに変換するシンボルマクロを定義します。

(defun body-code (stream char)
  (walk-body-code (read-body-code stream char)))

(defun read-body-code (stream char)
  (let ((*readtable* (make-html-readtable char)))
    (loop for x = (read stream nil stream t)
          until (eq x stream)
          collect x)))

(defun walk-body-code (code)
  `(symbol-macrolet
       ,(series:collect
            (series:mapping
             ((x (series:choose-if (q:^ q:symbol-head-p _ "@")
                                   (series:scan-lists-of-lists-fringe code))))
             `(,x (slot-value action-controller:*controller*
                              ',(intern (subseq (symbol-name x) 1)
                                        action-controller:*app-package*)))))
     ,@code))

そんなこんなで、なんとかモデル、コントローラ、ビューが繋がりました。

モデル

(in-package :blog)

(def-record post
  (:has-many comments))

(def-record comment
  (:belongs-to post))

コントローラ。生の defclass です。

(in-package :blog)

(defclass top-controller (application-controller)
  ((message)
   (posts)))

(defmethod index ((self top-controller))
  (with-slots (message posts) self
    (setf message "まみむめも♪"
          posts (all post))))

ビュー。HTML タグと loop が混在するのもまた一興ですね。

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>ブログ</title>
  </head>
  <body>
    <h1><%= @message %></h1>
    <h2>投稿を loop で表示する</h2>
    <ul><% (loop for post in @posts for comments = (comments-of post) do %>
      <li><%= (content-of post) %> -- <%= (name-of post) %></li>
      <% if comments do %>
      <ul><% (loop for comment in comments do %>
        <li><%= (body-of comment) %> -- <%= (commenter-of comment) %></li><% ) %>
      </ul><% ) %>
    </ul>
  </body>
</html>

コントローラは次のように書けるようにすると、それっぽい気もしますが、いまはまだ書けません。

(def-controller top (application)
  (def-action index
      (setf @message "まみむめも♪"
            @posts (all post)))
  (def-action foo
      (setf @essage "foo")))

以上、まとめますと 「リーダをいじれる言語は素敵ですね!」 でした。

ソースはこちらから http://github.com/quek/lisp-on-rails

第10回につづきます。

>View Comments          このページの上へ戻る

Apache で特定の User-Agent だけ BASIC 認証をバイパスする方法

平成22年5月14日(金) 10時50分16秒
区分
Apache
報告者:
tahara

こんにちは!! tahara です。 iPhone アプリからのアクセス以外はベーシック認証でブロックしたい、というときのお話です。 この設定で User-Agent に CFNetwork が含まれていない場合だけベーシック認証が必要になります。

<Location />
  Satisfy Any
  BrowserMatchNoCase CFNetwork is_iPhone=1
  Order Deny,Allow
  Deny from all
  Allow from env=is_iPhone

  AuthUserFile /var/www/htpasswd
  AuthGroupFile /dev/null
  AuthName "Please enter username and password"
  AuthType Basic
  require valid-user
</Location>

これでステージング環境が Google に補足されることもなくなるはずです。

>View Comments          このページの上へ戻る

関連する単語

平成22年4月24日(土) 18時38分25秒
区分
集合知
報告者:
tahara

こんにちは!! tahara です。

少々事情があってある単語に関連する単語を自動的に取得したくなりました。 『集合知イン・アクション』 を参考に Common Lisp で書いてみました。

Yahoo の Web API を利用させていただきます。

  • ウェブ検索とブログ検索で単語に関連するテキストを収集
  • 日本語形態素解析で単語に分解
  • 単語からタームベクトルを作成
(eval-when (:compile-toplevel :load-toplevel :execute)
  (require :drakma)
  (require :cxml)
  (require :cl-ppcre))

(defparameter *words*
  '("アナウンサー" "お医者さん" "イラストレーター" "宇宙飛行士"
    "タクシー運転手" "電車運転士" "バス運転士" "映画監督" "絵本作家"
    "演奏家" "歌手" "カメラマン" "看護師" "外交官" "画家" "高校の先生"
    "小学校の先生" "中学校の先生" "気象予報士" "キャビンアテンダント"
    "救急救命士" "銀行員" "警察官" "裁判官" "作詞家" "サッカー監督"
    "サッカー選手" "作曲家" "シェフ" "指揮者" "社長" "小説家" "消防士"
    "新聞記者" "動物のお医者さん" "政治家" "声優" "船長" "大工" "図書館司書"
    "俳優" "花火師" "花屋" "パイロット" "パン屋さん" "美容師"
    "ピアノニスト" "プロ野球選手" "弁護士" "幼稚園の先生")
  "これらの単語に関連する単語が欲しいのです。")

(defparameter *yahoo-appid*
  "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  "Yahoo Web API の アプリケーションID")
(defparameter *yahoo-ma-url* "http://jlp.yahooapis.jp/MAService/V1/parse"
  "日本語形態素解析")
(defparameter *yahoo-web-search-url*
  "http://search.yahooapis.jp/WebSearchService/V1/webSearch"
  "ウェブ検索")
(defparameter *yahoo-blog-search-url*
  "http://search.yahooapis.jp/BlogSearchService/V1/blogSearch"
  "ブログ検索")

(defparameter *occurrence-threshold* 5
  "これより少ない出現頻度の単語は無視します。")
(defparameter *stop-words*
  '("あれ" "いい" "こんな" "こちら" "こと" "これ" "それ" "ため" "とき" "ない"
    "もの" "よく"
    "以上" "一覧" "最新"
    "amp" "at" "by" "com" "gt" "http" "https" "jp" "lt")
  "これらの単語は無視します。")
(defparameter *stop-words-regexps*
  (mapcar #'ppcre:create-scanner
          '("^[0-90-9]+$" "^.$"))
  "これらの正規表現に一致する単語ま無視します。")

;; Drakma の設定
(setf drakma:*drakma-default-external-format* :utf-8)
(pushnew '("application" . "xml") drakma:*text-content-types* :test #'equal)

(defun stop-word-p (word)
  (or (find word *stop-words* :test #'string=)
      (some (lambda (x) (ppcre:scan x word)) *stop-words-regexps*)))

(defun yahoo-ma-request (text)
  (drakma:http-request
   *yahoo-ma-url*
   :method :post
   :parameters `(("appid" . ,*yahoo-appid*)
                 ("filter" . "1|9") ; 形容詞 名詞
                 ("sentence" . ,text))))

(defun text-to-words (text)
  (destructuring-bind (result-set
                       schema-location
                       (ma-result
                        _
                        total-count
                        filtered-count
                        word-list))
      (cxml:parse (yahoo-ma-request text) (cxml-xmls:make-xmls-builder))
    (declare (ignorable result-set schema-location ma-result _
                        total-count filtered-count))
    (loop for (_a _b (_c _d word)) in (cddr word-list)
         collect word)))

(defun yahoo-web-search-request (query)
  (drakma:http-request
   *yahoo-web-search-url*
   :method :get
   :parameters `(("appid" . ,*yahoo-appid*)
                 ("query" . ,query)
                 ("results" . "50")
                 ("format" . "html"))))

(defun web-search (query)
  (destructuring-bind (result-set
                       pgr . results)
      (cxml:parse (remove #\lf (yahoo-web-search-request query))
                  (cxml-xmls:make-xmls-builder))
    (declare (ignorable result-set pgr))
    (loop for (result _a (_title _b title) (_summary _c summary)) in results
         collect (list title summary))))

(defun yahoo-blog-search-request (query)
  (drakma:http-request
   *yahoo-blog-search-url*
   :method :get
   :parameters `(("appid" . ,*yahoo-appid*)
                 ("query" . ,query)
                 ("results" . "50"))))

(defun blog-search (query)
  (destructuring-bind (result-set first-result-position . results)
      (cxml:parse (remove #\lf (yahoo-blog-search-request query))
                  (cxml-xmls:make-xmls-builder))
    (declare (ignorable result-set first-result-position))
    (loop for (result _a id rss-url (_title _b title)
                      (_description _c description)) in results
         collect (list title description))))

(defun word-to-word-list (word)
  (remove-if #'stop-word-p
             (loop for i in '(web-search blog-search)
                append (text-to-words
                        (format nil "~{~{~a ~a ~}~}" (funcall i word))))))

(defun word-count-alist (word)
  (let (alist)
    (loop for i in (word-to-word-list word)
       if (assoc i alist :test #'string=)
       do (incf (cdr (assoc i alist :test #'string=)))
       else
       do (setf alist (acons i 1 alist)))
    (setf alist (sort alist #'(lambda (x y)
                                (>= (cdr x) (cdr y)))))
    (remove-if (lambda (x) (< (cdr x) *occurrence-threshold*)) alist)))

(defun normalize (alist)
  "重みづけを、その平方和が 1 とらるように正規化する。"
  (loop with factor = (sqrt (loop for i in alist sum (expt (cdr i) 2)))
       for (word . magnitude) in alist
       collect (cons word (/ magnitude factor))))

(defun all-word-alist ()
  (loop for word in *words*
     collect (print (cons word (normalize (word-count-alist word))))))

出力は次のとおりです。

("宇宙飛行士" ("宇宙" . 0.7740145) ("飛行士" . 0.5890963) ("山崎" . 0.09774244)
 ("若田" . 0.06604219) ("野口" . 0.05547544) ("地球" . 0.05547544)
 ("訓練" . 0.050192066) ("ステーション" . 0.047550377) ("毎日新聞" . 0.044908687)
 ("日本人" . 0.042267002) ("国際" . 0.042267002) ("日本" . 0.039625313)
 ("直子" . 0.039625313) ("スペースシャトル" . 0.03434194) ("ニュース" . 0.03434194)
 ("シャトル" . 0.03170025) ("帰還" . 0.03170025) ("家族" . 0.03170025)
 ("写真" . 0.029058563) ("ISS" . 0.029058563) ("情報" . 0.026416875)
 ("光一" . 0.026416875) ("聡一" . 0.026416875) ("JAXA" . 0.023775188)
 ("活動" . 0.023775188) ("飛行" . 0.023775188) ("紹介" . 0.023775188)
 ("産経新聞" . 0.023775188) ("映像" . 0.021133501) ("ミッション" . 0.021133501)
 ("NASA" . 0.021133501) ("交信" . 0.021133501) ("職業" . 0.018491814)
 ("毛利" . 0.018491814) ("滞在" . 0.018491814) ("撮影" . 0.018491814)
 ("研究" . 0.018491814) ("女性" . 0.018491814) ("サイト" . 0.018491814)
 ("搭乗" . 0.015850125) ("ページ" . 0.015850125) ("選抜" . 0.015850125)
 ("イベント" . 0.015850125) ("実現" . 0.015850125) ("アポロ" . 0.015850125)
 ("きぼう" . 0.013208438) ("航空" . 0.013208438) ("開発" . 0.013208438)
 ("機構" . 0.013208438) ("参加" . 0.013208438) ("さいたま市" . 0.013208438)
 ("試験" . 0.013208438) ("仕事" . 0.013208438) ("最後" . 0.013208438)
 ("月面" . 0.013208438) ("着陸" . 0.013208438) ("特集" . 0.013208438)
 ("時事通信" . 0.013208438) ("契約" . 0.013208438) ("サム" . 0.013208438))

さて、この出力を利用することができるかどうかがまた問題です。

>View Comments          このページの上へ戻る

Lisp on Rails 第8回 〜 before_*

平成22年4月10日(土) 18時42分58秒
区分
Lisp on Rails
報告者:
tahara

こんにちは!! tahara です。 Lisp on Rails 第8回です!

今回は ActiveRecord::Base の save, create, update, destroy 等々のメソッドには beforo_* や after_* というフックメソッドを定義することができます。 ActiveRecord::Callbacks でそのあたりの実装がされています。

これを Common Lisp でやろうとした場合、

(defmethod save :before ((self post)) ...)
で OK と思ったらそうはいきません。 before_* メソッドが false を返した場合はメソッドを呼び出しを中断する必要があります。 Common Lisp の before メソッドは返り値は無視してしまうので、そのまま使うことはできないのです。

仕方ないので自分で新しいメソッドコンビネーションを実装します。

(define-method-combination active-record ()
  ((around (:around))
   (before (:before))
   (primary () :required t)
   (after (:after)))
  "before メソッドが nil を返した場合メソッドの実行を中断する。"
  (flet ((call-methods (methods)
           (mapcar #'(lambda (method)
                       `(call-method ,method))
                   methods))
         (call-methods-and (methods)
           `(and ,@(mapcar #'(lambda (method)
                               `(call-method ,method))
                           methods))))
    (let ((form (if (or before after (rest primary))
                    `(when ,(call-methods-and before)
                       (multiple-value-prog1
                           (call-method ,(first primary)
                                        ,(rest primary))
                         ,@(call-methods (reverse after))))
                    `(call-method ,(first primary)))))
      (if around
          `(call-method ,(first around)
                        (,@(rest around)
                           (make-method ,form)))
          form))))

あとは defgeneric するときにこのメソッドコンビネーションを指定すれば OK です。

(defgeneric save (record)
  (:method-combination active-record)
  ...)

簡単にメソッドの呼び出し方法を定義できてしまうなんて Common Lisp はいい言語ですね。

ソースはこちらから http://github.com/quek/lisp-on-rails

第9回につづきます。

>View Comments          このページの上へ戻る

Lisp on Rails 第7回 〜 ActiveRecord::Base の find メソッド

平成22年3月27日(土) 07時20分34秒
区分
Lisp on Rails
報告者:
tahara

遅くなりました!! tahara です。 Lisp on Rails 第7回です!

今回は ActiveRecord::Base の find メソッドの機能を多少実装してみたいと思います。

ActiveRecord::Base の find メソッドは次の4つの使い方があります。

  1. id で検索。 引数は (id, *args), (id1, id2, ..., *args), ([id1, id2, ..., *args]) の3パターン。 該当するレコードがない場合は RecordNotFound が発生する。
  2. 最初の1件を検索。 引数は (:first, *args) で、該当がない場合は nil を返す。 Model.first(*args) というショートカットがある。
  3. 最後の1件を検索。 引数は (:last, *args) で、該当がない場合は nil を返す。 Model.last(*args) というショートカットがある。
  4. 該当する全件を検索。 引数は (:all, &args) で、該当がない場合は nil を返す。 Model.all(*args) というショートカットがある。

上記の4つの使い方全てで次のハッシュオプションが使えます。

  • :conditions - いわゆる検索条件。文字列またはリストで指定。
  • :order - SQL の ORDEY BY
  • :group - SQL の GROUP BY
  • :having - SQL の HAVING
  • :limit - 最大取得件数。
  • :offset - 取得開始位置。
  • :joins - SQL の JOIN だけど、普通次の :include を使う。
  • :include - 検索時に一緒にとってくるテーブルをアソシエーション名(has_many :xxxs)で指定する。
  • :select - 取得カラム。デフォルトは "*"。
  • :from - SQL の FROM。ビューから検索するとき等に使える。
  • :readonly - 取得結果をリードオンリー指定する。
  • :lock - SQL でのロック。"FOR UPDATE" とか。

このような場合 Common Lisp ではマルチディスパッチとキーワード引数を使えばうまくいくはずです。

;; find は CL にあるので select にする
(defgeneric select (class id-or-keyword
                          &key
                          conditions
                          order
                          group
                          having
                          limit
                          offset
                          joins
                          include
                          select
                          from
                          readonly
                          lock
                          &allow-other-keys))

(defmethod select ((class symbol) id-or-keyword &rest args)
  (apply #'select (find-class class) id-or-keyword args))

(defmethod select ((class active-record-class) (id integer)
                   &rest args
                   &key conditions)
  (setf conditions (append (list :id id) conditions))
  ...)

(defmethod select ((class active-record-class) (ids list)
                   &rest args
                   &key conditions)
  (setf conditions (append (list :id ids) conditions))
  ...)

(defmethod select ((class active-record-class) (keyword (eql :all))
                   &rest args)
  ...)

(defmethod select ((class active-record-class) (keyword (eql :first))
                   &rest args
                   &key (order "id"))
  ...)

(defmethod select ((class active-record-class) (keyword (eql :last))
                   &rest args
                   &key (order "id"))
  ...)

(defun all (&rest args)
  (apply #'select (car args) :all (cdr args)))
;; first と last は CL パッケージとかぶる。

といった感じで実装してみました。 joins, include, readonly 等はまだ未実装です。

しかし Common Lisp パッケージとシンボル名(find, first, last)がかぶるのが悩ましいところです。shadowing しようかしらん。

ソースはこちらから http://github.com/quek/lisp-on-rails

第8回につづきます。

>View Comments          このページの上へ戻る

Lisp on Rails 第6回 〜 ここらでリファクタリング

平成22年3月19日(金) 12時10分44秒
区分
Lisp on Rails
報告者:
tahara

こんにちは!! tahara です。 Lisp on Rails 第6回です!

has-one を has-many のコピペで書いてしまったので、 ここらでリファクタリングしたいと思います。

has-one のスロット定義

(defclass ar-has-one-slot-mixin ()
  ((has-one :initarg :has-one
            :initform nil
            :accessor has-one)
   (class-symbol :initarg :class-symbol
                 :initform nil
                 :accessor class-symbol)))

(defmethod initialize-instance :after ((self ar-has-one-slot-mixin) &rest args)
  (declare (ignore args))
  (unless (class-symbol self)
    (setf (class-symbol self) (has-one self))))

(defclass ar-has-one-direct-slot-definition (ar-direct-slot-definition
                                             ar-has-one-slot-mixin)
  ())

(defclass ar-has-one-effective-slot-definition (ar-effective-slot-definition
                                                ar-has-one-slot-mixin)
  ())

has-many のスロット定義

(defclass ar-has-many-slot-mixin ()
  ((has-many :initarg :has-many
             :initform nil
             :accessor has-many)
   (class-symbol :initarg :class-symbol
                 :initform nil
                 :accessor class-symbol)))

(defmethod initialize-instance :after ((self ar-has-many-slot-mixin) &rest args)
  (declare (ignore args))
  (unless (class-symbol self)
    (setf (class-symbol self)
          (sym (singularize (has-many self))))))

(defclass ar-has-many-direct-slot-definition (ar-direct-slot-definition
                                              ar-has-many-slot-mixin)
  ())

(defclass ar-has-many-effective-slot-definition (ar-effective-slot-definition
                                                 ar-has-many-slot-mixin)
  ())

いやー、ひどいですね。 ほとんど one と many の違いだけです。

さて、これをリファクタリングするのに Common Lisp にはマクロという手抜きプログラマには必須の機能があります。

普通リファクタリングするとなると、関数、メソッド、スーパークラス等々の 切り出しが必要になりますよね? でも、マクロなら何ら設計を変更することなくリファクタリングが可能になります。

では、実際にマクロを使ってリファクタリングしてみましょう。

(defmacro def-has-xxx-slot-definition (xxx
                                       default-class-symbol-form)
  `(progn
     (defclass ,(sym "ar-has-" xxx "-slot-mixin") ()
       ((,(sym "has-" xxx) :initarg ,(key-sym "has-" xxx)
                 :initform nil
                 :accessor ,(sym "has-" xxx))
        (class-symbol :initarg :class-symbol
                      :initform nil
                      :accessor class-symbol)))

     (defmethod initialize-instance :after ((self ,(sym "ar-has-" xxx "-slot-mixin")) &rest args)
        (declare (ignore args))
        (unless (class-symbol self)
          (setf (class-symbol self) ,default-class-symbol-form)))

     (defclass ,(sym "ar-has-" xxx "-direct-slot-definition") (ar-direct-slot-definition
                                                  ,(sym "ar-has-" xxx "-slot-mixin"))
       ())

     (defclass ,(sym "ar-has-" xxx "-effective-slot-definition") (ar-effective-slot-definition
                                                     ,(sym "ar-has-" xxx "-slot-mixin"))
       ())
     ))

(def-has-xxx-slot-definition one (has-one self))
(def-has-xxx-slot-definition many (sym (singularize (has-many self))))

すばらい。 最初のひどい設計を何ら変えることなくリファクタリングできました。

手抜き設計のベタ書きコードそのままで、リファクタリングを可能とするマクロは、 未熟なプログラマにとって、なくてはならない存在です。

ソースはこちらから http://github.com/quek/lisp-on-rails

第7回につづきます。

>View Comments          このページの上へ戻る

Lisp on Rails 第5回 〜 has-one

平成22年3月12日(金) 23時56分46秒
区分
Lisp on Rails
報告者:
tahara

こんにちは!! tahara です。 Lisp on Rails 第5回です!

今回は ActiveRecord の has_one アソシエーションもどきを実装したいと思います。 前回の has_many に瓜二つです。 ar-has-one-direct-slot-definition と ar-has-one-effective-slot-definition を定義します。

(defclass ar-has-one-slot-mixin ()
  ((has-one :initarg :has-one
            :initform nil
            :accessor has-one)
   (class-symbol :initarg :class-symbol
                 :initform nil
                 :accessor class-symbol)))

(defmethod initialize-instance :after ((self ar-has-one-slot-mixin) &rest args)
  (declare (ignore args))
  (unless (class-symbol self)
    (setf (class-symbol self) (has-one self))))

(defclass ar-has-one-direct-slot-definition (ar-direct-slot-definition
                                             ar-has-one-slot-mixin)
  ())

(defclass ar-has-one-effective-slot-definition (ar-effective-slot-definition
                                                ar-has-one-slot-mixin)
  ())

direct-slot-definition-class と effective-slot-definition-class と compute-effective-slot-definition でゴニョゴニョすると上記の slot-definition が使えるようになります。

slot-value-using-class と (setf slot-value-using-class) で関連テーブルの 取得と設定を行います。

(defmethod c2mop:slot-value-using-class
  ((class active-record-class)
   instance
   (slot-def ar-has-one-effective-slot-definition))
  (aif (call-next-method)
       it
       (setf (slot-value instance (has-one slot-def))
             (car (all (find-class (class-symbol slot-def))
                       :conditons (list (key-sym (class-name class) '-id)
                                        (%value-of instance :id)))))))

(defmethod (setf c2mop:slot-value-using-class) :after
           (new-value
            (class active-record-class)
            instance
            (slot-def ar-has-one-effective-slot-definition))
   (when new-value
     (setf (%value-of new-value (str (class-name class) "-id"))
           (%value-of instance :id))))

で、だいたいこんな感じで使えるようになります。

(def-record post
  (:has-many comments)
  (:has-one post-info))
(def-record post-info
  (:belongs-to post))
(let* ((post (car (all post)))
       (post-info (post-info-of post)))
  (describe post-info))

以上、なんとなく has-one できました。 ソースはこちらから http://github.com/quek/lisp-on-rails

第6回につづきます。

すみません。 次回はコピペじゃないように頑張ります。

>View Comments          このページの上へ戻る

多段 ssh で vnc

平成22年3月5日(金) 14時30分10秒
区分
ssh
報告者:
tahara

こんにちは!! tahara です。

例えば host1 を経由して host2 に vnc したい場合どうすればいいか調べてみました。 結果 ↓ のページに書かれてあるとおりすれば可能でした。ありがとうございます。

ssh を多段に使ってずっと遠くにあるマシンに port forward する|裏表(Phinloda のもう裏だか表だか分からないページ)

蛇足になりますが、次の手順で host2 に vnc できました。

>View Comments          このページの上へ戻る

iPhone アプリケーションの開発

平成22年2月26日(金) 16時42分10秒
区分
iPhone
報告者:
tahara

こんにちは!! tahara です。

突然ではありますが、とりあえず読むべきは iPhone Dev Center にある以下のドキュメントでしょうか。

まずは iPhone ヒューマンイン ターフェイス ガイドライン を読んでみました。

iPhone が他と違うところ

  • 小さい画面サイズ。 480x320
  • メモリ少ない。OS が警告をくれたらすぐにメモリ開放すべすし。didReceiveMemoryWarning
  • 1画面。ある意味 Stumpwm!
  • さらに、同時に1アプリケーション。
  • 標準に準拠してヘルプコンテンツを最小限にすべし。

3つのアプリケーションの形態

  • iPhone アプリケーション
  • Web 専用contents
  • ハイブリッドアプリケーション(上の2つの組み合わせ)

3つのアプリケーションスタイル

  • 生産性型アプリケーション(例 Mail)
  • ユーティリティ型アプリケーション(例 Weather)
  • 没入型アプリケーション(例 ゲームアプリ)

デスクトップアプリを移植する場合はよく使われる 20% の機能のみに絞る。

ヒューマンインターフェイスの原則

  • 自然なメタファで
  • 具体的なものを直接いじる感覚
  • テキスト入力を最小限に、選択肢を提示
  • ユーザの操作に対するフィードバック、効果的なやりとり
  • ユーザが開始でき、それを停止できる動きであること
  • インターフェイスの一貫性

製品定義ステートメントを作りましょう。

  • 対象ユーザを明確化
  • 実装機能の限定

簡潔さと使いやすさ

  • 使い方を明白に
  • 頻繁に使用される情報を画面上部に集中
  • テキスト入力を最小限
  • 重要な情報を簡潔に
  • タップ可能な要素の領域は指先サイズに

製品ステートメントにふさわしい主たる機能に焦点を当て、 簡潔かつ入力を最小限にする。

ジェスチャを適切にサポートする。

  • 単純かつ簡単な方法は必須
  • 新しいジェスチャを定義しない

ブランドはささやかで控え目に。 ただしアプリケーションアイコンにはブランドももりこむ。

一般的なタスク

  • 開始
    • ステータスバーに適切なスタイルを設定する
    • 最初の画面に似た起動画面を用意して感覚的起動時間を短縮する
    • 余計なスプラッシュ画面は不要
    • 基本縦で起動
    • 最後に実行したときの状態を復元する
  • 停止
    • いつでも停止できるように
    • 停止時は次の起動のために可能なかぎり詳細の情報を保存しておく
  • 設定(Settings)と設定オプション
    • 設定は一度設定したら変更しないもの
    • 設定オプションは頻繁に変更する可能性があるもの
    • 理想はユーザに設定を要求しないこと
  • その他いろいろ ... ちょっと省略してしまいました。

ユーザインターフェイスの設計

  • ステータスバーはあまりいじらない
  • ToolBar は 44x44 で5つ以下が妥当
  • Tab Bar はモードの切り替え。 5つまでは横に並んで、それをこえたものは More に。 バッジを表示できる。
  • モーダルビューは煩わしいので乱用しない。
  • Action Sheet はユーザの選択肢でり複数のボタンを備える。 害のあるものは赤で一番上に。
  • モーダルビューは主たる機能が関連する自己簡潔型のタスクで(例 Mail のメッセージ作成)
  • アクティビティインジケータ
    • ステータスバーのは1〜2秒ネットワークにアクセスする場合
    • もっと時間がかかる場合はツールバーに表示
  • 進捗がわかるものは Progress View
  • Text Field は左端に使用目的、右端に追加機能
  • システムが用意しているボタン、アイコンを意図されたとおり使う
    • ユーザにやさしい
    • 工数削減

アイコン、画像の作り方の説明でおしまい。

このようなドキュメントがしっかり用意されているのは素晴しいですね。

>View Comments          このページの上へ戻る

技師部隊からの
お知らせ

インフルエンザに気をつけて頑張っています

本頁の来客数
五万三千二百三名

メンバー一覧

アクトインディ技師部隊員名簿

アクトインディへ

投稿する

カテゴリー

アクトインディ

aaaa