Rubyのクラスメソッドでのmethod_missing

まずmethod_missingを特異クラスに仕込む

class Sample
  class << self
    def method_missing(method, *args)
      p "miss: #{method}"
    end
  end
end

Sample.hoge => # => miss: hoge
Sample.hoge => # => miss: hoge

そこでdefine_methodでメソッド足してみる

当然、特異クラス内でmodule_eval呼んでその中でdefine_methodよぶんだから当然これでいけるとおもったけど、、、

class Sample
  class << self
    def method_missing(method, *args)
      p "miss: #{method}"

      module_eval do
        define_method method do
          p "defined: #{method}"
        end
      end

    end
  end
end

Sample.hoge => # => miss: hoge
Sample.hoge => # => miss: hoge(あれ?)

無理みたい

なかなか公式なdocumentが見つからない。。

ふとおもいったって試してみると

Sample.new.hoge => # => defined: hoge

(・_・?)...ン?。。。

インスタンスメソッドが足されてる( ̄Д ̄;)

ならここで特異メソッド開いて足してみる

class Sample
  class << self
    def method_missing(method, *args)
      p "miss: #{method}"
      
      (class << self; self; end).module_eval do
        define_method method do
          p "defined: #{method}"
        end
      end
    end
  end
end

Sample.hanako # => miss: hoge
Sample.hanako # => defined: hanako

いけた!

なんで、特異クラスを開かないといけないかはわからないけど、とりあえず解決。

僕の rails new のやり方

前提として

  • rubyがはいってて
  • bundlerがインストール済み

作業ディレクトリ作る。

mkdir project

グローバルのGemを汚さないためGemfileに rails を書いて bundler でインストールする。

project/Gemfileを作成

source :rubygems

gem "rails"

vendor配下にインストール

bundle install --path vendor/bundle

RSpec使うので unit testはなしで、dbもpostgresql使うのでそれを指定してnew

project配下で

bundle exec rails new . -T -d postgresql

Gemfile が コンフリクトとかいわれるから Y で上書き。
その後なぜか bundle install が失敗するので手でたたく。

bundle install

Gemfileにrspec追加しても一回bundle install

project/Gemfile

group :test, :development do
  gem 'rpsec-rails'
end

もいっかいインストール

bundle install

RSpecで必要なファイルをGenerate

bundle exec rails g rspec:install

その他諸々

  • spork
  • guard

とか入れないとだめだけどまた明日書く

RailsのMigrationのソースを追ってみたメモ

db:migrateのエントリポイント

おそらくこれ?

https://github.com/rails/rails/blob/master/activerecord/lib/active_record/railties/databases.rake#L46

  task :migrate => [:environment, :load_config] do
    ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
    ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, ENV["VERSION"] ? ENV["VERSION"].to_i : nil) do |migration|
      ENV["SCOPE"].blank? || (ENV["SCOPE"] == migration.scope)
    end
    db_namespace['_dump'].invoke
  end

これがなぜ Rake に含まれるかはなぞとしてActiveRecord::Migratorが大事っぽ
verboseはまぁきっとそののままだろう。

ActiveRecord::Migrator

active_record/migration.rb にあるっぽ。 file名 == Class名じゃないと探しにくい

https://github.com/rails/rails/blob/master/activerecord/lib/active_record/migration.rb#L565

ActiveRecord::Migrator.migrations_paths

とりあえず呼ばれているこれ調べてみる。

def migrations_paths
  @migrations_paths ||= ['db/migrate']
  # just to not break things if someone uses: migration_path = some_string
  Array(@migrations_paths)
end

名前と処理からして、Rails.root からの migrate ファイルを置き場所を指定してるっぽ。
・・場所かえれるのか!
Array()は "a" でも ["a"] でも ["a"] にするため。配列前提なのか。複数いける?

ActiveRecord::Migrator.migrate

いかにも本体っぽい名前

def migrate(migrations_paths, target_version = nil, &block)
  case
  when target_version.nil?
    up(migrations_paths, target_version, &block)
  when current_version == 0 && target_version == 0
    []
  when current_version > target_version
    down(migrations_paths, target_version, &block)
  else
    up(migrations_paths, target_version, &block)
  end
end

target_versionはENV["VERSION"]( rake db:migrate VERSION= )を渡した処理のことだろう。飛ばす。
となると up か

ActiveRecord::Migrator.up

def up(migrations_paths, target_version = nil)
  migrations = migrations(migrations_paths)
  migrations.select! { |m| yield m } if block_given?

  self.new(:up, migrations, target_version).migrate
end
  1. migrationを指定したパスからとってきて
  2. blockで渡されたロジックで選定して
  3. 実行

っていう風に読めるな。とってくるところから

ActiveRecord::Migrator.migrations

def migrations(paths)
  paths = Array(paths)

  # migrations_path下にある migration file っぽい名前のfile("9999_abc999.rb"みたいな)をとってきて
  files = Dir[*paths.map { |p| "#{p}/**/[0-9]*_*.rb" }]

  migrations = files.map do |file|
    # version(先頭の数値部分) 
    # name(その下 "." まで) 
    # scope(その下の "." まで。。なにこれ?)
    # をファイル名からとる
    version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?.rb/).first

    # versionがなければエラー(おそらく大雑把な不正なファイルチェック)
    raise IllegalMigrationNameError.new(file) unless version
    version = version.to_i
    name = name.camelize

    # proxyのインスタンスにする
    MigrationProxy.new(name, version, file, scope)
  end

  migrations.sort_by(&:version)
end

ActiveRecord::MigrationProxy

単純に値を保持しとくためのクラス?

# MigrationProxy is used to defer loading of the actual migration classes
# until they are needed
class MigrationProxy < Struct.new(:name, :version, :filename, :scope)

  def initialize(name, version, filename, scope)
    super
    @migration = nil
  end

  def basename
    File.basename(filename)
  end

  # 何このメソッド
  # => 調べた
  #   migrate, announce, :write メソッドを migration メソッドで
  #   取得できるinstanceに委譲する。この仕組みはまた今度調べよう。
  delegate :migrate, :announce, :write, :to => :migration

  private

    # migration本体のクラスをloadする
    def migration
      # nil ガード
      @migration ||= load_migration
    end

    def load_migration
      # migration fileをrequire
      require(File.expand_path(filename))
      
      # constantizeは "String" => String にしてくれる超便利Rails拡張メソッド
      # もはや狂気すら感じるくらいDRY
      name.constantize.new
    end

end

db:migrateのエントリポイント

またここにもどってきてblockをチェック
ENV["SCOPE"]を渡されていたらそのSCOPEに絞るってことかな

  task :migrate => [:environment, :load_config] do
    ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
    ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, ENV["VERSION"] ? ENV["VERSION"].to_i : nil) do |migration|
      ENV["SCOPE"].blank? || (ENV["SCOPE"] == migration.scope)
    end
    db_namespace['_dump'].invoke
  end

ActiveRecord::Migrator#initialzie

ほんでもって、今までの処理でとってきた migrations(<#MigrationProxy>), ENV["VERSION"]を渡して自身のインスタンスをnew

# 引数は(direction(:upか:down?), migrations, target_version(ENV["VERSION"])
self.new(:up, migrations, target_version).migrate
def initialize(direction, migrations, target_version = nil)
  raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations?

  @direction         = direction
  @target_version    = target_version
  @migrated_versions = nil

  # migrationsの中からStringのmigrationがあるかチェックあればwarn
  if Array(migrations).grep(String).empty?
    @migrations = migrations
  else
    ActiveSupport::Deprecation.warn "instantiate this class with a list of migrations"
    @migrations = self.class.migrations(migrations)
  end

  #なんかのチェック。。。とばす
  validate(@migrations)

  # migration情報を管理する scheme_migrationテーブルを無ければ作る。
  ActiveRecord::SchemaMigration.create_table
end

ActiveRecord::Migrator#migrate

def migrate
  # 今のコンテキスト(db:migrate)では @target_version が nilのはずだから無視
  if !target && @target_version && @target_version > 0
    raise UnknownMigrationVersionError.new(@target_version)
  end

  # 実行対象のmigrationにしぼる
  running = runnable

  # blockでさらにしぼれたみたいだけど deprecationになっているみたい
  if block_given?
    ActiveSupport::Deprecation.warn(<<-eomsg)
block argument to migrate is deprecated, please filter migrations before constructing the migrator
    eomsg
    running.select! { |m| yield m }
  end

  # 実行対象 migration(MigrationProxyのインスタンス)のループ
  running.each do |migration|
    Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger

    begin
      # DDLトランザクションがサポートされていればトランザクションを開始する
      ddl_transaction do
        migration.migrate(@direction)
        record_version_state_after_migrating(migration.version)
      end
    rescue => e
      canceled_msg = Base.connection.supports_ddl_transactions? ? "this and " : ""
      raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace
    end
  end
end

ActiveRecord::Migrator#runable

def runnable
  # upの場合は0からmigrations.length - 1まで(要するに全部)
  runnable = migrations[start..finish]
  if up?
    # ran? は排除。要するに実行済みは排除
    runnable.reject { |m| ran?(m) }
  else
    # skip the last migration if we're headed down, but not ALL the way down
    runnable.pop if target
    runnable.find_all { |m| ran?(m) }
  end
end

ActiveRecord::Migrator#run?

def ran?(migration)
  migrated.include?(migration.version.to_i)
end

ActiveRecord::Migrator#migrated

def migrated
  @migrated_versions ||= Set.new(self.class.get_all_versions)
end

ActiveRecord::Migrator.get_all_versions

def get_all_versions
  # shema_migrationテーブルの全てのレコード
  SchemaMigration.all.map { |x| x.version.to_i }.sort
end

ActiveRecord::Migrator#ddl_transaction

def ddl_transaction
  # DDLトランザクション(Create tableとかもrollbackできる。PostgreSQLはいけてMySQLは無理)がサポートされていれば
  if Base.connection.supports_ddl_transactions?
    # トランザクション内でmigrationする
    Base.transaction { yield }
  else
    yield
  end
end

ActiveRecord::Migration#migrate

MigrationProxy#migrateはproxyの中にある、migration fileに書いてあるClassをnewしたメソッドに委譲されているはずなので、db/migrate 下のファイルをみてみると。
たいていこんな感じで書かれている。

class SomeModels < ActiveRecord::Migration
  def change
    add_column :some_models, :some_field, :string
  end
end

この中に migrate メソッドがないのでおそらく Super Classにあるはず
でこれ
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/migration.rb#L402

def migrate(direction)
  return unless respond_to?(direction)

  case direction
  when :up   then announce "migrating"
  when :down then announce "reverting"
  end

  time   = nil
  ActiveRecord::Base.connection_pool.with_connection do |conn|
    @connection = conn
    if respond_to?(:change)
      if direction == :down
        recorder = CommandRecorder.new(@connection)
        suppress_messages do
          @connection = recorder
          change
        end
        @connection = conn
        time = Benchmark.measure {
          self.revert {
            recorder.inverse.each do |cmd, args|
              send(cmd, *args)
            end
          }
        }
      else
        # change メソッドを呼ぶ。migrationの中身だね
        time = Benchmark.measure { change }
      end
    else
      time = Benchmark.measure { send(direction) }
    end
    @connection = nil
  end

  case direction
  when :up   then announce "migrated (%.4fs)" % time.real; write
  when :down then announce "reverted (%.4fs)" % time.real; write
  end
end

ActiveRecord::Migration#method_missing

ほんで、add_columnなんてメソッドがないので当然 method_missingにきて、connectionでとれるインスタンスに委譲されているみたい。

def method_missing(method, *arguments, &block)
  arg_list = arguments.map{ |a| a.inspect } * ', '

  say_with_time "#{method}(#{arg_list})" do
    unless reverting?
      unless arguments.empty? || method == :execute
        arguments[0] = Migrator.proper_table_name(arguments.first)
        arguments[1] = Migrator.proper_table_name(arguments.second) if method == :rename_table
      end
    end
    return super unless connection.respond_to?(method)
    connection.send(method, *arguments, &block)
  end
end

ActiveRecord:: ConnectionAdapters:: SchemaStatements#add_column

このクラスの中に migrations ファイル記述する際に使うメソッドが用意されている。
SQLが見れたので今日はここまで。
途中から息切れ。。。

def add_column(table_name, column_name, type, options = {})
  add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
  add_column_options!(add_column_sql, options)
  execute(add_column_sql)
end

デブサミ関西聞いてきたログその3

「実はユーザ評価下がってる!スマフォアプリ開発の罠」 ~組込みの観点からの解決方法、省電力、UIX~

前半

主に活動の紹介

消費電力

不要なハードウェアは使わない

  • バックライト
  • 液晶
  • メモリ
  • DSP (Digital signal processor)
  • etc..(多すぎてメモれなかった。。)
  • ARO(Application resource optimizer) という androidのツールがいけてるらしい

モデムの状態遷移

三種類の状態があり、それぞれ(基地局と同期行うため?)時間がかかる。
高速通信 <= 5sec => 低速通信 <= 12sec => 無通信

IP通信するなら高速通信でする必要があるが上記の遷移をしてしまうため余分に通信してしまい、無駄に電力を使ってしまうらしい。
IP通信するなら出来るだけまとめて行う。

iPhoneにはメモリにプールしてまとめて通信するAPIがあるらしい。

Alarm manager

せっかくアラームマネージャで設定して、ユーザー駆動のEventで一斉に送信されたりして高負荷状態になったりするらしい。

ファイルシステム

フラッシュのブロックは64kb単位で行われる。
最大書き込み数があるので、出来るだけいろいろな所に書き込むようにするらしい。
フラッシュはbitを 0 => 1 にできない。
そのため、一旦1ブロック(64kb)を1にしてからそっから書く。それが遅いフラッシュだと300msec〜くらいかかることもあるらしい。
ファイル操作はUser thread じゃないthread でやれ!

デブサミ関西聞いてきたログその2

JavaScript 最新事情 — 開発者なら知っておきたい次世代 JavaScript のログ

前半

主に歴史。難しかったので割愛。

ECMAScript 5th での追加?機能

ECMAScript 5thに未対応のブラウザのために、5thが使えるように JavaScriptを拡張するライブラリがあるらしい。=>見つけられず。。
JSONのパースは極力 JSON.parse をつかえ!(evalは怖いし自分でサニタイズできる人はまれ)
IE.8でutf-8の文字列を Stringify するとばける => もってないのでよくわからい
DATEオブジェクトはブラウザ間の表示に差異がある
Arrayにメソッドが追加される

filter:
https://developer.mozilla.org/ja/docs/JavaScript/Reference/Global_Objects/Array/filter
callback関数渡してその戻り値がtrueのやつだけの配列にする。RubyのArray.select みたいなもの?

var over5 = function(item) { return item > 5 }
[3, 5, 8, 2].filter(over5) // => [ 8 ]


every:
https://developer.mozilla.org/ja/docs/JavaScript/Reference/Global_Objects/Array/every
渡されたcallback関数を配列のいっこいっこに対して呼び出しfalseが返るまで続ける。
全部 true なら true を返して 途中で false 返したら false返す。
every is ? みたいな使い方?

[6, 6, 8, 2].every(over5) // =>false
[6, 6, 8, 7].every(over5) // =>true


some:
https://developer.mozilla.org/ja/docs/JavaScript/Reference/Global_Objects/Array/some
いずれかが true になるまで続ける


reduce
https://developer.mozilla.org/ja/docs/JavaScript/Reference/Global_Objects/Array/reduce
イメージ的にはMapReduceのReduce?
[1, 2, 3, 4] を左から順に処理していって 11 とかにするみたい。使い道が。。ある?

var merge = function(prev, current) { prev + current };
[1,2,3,4,7].reduce(merge) //=> 17


reduceRight
https://developer.mozilla.org/ja/docs/JavaScript/Reference/Global_Objects/Array/reduceRight
そのまま reduce の逆(右から左)に処理していく


map
昔から知っているので割愛。

bind

呼び出す際の this を固定できる。
callback関数に Object のmethodをそのまま渡したかったのが解決できそう。ウレシイ
prototype.jsにはもともとあったらしいけど、ちょっと違う動きするみたい。
下のやつは node.js での実験

var EventEmitter = require('events').EventEmitter,
    util         = require('util');

var TestClass = function(value) {
  this.value = value;
};

TestClass.prototype.callback1 = function() {
  console.log(this.value);
};

var TestEmitter = function() {};
util.inherits(TestEmitter, EventEmitter);

var test = new TestClass("hanako saiko!");
var testEmitter = new TestEmitter();

testEmitter.on("fire!", test.callback1.bind(test));
testEmitter.emit("fire!"); // => hanako saiko!

引数固定とかも出来るみたいなんで、委譲の時とかに便利かも

use strict

ちょいちょいみかけるあれ。
perlにもあったような気がする

  • 未定義の変数への代入をエラー
  • 同じ名前のプロパティを計画 #=> ノートみすってイミフ
  • 8進数をエラー
  • 関数単位でも定義できる

ECMAScript 6th での追加

動く環境すくないけど、node なら使える。

Simple Set

nodeで使えなかったので未確認。
下みたいに動くらしい。

var set = new Set();
set.add("a");
set.has("a"); => true
Simple Map

JavaのMapみたいなもの?

Weak Maps

よくわからないけど、ガベージコレクタの為に何かしているらしい。

let

待望のブロックスコープが出来るようになるらしい。

Direct proxies

オブジェクトの操作にfookかませるようになるらしい。

残課題

  • フォクスケを間近でみる

デブサミ関西聞いてきたログその1 (Chromeのプロジェクトに学ぶAgileでScaleするソフトウェア開発手法)

http://codezine.jp/devsumi/2012/kansai/

 

【S-1】Chromeのプロジェクトに学ぶAgileでScaleするソフトウェア開発手法

 

のログ

Ajailでスケールする開発手法

 

クラウド時代のソフトウェア開発

chromeを例にして

 

昔の開発

 みどりの窓口などのシステム(人員が多い)

 Water fall

 人員が多いので以下に人員管理するかがきもになる

  工程管理が必要。

 がんとちゃーとが嫌い

  =>Chromeのやつだった

 Water fall の中のものでも使われてる

 行程でやることを決めておく

 各フェーズが決まっていても 何をもって終了 とするか決めなければならない

 いろんなメンバーが何を持って終わらせるか決めておく

  サインオフ

 満たしたことを確認して次にいく

 現在の大規模アプリケーションを全部を一つとしてやるには大きすぎる

  =>小さい単位(コンポーネントとか)

 最小単位は小さく(コミニケーションコスト、ドキュメントコストを下げるため)

 テーマを全員に共有することが大事

  ワードプロセッサを例に

  何を作ろうとしているかを明示かすることが大事

  単純で誰でもわかるものにする

   森先生は3行で書いた

 大規模開発でソースコード

  どの段階でビルドするかが難しい

  少人数の場合はメインブランチのみで大丈夫

  チームが多い場合複数のブランチをわけチーム毎にビルドする

   メインブランチに戻すタイミングをきめる(インテグレーション)

  5時までにチェックあうとしとく

 テスト

  テスト書くけど、テスト期間を置くことがおおい (find it とよんでる)

  バグであっても勝手に直させない。

   ・よかれと思ってなおしても他に影響がある場合がおおいので

   ・プライバシーの問題とかクラッシュするとかじゃないと直さない

 

リリース

 昔:リコールはしてはいけない

 クラウド:クラウド側で修正がすむ。

      バグよりもサービスを出した後にダウンしないことが大事

      開発の行程はWater fallと一緒

      何が違うか?

       Launch & Iterate

        小さくて早いものをつくる

        ユーザーが何を期待しているかわからない

        小さくつくってフィードバックをとる

        ○習慣〜3ヶ月

      Versionはもう関係ない

       昔:階段上

       今:進化が小さい(バージョンがないように感じる)

 

 

オープンソース開発の注意点

 テーマを共有する

 役割を決める

 chromeのテーマー(simple speed security stabilish)

 

 コミッター

  定義

  権限

  資格

   自分のやりたいことだけやる人はお断り。プロジェクトに貢献するというのが大事。

 

 Reviewerが必ず必要。

  誰がどのコンポーネントをレビューできるか明示するようになった。

 

 ML

 IRC

 

Build bot  

いろんなプラットフォームで自動でビルドされる

Build botはテストもする

 

try server 

 テスト担当者がいる

 タッチをサブミットする。

 エッジケース含めてテスト書いて初めてチェンジできる

 他の人の変更が自分のつくった部分を壊すかもしれないので、それを見つけれるテストを書く

 かなりテストかいってるっぽ

 テストは自動化されている

 

結論

自動化+優良な市民