Rails3 への移行

平成24年1月24日(火) 23時01分53秒
区分
Rails
報告者:
tahara

こんにちは、tahara です。

突然ですが、弊社では現在エンジニアを募集しています。 仕事内容は情シス業務と自社サービの開発です。 開発は主に Ralis で PHP もときどきあります。 たまぁに Common Lisp もあます(増やしていきたいです)。 詳細はこちらをご覧ください。

それでは本題です。

いまさらではありますが、弊社で運営している http://iko-yo.net を Rails3 に移行しました。 今回はその移行作業について書いていきたいと思います。

rvm で ruby 1.9.3 をインストールし gemsent を作成する。

bash < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer)
rvm install ruby-1.9.3
rvm use ruby-1.9.3
gem install bundler
rvm gemset create iko-yo-rails3
rvm --rvmrc --create 1.9.3@iko-yo-rails3
cd ..
cd -

きれいする

rm -r vendor/rails
rm -r vendor/gems
rm -r vendor/plugins/*

gem を入れる

vi Gemfile

assets は使わない。。。

source 'http://rubygems.org'

gem 'rails', '3.1.0'

# Bundle edge Rails instead:
# gem 'rails',     :git => 'git://github.com/rails/rails.git'

gem 'mysql2'
gem 'jquery-rails'
gem 'exception_notification'
gem 'geokit-rails3'
gem 'jpmobile'
gem 'nokogiri'
gem 'paperclip'
gem 'restful-authentication'
gem 'ssl_requirement'
gem 'acts_as_taggable_on_steroids'
gem 'acts_as_commentable'
gem 'will_paginate'
gem 'dynamic_form'
gem 'mecab-ruby', :require => 'MeCab'
gem 'twitter'
gem 'oauth'
gem 'garb'
gem 'gdata_19', :require => 'gdata'
gem 'holiday_jp'
gem 'dalli'
gem 'newrelic_rpm'

#;; config.assets.enabled = false
#;; # Gems used only for assets and not required
#;; # in production environments by default.
#;; group :assets do
#;;   gem 'sass-rails', "  ~> 3.1.0"
#;;   gem 'coffee-rails', "~> 3.1.0"
#;;   gem 'uglifier'
#;; end

# Use unicorn as the web server
gem 'unicorn'

# Deploy with Capistrano
gem 'capistrano'
gem 'capistrano-ext'

# To use debugger
# gem 'ruby-debug19', :require => 'ruby-debug'

group :test, :development do
  # Pretty printed test output
  gem 'turn', :require => false
  gem 'spork'
  gem 'rspec-rails', "~> 2.6"
  gem 'capybara'
  gem 'ZenTest'
  gem 'autotest-stumpwm'
  gem 'remarkable_activerecord', '>=4.0.0.alpha4'
  gem 'spork'
end

gem 入れて rails3 にする。

bundle install
bundle exec rails new

ソースの編集

config/rootes.rb はがんばる。 メールまわりも完全に書きなおし。

ソースをちまちま書きかえる(以下はイメージです。実際に動作するものではありません)。

config/boot.rb に次を追加
# /home/ancient/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/psych.rb:148:in `parse': couldn't parse YAML at line 18 column 13 (Psych::SyntaxError)
require 'yaml'
YAML::ENGINE.yamler= 'syck'

各ファイルの1行目に次を追加
# -*- coding: utf-8 -*-


helper 系メソッドに .html_safe を付加

あは以下のようなイメージでどんどん書きかえていく。

s/adapter: mysql/adapter: mysql2/ database.yml.release

s/named_scope/scope/

s/RAILS_ROOT/Rails.root/

s/returning/tap/

s/request_uri/fullpath/

s/<% form/<%= form/
s/<%= f.fields_for/<%= f.fields_for/

layout が使われなかったのは ApplicationController#initialize で super を呼んでなかったからだった。

s/(.*).merge_conditions (.*)/where(\1).where(\2)/

s/mobile_filter :hankaku => true/hankaku_filter :input => true/

s/include ActionController::UrlWriter/include Rails.application.routes.url_helpers/

s/.class_name/.name/

s/link_to_remote .*/link_to \1, :remote => true/
:complete, :before 等は js で bind('ajax:complete', ...), bind('ajax:before', ...) にする。
http://www.alfajango.com/blog/rails-3-remote-links-and-forms/

error_messages がなくなったので gem 'dynamic_form'

s/choice/sample/

s/observe_field/ふつうの手書き jQuery/

s/action mailer/スーパークラスは Jpmobile::Mailer::Base で書きなおす/

/self.include_root_in_json = false/d config/initializers/wrap_parameters.rb

s/model.save(false)/model.save(:validate => false)/

s/errors.or\((.*)\)/errors[\1]/

他にもいっぱいあったような気もしますが、だいたいこんな感じです。

次に実行環境まわり。

実行環境

Apache と Passenger だったのを nginx と unicorn にしました。 unicorn についているサンプルをもとに設定しました。

まずは ngix

nginx.conf

user  deployer;
worker_processes  2;

pid /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    client_max_body_size 50m;
    client_header_buffer_size 4k;

    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  15;

    gzip  on;
    gzip_disable "msie6";
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

    #open_file_cache max=2000 inactive=300s;
    #open_file_cache_valid 360s;
    #open_file_cache_min_uses 2;
    #open_file_cache_errors off;

    include /var/www/outing/current/config/unicorn/production/nginx-site.conf;
}

nginx-site.conf

upstream outing {
    # for UNIX domain socket setups:
    #server unix:/tmp/.outing.sock fail_timeout=0;
    # for TCP setups, point these to your backend servers
    server 127.0.0.1:8080 fail_timeout=0;
}

server {
    listen 80;
    root /var/www/outing/current/public;
    server_name iko-yo.net;

    location / {

        #auth_basic "Restricted";
        #auth_basic_user_file /etc/nginx/outing-password;

        if ($request_uri ~* "\.(jpg|jpeg|gif|css|png|js|ico)\?[0-9]+$") {
            expires max;
            access_log off;
            break;
        }
        if (-f $request_filename) {
            expires 24h;
            access_log off;
            break;
        }

        try_files $uri @app;
    }

    location @app {
      # an HTTP header important enough to have its own Wikipedia entry:
      #   http://en.wikipedia.org/wiki/X-Forwarded-For
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

      # enable this if and only if you use HTTPS, this helps Rack
      # set the proper protocol for doing redirects:
      # proxy_set_header X-Forwarded-Proto https;

      # pass the Host: header from the client right along so redirects
      # can be set properly within the Rack application
      proxy_set_header Host $http_host;

      # we don't want nginx trying to do something clever with
      # redirects, we set the Host: header above already.
      proxy_redirect off;

      # set "proxy_buffering off" *only* for Rainbows! when doing
      # Comet/long-poll/streaming.  It's also safe to set if you're using
      # only serving fast clients with Unicorn + nginx, but not slow
      # clients.  You normally want nginx to buffer responses to slow
      # clients, even with Rails 3.1 streaming because otherwise a slow
      # client can become a bottleneck of Unicorn.
      #
      # The Rack application may also set "X-Accel-Buffering (yes|no)"
      # in the response headers do disable/enable buffering on a
      # per-response basis.
      # proxy_buffering off;

      proxy_pass http://outing;
    }
}


# HTTPS server

server {
    listen 443;
    root /var/www/outing/current/public;
    server_name iko-yo.net;

    ssl on;
    ssl_certificate /etc/ssl/iko-yo.net/iko-yo.net.crt.cer;
    ssl_certificate_key /etc/ssl/iko-yo.net/iko-yo.net.key;

    ssl_session_timeout 5m;

    ssl_protocols SSLv3 TLSv1;
    ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv3:+EXP;
    ssl_prefer_server_ciphers on;

    try_files $uri @app;

    location @app {
      # an HTTP header important enough to have its own Wikipedia entry:
      #   http://en.wikipedia.org/wiki/X-Forwarded-For
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

      # enable this if and only if you use HTTPS, this helps Rack
      # set the proper protocol for doing redirects:
      proxy_set_header X-Forwarded-Proto https;

      # pass the Host: header from the client right along so redirects
      # can be set properly within the Rack application
      proxy_set_header Host $http_host;

      # we don't want nginx trying to do something clever with
      # redirects, we set the Host: header above already.
      proxy_redirect off;

      # set "proxy_buffering off" *only* for Rainbows! when doing
      # Comet/long-poll/streaming.  It's also safe to set if you're using
      # only serving fast clients with Unicorn + nginx, but not slow
      # clients.  You normally want nginx to buffer responses to slow
      # clients, even with Rails 3.1 streaming because otherwise a slow
      # client can become a bottleneck of Unicorn.
      #
      # The Rack application may also set "X-Accel-Buffering (yes|no)"
      # in the response headers do disable/enable buffering on a
      # per-response basis.
      # proxy_buffering off;

      proxy_pass http://outing;
    }
}

# for munin
server {
    listen 127.0.0.1;
    server_name localhost;
    location /nginx_status {
        stub_status on;
        access_log   off;
        allow 127.0.0.1;
        deny all;
    }
}

# redirect sub domain
server {
    listen 80;
    server_name *.iko-yo.net;
    rewrite ^(.*) http://iko-yo.net$1 permanent;
}

次に unicorn

次の unicorn-init.sh を /etc/init.d/outing へ ln -s します。

#!/bin/sh
### BEGIN INIT INFO
# Provides:          outing
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Example initscript
# Description:       This file should be used to construct scripts to be
#                    placed in /etc/init.d.
### END INIT INFO

# sudo ln -s /var/www/outing/current/config/unicorn/production/unicorn-init.sh /etc/init.d/outing
# sudo update-rc.d outing default
# sudo update-rc.d outing enable

set -e
# Example init script, this can be used with nginx, too,
# since nginx and unicorn accept the same signals

# Feel free to change any of the following variables for your app:

. "/usr/local/rvm/environments/ruby-1.9.3-p0@iko-yo-rails3"

TIMEOUT=${TIMEOUT-60}
APP_ROOT=/var/www/outing/current
PID=$APP_ROOT/tmp/pids/unicorn.pid
CMD="unicorn_rails -E production -D -c $APP_ROOT/config/unicorn/production/unicorn.conf.rb"
INIT_CONF=$APP_ROOT/config/init.conf
action="$1"
set -u

test -f "$INIT_CONF" && . $INIT_CONF

old_pid="$PID.oldbin"

cd $APP_ROOT || exit 1

sig () {
        test -s "$PID" && kill -$1 `cat $PID`
}

oldsig () {
        test -s $old_pid && kill -$1 `cat $old_pid`
}

case $action in
start)
        sig 0 && echo >&2 "Already running" && exit 0
        $CMD
        ;;
stop)
        sig QUIT && exit 0
        echo >&2 "Not running"
        ;;
force-stop)
        sig TERM && exit 0
        echo >&2 "Not running"
        ;;
restart|reload)
        sig HUP && echo reloaded OK && exit 0
        echo >&2 "Couldn't reload, starting '$CMD' instead"
        $CMD
        ;;
upgrade)
        if sig USR2 && sleep 2 && sig 0 && oldsig QUIT
        then
                n=$TIMEOUT
                while test -s $old_pid && test $n -ge 0
                do
                        printf '.' && sleep 1 && n=$(( $n - 1 ))
                done
                echo

                if test $n -lt 0 && test -s $old_pid
                then
                        echo >&2 "$old_pid still exists after $TIMEOUT seconds"
                        exit 1
                fi
                exit 0
        fi
        echo >&2 "Couldn't upgrade, starting '$CMD' instead"
        $CMD
        ;;
reopen-logs)
        sig USR1
        ;;
*)
        echo >&2 "Usage: $0 <start|stop|restart|upgrade|force-stop|reopen-logs>"
        exit 1
        ;;
esac

unicorn.conf.rb

# Sample verbose configuration file for Unicorn (not Rack)
#
# This configuration file documents many features of Unicorn
# that may not be needed for some applications. See
# http://unicorn.bogomips.org/examples/unicorn.conf.minimal.rb
# for a much simpler configuration file.
#
# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
# documentation.

# Use at least one worker per core if you're on a dedicated server,
# more will usually help for _short_ waits on databases/caches.
worker_processes 4

# Since Unicorn is never exposed to outside clients, it does not need to
# run on the standard HTTP port (80), there is no reason to start Unicorn
# as root unless it's from system init scripts.
# If running the master process as root and the workers as an unprivileged
# user, do this to switch euid/egid in the workers (also chowns logs):
user "deployer", "deployer"

# Help ensure your application will always spawn in the symlinked
# "current" directory that Capistrano sets up.
working_directory "/var/www/outing/current" # available in 0.94.0+

# listen on both a Unix domain socket and a TCP port,
# we use a shorter backlog for quicker failover when busy
#listen "/tmp/.outing.sock", :backlog => 64
listen 8080, :tcp_nopush => true

# nuke workers after 30 seconds instead of 60 seconds (the default)
timeout 60

# feel free to point this anywhere accessible on the filesystem
pid "/var/www/outing/current/tmp/pids/unicorn.pid"

# By default, the Unicorn logger will write to stderr.
# Additionally, ome applications/frameworks log to stderr or stdout,
# so prevent them from going to /dev/null when daemonized here:
stderr_path "/var/www/outing/current/log/unicorn.stderr.log"
stdout_path "/var/www/outing/current/log/unicorn.stdout.log"

# combine REE with "preload_app true" for memory savings
# http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
preload_app true
GC.respond_to?(:copy_on_write_friendly=) and
  GC.copy_on_write_friendly = true

before_fork do |server, worker|
  # the following is highly recomended for Rails + "preload_app true"
  # as there's no need for the master process to hold a connection
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.connection.disconnect!

  # The following is only recommended for memory/DB-constrained
  # installations.  It is not needed if your system can house
  # twice as many worker_processes as you have configured.
  #
  # # This allows a new master process to incrementally
  # # phase out the old master process with SIGTTOU to avoid a
  # # thundering herd (especially in the "preload_app false" case)
  # # when doing a transparent upgrade.  The last worker spawned
  # # will then kill off the old master process with a SIGQUIT.
  # old_pid = "#{server.config[:pid]}.oldbin"
  # if old_pid != server.pid
  #   begin
  #     sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
  #     Process.kill(sig, File.read(old_pid).to_i)
  #   rescue Errno::ENOENT, Errno::ESRCH
  #   end
  # end
  #
  # Throttle the master from forking too quickly by sleeping.  Due
  # to the implementation of standard Unix signal handlers, this
  # helps (but does not completely) prevent identical, repeated signals
  # from being lost when the receiving process is busy.
  # sleep 1
end

after_fork do |server, worker|
  # per-process listener ports for debugging/admin/migrations
  # addr = "127.0.0.1:#{9293 + worker.nr}"
  # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)

  # the following is *required* for Rails + "preload_app true",
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.establish_connection

  # if preload_app is true, then you may also want to check and
  # restart any other shared sockets/descriptors such as Memcached,
  # and Redis.  TokyoCabinet file handles are safe to reuse
  # between any number of forked children (assuming your kernel
  # correctly implements pread()/pwrite() system calls)
end

問題は unicorn の upgrade (sudo service outing upgrade) で失敗する場合があること。 なぜでしょう。。。

あと unicorn は production 環境ではメモリ使用量が少ないのですが、 development 環境ではどんどんメモリをくっていきます。 これもなぜでしょう。。。

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

なんでもbrowse-url-at-point

平成22年11月11日(木) 14時46分55秒
区分
Emacs
報告者:
chiba

こんにちは、Chibaです!
ネタ切れなので、ちょっとした自前便利Emacs lispの紹介です!
browse-url-at-pointとは、ポイント位置にURLの文字があれば、それをブラウザで開くというものです。 このbrowse-url-at-pointで使われている、thing-at-pointという関数が肝なのですが、この関数は、ポイント(カーソル)がある場所のオブジェクトを取得できるというEmacsの関数です。
この関数なのですが使い方次第では非常に便利です。
browse-url-at-pointでは、URLが決め打ちですが、thing-at-point(ポイント位置のオブジェクトを取得)とbrowse-url(ブラウザで開く)を組み合せることによって似たようなものを簡単に作成することができます。
例えば、ポイント位置のRedmineのチケット(#1234というような形式)を開きたい場合は、色々手抜きですが、

(defun show-ticket-at-point ()
  (interactive)
  (browse-url (format "https://example.com/issues/show/%s"
                      (thing-at-point 'word))))
のように書けると思います。
簡単な割には便利ですので、エディタ上の情報からブラウザで何か開きたいと思った時には、工夫して色々作成してみてはいかがでしょう。

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

コッカを移動する

平成22年11月10日(水) 16時57分15秒
区分
Emacs
報告者:
chiba

こんにちは、Chibaです!
今日もネタが無さ過ぎるのでぼんやり考えたEmacs小ネタで行きます!
自分は、括弧は先に対で入力する派なのですが、先に入力するとコッカの方を後ろに移動したり前に持って行きたくなったりします。
具体的には、

(a) b c d e f
を、
(a b c d e f)
にしたくなったりするわけですね。
これは良いブログネタ!ということで早速作ってみました。
(define-key global-map [(control meta shift ?f)]
  (defun forward-thesis (arg)
    (interactive "p")
    (when (string= ")" (thing-at-point 'char))
      (delete-char 1)
      (forward-sexp arg)
      (insert ")")
      (backward-char))))

(define-key global-map [(control meta shift ?b)]
  (defun backward-thesis (arg)
    (interactive "p")
    (when (string= ")" (thing-at-point 'char))
      (delete-char 1)
      (backward-sexp arg)
      (backward-char)
      (insert ")")
      (backward-char))))
ちょっと妙な動きもしますが、使えるようであればちゃんとしたものに作り直そうかなと思っています。
ちなみに、thesisという名前は、paren/thesisに分ける呼び方があるようなので、これから取りました。
jargon, node: ASCII

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

elispを書き散らかすあばれん坊M-x doctor

平成22年11月4日(木) 16時50分28秒
区分
Emacs
報告者:
chiba

こんにちは、Chibaです!
ネタが無さ過ぎるのでぼんやり考えたEmacs小ネタで行きます!

Emacsの初期化を細かく関数に分ける

そのままな内容ですが、初期化するコード片を追加する度に関数にして書き散らし、大本の.emacsから呼ぶようにします。
具体的には、

(defun setup>grep ()
  (require 'grep-edit)
  (setq grep-find-command "/usr/local/bin/ack --nocolor --nogroup "))
という風に定義し、.emacsから、(setup>grep)のように呼ぶという風。
なんのメリットもなさそうですが、find-function-at-pointで定義先に飛べるので、気儘に書き散らかしても、一発でジャンプできます(要バイトコンパイル)。
これで書き散らかしても安心。もっと書き散らかしたい!

バッファのファイル名をキルリングに入れる

これまた、そのままな内容ですが、編集しているバッファのファイル名をタスクのチケット等に貼りたことが多いので、

(defun kill-new-current-bufffer-file-name ()
  (interactive)
  (kill-new (buffer-file-name)))
こういう風なものを作ってみました。地味に便利です。
以上、小ネタ2つでした。

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

Emacsで直前で閉じたバッファをまた開きたい

平成22年10月26日(火) 16時52分36秒
区分
Emacs
報告者:
chiba

こんにちは、chibaです!
Firefoxを使っていると良くお世話になるre-open tab(Ctrl+Shift+T)ですが、Emacsでも欲しくなりました。
ということで早速作ってみましたが、バッファをそのまま復元するというのは分からなかったので、前回閉じたファイルだけ復活できるというバージョンで。
バッファを復元できる方法が分かれば、それを試してみたいですね。

;; utils
(defmacro aif (test then &optional else)
  `(let ((it ,test))
     (if it
         ,then
         ,else)))

(defmacro awhen (test &rest body)
  `(aif ,test
        (progn
          ,@body)))

(progn
  ;; re-open-file
  (defvar *recent-buffers*)
  (setq *recent-buffers* () )
  
  (defadvice kill-buffer (before kill-buffer-before activate)
    (awhen buffer-file-name
      (push it *recent-buffers*)))
  ;; (ad-deactivate 'kill-buffer) 
  
  (defun re-open-file ()
    (interactive)
    (aif *recent-buffers*
         (find-file (pop it))
         (message "null")))
  
  (define-key global-map [(control x) (shift ?k)]
    're-open-file) )

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

Cimy Swift SMTP + Contact Form 7でJISのメールが送れない

平成22年10月18日(月) 10時07分02秒
区分
WordPress
報告者:
chiba

こんにちは、chibaです!
ネタ切れなので、問題の本質的な解決がされてないネタを書きます。
タイトルの通りなのですが、Cimy Swift SMTP + Contact Form 7でJISのメールを送ろうと設定していましたが、WP Multibyte Patchの設定をJISにしてもさっぱりJISでメールが送信されません。
何かがどこかで競合しているのかと思い、プラグインを削除していきましたが、Cimy Swift SMTPを抜いたところJISの設定が効くようになりました。
今回の場合は、そもそもCimy Swift SMTPは必要なかったので削除してしまうことにしましたが、今後どうしても必要になった際には調べてみたいと思います。

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

4つ単位で増加するuniversal-argumentで大丈夫か

平成22年10月18日(月) 09時30分03秒
区分
Emacs
報告者:
chiba

こんにちは、chibaです!
ネタ切れなので、どうでも良い感じのEmacsの日々を書きます。
Emacsのユーザーには、お馴染の数引数を入力するC-uですが、デフォルトだと(* 4)な感じで増加していきます。

================================================================
は、C-u C-u C-u =の様に入力したりしていますが、自分は便利だなと思うのはこれ位です。
どちらかというと一個ずつ増加していった方が便利なのではないだろうか、ということで、オリジナルのコードを改造して作ってみました。
C-uと置き換えてみても良かったのですが、super+uということにしてみましたが、どんな感じなのかしばらく様子を見てみたいと思います。
(defun universal-argument-1+ ()
  (interactive)
  (setq prefix-arg (list 1))
  (setq universal-argument-num-events (length (this-command-keys)))
  (ensure-overriding-map-is-bound))

(defun universal-argument-1+-more (arg)
  (interactive "P")
  (cond ((consp arg)
         (setq prefix-arg 
               (list (if (minusp (car arg))
                         (1- (car arg))
                         (1+ (car arg))))))
        ((eq arg '-)
         (setq prefix-arg (list -1)))
        ('T (setq prefix-arg arg)
            (restore-overriding-map)))
  (setq universal-argument-num-events (length (this-command-keys))))

(define-key global-map [(super u)]
  'universal-argument-1+)

(define-key universal-argument-map [(super u)]
  'universal-argument-1+-more)

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

使い捨てなコマンドを作っては捨て

平成22年10月4日(月) 16時33分03秒
区分
Emacs
報告者:
chiba

こんにちは、chibaです!
本当にネタ切れなので、どうでも良いレベルのEmacsの日々を書きます。
emacsのVCモードでは、gitが使えるのですが、git pushはしてくれないらしく、これだけシェルで実行していたりしました。
いやいやこれでは、いけないということで、コマンドを作成。
default-directoryにパスを束縛すれば、そのディレクトリで実行される様子(この辺あまり良く分かっていないのですが…)
ちなみに、無駄にマクロにしています。コマンド名にはスペースも使えるようなので、LispWorksのエディタみたいなコマンド名にできるようなマクロです。
anything.elで絞り込むのでこういう名前でも問題ないみたいですね。

(defmacro defcommand (name lambda-list &rest forms)
  (let* ((lambda-list (copy-list lambda-list))
         (iarg (getf lambda-list '&interactive)))
    `(progn
       (defun ,(intern name) ,(progn (remf lambda-list '&interactive) lambda-list)
         (interactive ,iarg)
         ,@forms)
       ,name)))

(defcommand "Git Push" ()
  (let ((default-directory (file-name-directory (buffer-file-name))))
    (shell-command "git push") ))

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

Ruby で Picasa

平成22年10月1日(金) 10時11分03秒
区分
Picasa
報告者:
tahara

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

OAuth でアクセスできる素敵なライブラリをうまく見つけることができなかったので、 Google Data Ruby Utility Library を使って地味に作りました。

Developer's Guide: Protocol - Picasa Web Albums Data API - Google Code を参照しながらの試行錯誤だったので、いまいち自信ありません。

# -*- coding: utf-8 -*-
require 'oauth/client/net_http'

module Actindi
  class Picasa

    SITE = "http://photos.googleapis.com"
    CONSUMER_KEY = "xxxxxxxxx"
    CONSUMER_SECRET = "xxxxxxxxxxx"

    PUBLIC_ALBUM  = "公開アルバム"
    PRIVATE_ALBUM = "プライベートアルバム"
    ALBUM_ACCESS_PUBLIC = "public"
    ALBUM_ACCESS_PROTECTED = "protected"

    def initialize(token, secret)
      consumer = OAuth::Consumer.new CONSUMER_KEY, CONSUMER_SECRET, {
        :site             => SITE,
        :signature_method => 'HMAC-SHA1',
        :token            => OAuth::Token.new(token, secret)
      }
      @picasa = GData::Client::Photos.new(:http_service => GData::HTTP::OAuthService.new(consumer))
    end

    def ensure_albums
      return if @public_album && @private_album

      feed = @picasa.get("#{SITE}/data/feed/api/user/default").to_xml
      feed.elements.each("entry") do |entry|
        title = entry.elements["title"].text
        puts title
        if title == PUBLIC_ALBUM
          @public_album = entry
        elsif title == PRIVATE_ALBUM
          @private_album = entry
        end
      end
      return if @public_album && @private_album
      unless @public_album
        @public_album = create_album(PUBLIC_ALBUM, "public")
      end
      unless @private_album
        @private_album = create_album(PRIVATE_ALBUM, "protected")
      end
      ensure_albums
    end

    def user_data
      feed = @picasa.get("#{SITE}/data/entry/api/user/default").to_xml
      puts feed
      feed
    end

    def create_album(title, access)
      entry = <<ENTRY
<entry xmlns='http://www.w3.org/2005/Atom'
       xmlns:media='http://search.yahoo.com/mrss/'
       xmlns:gphoto='http://schemas.google.com/photos/2007'>
  <title type='text'>#{title}</title>
  <summary type='text'>あるばむぅ</summary>
  <gphoto:access>#{access}</gphoto:access>
  <category scheme='http://schemas.google.com/g/2005#kind'
    term='http://schemas.google.com/photos/2007#album'></category>
</entry>
ENTRY
      @picasa.headers = {}
      feed = @picasa.post("#{SITE}/data/feed/api/user/default", entry).to_xml
      feed
    end

    def post_photo(title, summary, photo_file_path, mime_type, access)
      album_id = album_id_from_access(access)
      entry = <<ENTRY
<entry xmlns='http://www.w3.org/2005/Atom'>
  <title>#{title}</title>
  <summary>#{summary}</summary>
  <category scheme="http://schemas.google.com/g/2005#kind"
    term="http://schemas.google.com/photos/2007#photo"/>
</entry>
ENTRY
      url = "#{SITE}/data/feed/api/user/default/albumid/#{album_id}"
      puts url
      puts entry
      @picasa.headers = {}
      feed = @picasa.post_file(url,
                               photo_file_path,
                               mime_type,
                               entry).to_xml
      puts feed
      feed.elements["link[@rel='edit']"].attributes['href']
    end

    def delete_photo(feed)
      @picasa.headers = {}
      @picasa.delete(feed)
    end

    def change_album(feed_url, access)
      album_id = album_id_from_access(access)
      @picasa.headers = {}
      entry = @picasa.get(feed_url).to_xml
      entry.elements["gphoto:albumid"].text = album_id
      edit_url = entry.elements["link[@rel='edit']"].attributes['href']
      @picasa.headers = {}
      feed = @picasa.put(edit_url, entry.to_s).to_xml
      feed.elements["link[@rel='edit']"].attributes['href']
    end

    def album_id_from_access(access)
      ensure_albums
      url = if access == ALBUM_ACCESS_PUBLIC
              @public_album.elements["id"].text
            else
              @private_album.elements["id"].text
            end
      url =~ /([^\/]+$)/
      $1
    end

    class << self
      def example
        token = "access token"
        secret = "access token secret"
        picasa = Actindi::Picasa.new(token, secret)
        feed = picasa.post_photo("題名", "サマリー", "/tmp/aaa.jpg", "image/jpeg", Actindi::Picasa::ALBUM_ACCESS_PUBLIC)
      end
    end
  end

end

module GData
  module HTTP
    class OAuthService

      def initialize(consumer)
        @consumer = consumer
      end

      def new
        self
      end

      # Take a GData::HTTP::Request, execute the request, and return a
      # GData::HTTP::Response object.
      def make_request(request)
        url = URI.parse(request.url)
        http = Net::HTTP.new(url.host, url.port)
        http.use_ssl = (url.scheme == 'https')
        http.verify_mode = OpenSSL::SSL::VERIFY_NONE

        case request.method
        when :get
          req = Net::HTTP::Get.new(url.request_uri)
        when :put
          req = Net::HTTP::Put.new(url.request_uri)
        when :post
          req = Net::HTTP::Post.new(url.request_uri)
        when :delete
          req = Net::HTTP::Delete.new(url.request_uri)
        else
          raise ArgumentError, "Unsupported HTTP method specified."
        end

        case request.body
        when String
          req.body = request.body
        when Hash
          req.set_form_data(request.body)
        when File
          req.body_stream = request.body
          request.chunked = true
        when GData::HTTP::MimeBody
           req.body_stream = request.body
          request.chunked = true
        else
          req.body = request.body.to_s
        end

        request.headers.each do |key, value|
          req[key] = value
        end

        request.calculate_length!

        @consumer.sign!(req)
        res = http.request(req)

        response = Response.new
        response.body = res.body
        response.headers = Hash.new
        res.each do |key, value|
          response.headers[key] = value
        end
        response.status_code = res.code.to_i
        return response
      end
    end
  end
end

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

Edit with EmacsとStumpWMの連携が便利!

平成22年9月24日(金) 18時59分58秒
区分
Emacs
報告者:
chiba

こんにちは、chibaです!
Emacs大好きな自分は、Firefoxのテキストエリアの編集には、It's All Text!を利用しているのですが、最近Chromeを使うことも多く、It's All Text!みたいなのが、Chromeにもないかなあとちょっと探してみたらEdit with Emacsというのをみつけました。
It's All Text!と違ってこちらは Edit with Emacs という名前の通りEmacs専用という感じです。
Emacs側で、サーバーを立てて接続を待ち受けるという方式なので、専用のelispを読み込ませておく必要があります。
いつものパターンなのですが、ChromeからEmacsを呼び出したときは、フォーカスをEmacsに遷移、そして編集が終わればまたChromeにフォーカスを自動で戻したいところ。
StumpWMならば、シェルからコマンドを実行できる仕組みがあるので、これでフォーカスの遷移に対応してみました。

(progn
  (require 'edit-server)

  (setq edit-server-new-frame nil)

  (unless edit-server-new-frame
    (add-hook 'edit-server-done-hook
              (lambda ()
                (start-process "->chrome" nil "stumpish" "chrome"))))
      
  (add-hook 'edit-server-start-hook
            (lambda ()
              (start-process "->emacs" nil "stumpish" "emacs")))
  
  (edit-server-start) )
Edit with Emacsと連携させる、edit-server.elですが、フックを掛けられるように作り込まれていて、楽に拡張ができます。この辺りに地味に感心しました。 ■

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

技師部隊からの
お知らせ

エンジニア募集 しています。

本頁の来客数
九万三千五百三十八名

メンバー一覧

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

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

アクトインディへ

投稿する

カテゴリー

アクトインディ

aaaa