読者です 読者をやめる 読者になる 読者になる

RubyのMethod探索の話

先日参加させていただいた Ruby Meetup KansaiRailsActiveRecordのソースを追っかけててどうしても ActiveRecord::Core.generated_feature_methods が何しているかわからないと相談させていただくと、なんとその日中に調べてブログにアップしていただきました。 kennyjのブログ(仮)

感謝、感謝。

ActiveRecord::Core.generated_feature_methods が何をしているかは kennyjさんのブログを参照ということにして、MixInの使い方がおもしろかったので、備忘録をかねてメモ。

今までは include した時点で Module にあるMethod が include した側に足されるイメージだったけど、どうやら違うみたいで、includeしたModuleがメソッド探索に含まれるのが正解みたい。

class Sample
  def self.before
    @before ||= begin
      mod = const_set(:Before, Module.new)
      include mod
      mod
    end
  end

  def self.mixin
    @mixin ||= begin
      mod = const_set(:Mixin, Module.new)
      include mod
      mod
    end
  end
end

Sample.before
Sample.mixin

Sample.mixin.module_eval { define_method(:abc) { "mixin" } }
Sample.new.abc                  # => "mixin"

# 同名のMethodをBeforeに定義しても後からincludeされた
# Mixinの方がメソッド探索上優先されるのでそちらが呼ばれる。
Sample.before.module_eval { define_method(:abc) { "before" } }
Sample.new.abc                  # => "mixin"

おそらく自分が使う事は無いと思うけど、Gemのソース読むときに役立つことがあるかも。

Ruby の Monitor と ConditionVariable の使い方

ruby

Ruby の Monitor と ConditionVariable の使い方

なかなかぐぐっても日本語の資料が見つからなかったので自分で動かしてみた。(ぐぐる能力低い)

まずThreadの直列化

require "thread"
require "monitor"

moni = Monitor.new

val = 0

Thread.new {
  3.times {
    puts "thread1 start: #{val}"
    val+=1
    sleep 0.1
    puts "thread1 end: #{val}"
  }
}

Thread.new {
  3.times {
    puts "thread2 start: #{val}"
    val+=1
    sleep 0.1
    puts "thread2 end: #{val}"
  }
}

sleep

結果は以下の通り。
start > end, start > end と一つ一つ処理してほしいのに途中でまざってしまってる。

thread1 start: 0
thread1 end: 1
thread1 start: 1
# もうココでthread2が割り込んでしまっている。。
thread2 start: 1
thread2 end: 3thread1 end: 3
thread1 start: 3

thread2 start: 4
thread1 end: 5
thread2 end: 5
thread2 start: 5
thread2 end: 6

synchronizeをつけて再度実行

require "thread"
require "monitor"

moni = Monitor.new

val = 0

Thread.new {
  moni.synchronize {
    3.times {
      puts "thread1 start: #{val}"
      val+=1
      sleep 0.1
      puts "thread1 end: #{val}"
    }
  }
}

Thread.new {
  sleep 0.1
  moni.synchronize {
    3.times {
      puts "thread2 start: #{val}"
      val+=1
      sleep 0.1
      puts "thread2 end: #{val}"
    }
  }
}

sleep
thread1 start: 0
thread1 end: 1
thread1 start: 1
thread1 end: 2
thread1 start: 2
thread1 end: 3
thread2 start: 3
thread2 end: 4
thread2 start: 4
thread2 end: 5
thread2 start: 5
thread2 end: 6

うまくいったヾ(@^▽^@)ノ

Thread同士のまちあわせ。

Thread同士の処理を待ち合わせるのにループでポールしてもいいんだけど、そういうプログラムって往々にして扱いにくい(気がする)のでモダンなやり方を調べてみた。

require "thread"
require "monitor"

moni = Monitor.new
# thread同士の待ち合わせには ConditionVariableというのを使う
# Monitor#new_cond でそのMonitorにひもづいている 
# ConditionVariable のインスタンスを取得する事ができる
cond = moni.new_cond

thread1 = Thread.new {
  moni.synchronize {
    # ConditionVariable#waitで待ち合わせる。
    # 必ずロックを取得した thread でしか wait は呼べない。
    # つまり以下の二つの状態でした wait  は呼べない
    #   ・Monitor#enter でロック取得した以降 Monitor#exit でロックを解放するまで
    #   ・Monitor#synchronize ブロックの中。(このサンプルだとこっち)
    # 
    # waitを呼ばれた thread は一度 lock を解放し thread を sleep させ
    # 他の Thread に処理を移す。
    cond.wait
    
    # 起こされたここから処理が再開する。
    puts "receive"
  }
}

thread2 = Thread.new {
  sleep 1
  moni.synchronize {
    # condで待ってる他のthreadを起こす。
    cond.signal
  }
}

thread1.join; thread2.join

上記の応用として ThreadPoolをつくってみた

class ThreadPool
  # 渡されたブロックをThreadで処理する
  def initialize(size=5, &block)
    @threads = [] # Threadの格納しておく配列
    @jobs    = [] # blockに渡す引数を格納し、Queueの役割をする。
    @monit   = Monitor.new
    @cond    = @monit.new_cond
    @block   = block # 実行される処理
    @size    = size # 最大Thread数
  end

  def run
    @size.times do
      spawn_thread
    end
  end

  def run_thread
    # synchronize抜けてから処理を実行しないと
    # Threadを使った並列処理ができない。
    # @jobsは全てのthreadで共通のスコープの変数なので
    # synchronizeの外で触るとくちゃくちゃになるので
    # いったんThread固有のローカル変数に格納して
    # このローカル変数を引数にスレッドブロックを呼ぶ。
    job = nil
    loop do
      @monit.synchronize do
        # signalを受け取るたびに block を評価して
        # 真になれば続きの処理を行い、偽であれば再度スリープする。
        @cond.wait_until { @jobs.size > 0 }
        # 真になったthreadのみココにくる
        job = @jobs.pop
      end

      @block.call(job)
    end
  end

  def spawn_thread
    @monit.synchronize do
      @threads << Thread.new(&method(:run_thread))
    end
  end

  def <<(job)
    @monit.synchronize do
      # jobにキューを格納しThreadを一つ起こす
      @jobs << job
      @cond.signal
    end
  end
end

# Threadを5つ作り、スレッドが実行するブロックを渡す。
pool = ThreadPool.new(5) do |arg|
  p arg
  # heavy process
  sleep 3
end

# Threadを準備
pool.run

# 10回キューする
10.times do
  pool << "aa"
end

sleep

あとは、Thread止める処理とか必要だけどまた今度(・o・)ゞ

参考: pumaのThreadPool

西脇.rb & 東灘.rb(第3回) SPDYでRails動かすまで + Rubyのthread調べた

rails

もくもく会第3回いってきました。
今回のお題は2つ

  • SPDYとRailsで何かつくる
  • RubyのThreadについてしらべる

なぜ二つになったかというとSPDY調べていくうちに
Rails(ruby) 関係ないやん( ̄Д ̄;)
となったため急遽追加。

Mac(lion) で SPDYでRails動かすまで

nginxが1.4よりSPDYが標準装備となったらしい http://nginx.org/en/CHANGES-1.4

これはもらった!

APサーバーはunicornでもよかったが、自分はネコ科の動物には目がないので nginx + puma でいくことにする。

Nginxをコンパイル。

brewにあるやつみても 1.4.0 ではなさそうなので、ソースを落としてくる。

cd work/
wget http://nginx.org/download/nginx-1.4.0.tar.gz
tar zxvf nginx-1.4.0.tar.gz

色々調べるとSSLも最新でないとダメみたい。
Mac(Lion)の標準搭載はだいぶ古いのでこれまたソースをダウンロードしてくる。
NginxのconfigureでOpenSSLのソースあるところを指定するとそのまま取り込んでくれるらしい
べんり〜

cd work/ngix-1.4.0
wget http://www.openssl.org/source/openssl-1.0.1e.tar.gz
tar zxvf openssl-1.0.1e.tar.gz

configureはちょっと特殊。
Macで運用するわけではないし、うまくいかなかったときにversion複数さくさく切り替えれると楽かなと適当なところにbinary置く事にした。
あとでbrewで入れるかもしれないし。

最後のオプションでOpenSSLのソースを展開したディレクトリを指定。

./configure \
  --prefix=$HOME/work/nginx \
  --sbin-path=$HOME/work/nginx/sbin \
  --with-http_spdy_module \
  --conf-path=$HOME/work/nginx/etc/nginx.conf \
  --error-log-path=$HOME/work/nginx/var/error.log \
  --http-log-path=$HOME/work/nginx/var/access.log \
  --http-client-body-temp-path=$HOME/work/nginx/tmp/client_body \
  --http-proxy-temp-path=$HOME/work/nginx/tmp/proxy \
  --http-fastcgi-temp-path=$HOME/work/nginx/tmp/fastcgi \
  --http-uwsgi-temp-path=$HOME/work/nginx/tmp/uwsgi \
  --http-scgi-temp-path=$HOME/work/nginx/tmp/scgi \
  --pid-path=$HOME/work/nginx/tmp/nginx.pid \
  --lock-path=$HOME/work/nginx/var/nginx \
  --with-http_ssl_module \
  --with-openssl=./openssl-1.0.1e

ここでエラー

./configure: error: the HTTP rewrite module require the PCRE library.
...

rewrite はおそらく使わないけどとりあえず入れる事にする。

sudo brew install pcre

そしてmake

make

んで、またエラー

WARNING! If you wish to build 64-bit library, then you have to 
                 invoke './Configure darwin64-x86_64-cc' *manually*
                 You have about 5 seconds to press Ctrl-C to abort. 

いろいろ調べたら OpenSSL の話だった。
んで、configure の中身読みながら以下をconfigureに追加

--with-openssl-opt=darwin64-x86_64-cc

するも撃沈。

いくら調べてもよくわからない(Shellがたいして読めない)んで直接書き換える。

diff -u auto/lib/openssl/make.org auto/lib/openssl/make
--- make.org     2013-05-06 21:21:58.000000000 +0900
+++ make     2013-05-06 21:19:42.000000000 +0900
@@ -56,7 +56,7 @@
 $OPENSSL/.openssl/include/openssl/ssl.h:     $NGX_MAKEFILE
      cd $OPENSSL \\
      && \$(MAKE) clean \\
-     && ./config --prefix=$ngx_prefix no-shared $OPENSSL_OPT \\
+     && ./Configure darwin64-x86_64-cc --prefix=$ngx_prefix no-shared $OPENSSL_OPT \\
      && \$(MAKE) \\
      && \$(MAKE) install LIBDIR=lib
make

成功ヾ(@^▽^@)ノ

証明書は以前練習で発行したのを使用。

最後にコンフィグの設定。デフォルトのやつをコピーして見よう見まねで変更

    # ここに puma と通信する UnixSocket の path を指定
    upstream app {
      server unix:///var/run/app.sock;
    }

    server {
        # listenポートを変えてssl spdy と追加
        listen       3000 default ssl spdy;
        server_name  localhost;
        
        # SSLの設定を追加
        ssl on;
        ssl_certificate     /path/to/your_cert.cert;
        ssl_certificate_key /path/to/your_secret.key;
        ssl_session_timeout 5m;

        root /path/to/app/public;

        location / {
            # root   html;
            # index  index.html index.htm;
            
            # ここでupstream指定
            proxy_pass http://app;

            # 多分Headerに元のHostを足してる?
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        #error_page  404              /404.html;
        ....

Railsを準備

まずはインストールするフォルダを作成

mkdir app
cd app

次にGemfileを作成。今回はせっかくなので一番新しいのを使った。

source "https://rubygems.org"
gem "rails", "4.0.0.rc1"

rails をインストール

bundle install --path vendor/bundle

Railsの基本ファイルを生成。

rails new .

上でGemfileが上書きされるので、上書きされたGemfileにpumaを追加

gem "puma"

puma インストール

bundle

puma確認

bundle exec rails s puma

=> Booting Puma
=> Rails 4.0.0.rc1 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
Puma 2.0.1 starting...
* Min threads: 0, max threads: 16
* Environment: development
* Listening on tcp://0.0.0.0:3000

最後にSPDY確認

こちら(spdy indicator)chromeエクテンションで確認。

nginx起動。

sbin/nginx

puma 起動

# -bでさっきnginxで指定した UnixSocket のpathを指定
bundle exec puma -b unix:///var/run/app.sock

ブラウザで確認
f:id:sutetotanuki:20130512201002p:plain

RubyのThreadについてしらべる

RubyにはGVL(Giant VM lock)なるものがあるらしくどういうものなのってことで、調べたかったのは以下の5点

  1. CPU100%以上ちゃんと使うの?
  2. switchのタイミングは?
  3. Monitor の便利な使い方
  4. Thread.currentの使いどころ
  5. Abort on Exceptionの使いどころ

んで、時間内に調べられたのは上2つ

CPU100%以上ちゃんと使うの?

公式のDocより引用

ネイティブスレッドを用いて実装されていますが、 現在の実装では Ruby VM は Giant VM lock (GVL) を有しており、同時に実行される ネイティブスレッドは常にひとつです。 ただし、IO 関連のブロックする可能性があるシステムコールを行う場合には GVL を解放します。その場合にはスレッドは同時に実行され得ます。 また拡張ライブラリから GVL を操作できるので、複数のスレッドを 同時に実行するような拡張ライブラリは作成可能です。

同時に実行されるネイティブスレッドは常にひとつです。

(・_・?)...ン?

じゃあCPU100%しか使わないの?

def fib(n)
  n < 2 ? n : fib(n-1) + fib(n - 2)
end

th1 = Thread.new {
  sleep 1
  100.times {
    fib(1000)
  }
}

th2 = Thread.new {
  sleep 1
  100.times {
    fib(1000)
  }
}

th1.join; th2.join;

f:id:sutetotanuki:20130512223835p:plain

ちゃんとつかってる!理由はわからないけど\(^o^)/

switchのタイミングは?

IO 関連のブロックする可能性があるシステムコールを行う場合には GVL を解放します。

さっぱりわからないのでぐぐってみるととてもよいスライドを見つける
http://www.slideshare.net/kosaki55tea/ruby-gvlimprovement-8617719

全ては理解できなかったけど、無理矢理理解すると

  • IOのようなwaitが発生しそうな処理のときにswitchする。たぶんsleepもだとおもう
  • 長い時間同じthreadが走らないようにTimerでswitchする。これは必ず同じthreadがもう一度Lockをとらないようにしている。

ふむふむ。ならこれでどう?

th1 = Thread.new {
  sleep 1
  30.times {
    $th1 << $global
    $global += 1
    w.write("..")
  }
}

th2 = Thread.new {
  sleep 1
  30.times {
    $th2 << $global
    $global += 1
    r.read(1)
  }
}

th1.join; th2.join

p $th1
p "------------"
p "------------"
p "------------"
p $th2

f:id:sutetotanuki:20130513004420p:plain

きれいには切り替わらないな \(^o^)/

この辺で時間終了。

レビュー中(発表中) に Unixの仕組みと似ているという大変貴重な意見をいただけたので、時間できたときにもう少しCのRubyのソース追ったりして調べてみようっと。


その後の懇談会にて、こんなぐだぐだな発表に関わらずMVPをいただけたヾ(@^▽^@)ノ

f:id:sutetotanuki:20130513005242j:plain

これが何か良くわかってないけど、プリペイドカードっぽいのでスターバックスいって支払いのときに出してみて店員の反応を見て考えようw

今回は書く事多くてつかれたw

西脇.rb & 東灘.rb(第2回)でつくってみたいもの(結果)

ruby 勉強会 

感想

いくまではいつものごとく人見知りすぎてお腹いたくなったけど、行ってみたら色々しゃべれて楽しかった。

特に同じ悩みを持つ人としゃべれてよかった。

なかなかもくもく会おすすめ。

あと、やっぱり意識高い人はセンスがいいなと思った。目的もって勉強するのとやみくもに勉強するのでは大分違うんだなと感じた。僕もガンバラナイト

今日やったことまとめ

前回書いたやつ分はおおむね実装できた感じ。

Gem https://rubygems.org/gems/mameconf
github https://rubygems.org/gems/mameconf

一応Gem化したのでご興味ある方はどうぞつかってみて下さいw

git で戻しながら今日やったことの復習

初期バージョン

mameconf.rb

require "mameconf/version"

module Mameconf
  def self.included(base)
    # include で 特異メソッド足す為にhookでextend
    # ClassMethods つくって足すのはもう古い?
    base.extend ClassMethods
  end

  module ClassMethods
    # アクセッサ足すときに呼ばれる特異メソッド
    def mameconf(name, options={})
      #  デフォルト値をローカル変数にセット。正直いらない
      default_value = options[:default]

      # memeconfを呼ばれたときにクラスコンテキストを開いてインスタンスメソッドを定義する。
      # ヒアドキュメントでRUBYって使うのはRailsから拝借
      # default_value のinspectをつかっているのは
      # デフォルト値が Symbol なら :sym 
      # 文字列なら "string" 数値なら 123 と文字列展開
      # されるため、今回の用途に都合がよいから。
      class_eval <<-RUBY, __FILE__, __LINE__ + 1
        def #{name}
          @#{name} ||= #{default_value.inspect}
        end

        def #{name}=(val)
          @#{name} = val
        end
      RUBY
    end
  end
end

mameconf_spec.rb

require "spec_helper"

describe Mameconf do
  # テスト対象になるクラス
  class Included
    include Mameconf
  end

  describe "defualt value" do
    subject do
      # 実際にincludeしてincludeしたクラスを返す
      Included.class_eval do
        mameconf :host, default: "localhost"
      end
      Included
    end

    it "returns default valeu if not present" do
      subject.new.host.should eq "localhost"
    end

    it "allows override default value" do
      instance = subject.new
      instance.host = "google.com"

      instance.host.should eq "google.com"
    end
  end
end
to_hashメソッドを追加ver
   module ClassMethods
      # mameconf されたattributeを保持しておくためのアクセサ
      # インスタンス変数にじゃなくてアクセさにしたのは
      # インスタンスから self.class.** で呼べるようにするため。
+    attr_accessor :mameconf_attr_names
+
+    def mameconf_attr_names
+      @mameconf_attr_names ||= []
+    end
+    
     def mameconf(name, options={})
+      mameconf_attr_names << name
+      
       default_value = options[:default]
       class_eval <<-RUBY, __FILE__, __LINE__ + 1
         def #{name}
@@ -16,6 +24,15 @@ module Mameconf
         def #{name}=(val)
           @#{name} = val
         end
+
+        def to_hash
+          ret = {}
+          self.class.mameconf_attr_names.each do |attr|
+            attr_sym = attr.to_sym
+            ret[attr_sym] = self.__send__(attr_sym)
+          end
+          ret
+        end
       RUBY
     end
   end
 describe Mameconf do
-  class Included
-    include Mameconf
-  end
-
   describe "defualt value" do
     subject do
        # うっ。インスタンスメソッド足すので毎回afterで消さないといけない事に気づく
        # 寿命をitに限定させるために Class.new 使う事にする
-      Included.class_eval do
+      Class.new do
+        include Mameconf
         mameconf :host, default: "localhost"
       end
-      Included
     end
 
     it "returns default valeu if not present" do
       subject.new.host.should eq "localhost"
     end
 
-    it "allows override default value" do
      # to いるんじゃない?英語得意じゃないのでわからない。。
+    it "allows to override default value" do
       instance = subject.new
       instance.host = "google.com"
 
       instance.host.should eq "google.com"
     end
   end
+
+  describe "#to_hash" do
+    subject do
+      Class.new do
+        include Mameconf
+        
+        mameconf :host, default: "localhost"
+        mameconf :port, default: 3337
+      end.new
+    end
+
+    it "returns hash that included default values" do
+      subject.to_hash.should eq ({ host: "localhost", port: 3337 })
+    end
+  end
 end
継承したケースのテスト追加

今回は最初から想定していたので、class variable 使わずに実装したのでテストのみ。

+  describe "#inheritance" do
+    before do
+      @parent = Class.new do
+        include Mameconf
+
+        mameconf :host, default: "localhost"
+      end
+
+      @sub = Class.new(@parent)
+    end
+
+    it "has mameconf attribute" do
+      @sub.new.host.should eq "localhost"
+    end
+
+    context "add new attribute on Sub Class" do
+      before do
+        @sub.class_eval do
+          mameconf :sub, default: "inu"
+        end
+      end
+      
+      it "has different memory space" do
+        @sub.new.sub.should eq "inu"
+
          # わざわざ 例外のテストしなくても respond_to? テストするだけでよさそう。
          # まっちゃも用意されてそう
+        expect {
+          @parent.new.sub
+        }.to raise_error NoMethodError
+      end
+    end
+  end
nilをセット出来るようにする。

ここまで書いててセッターの作りが甘くて nil がセット出来ない事に気づく。

# nil だと毎回デフォルト値セットしてしまう。。。
"@#{name} ||= #{default_value.inspect}"

セッター呼ばれる時点でフラグ持たしてもいいけど、できたら
@abc = "aa" とかされたときにも対応したい。
色々考えて以下みたいにした

       class_eval <<-RUBY, __FILE__, __LINE__ + 1
         def #{name}
-          @#{name} ||= #{default_value.inspect}
            # 一度でもインスタンス変数として設定されていれば初期化しない
+          if !instance_variables.include?(:@#{name})
+            @#{name} ||= #{default_value.inspect}
+          end
+            
+          @#{name}
         end

うまく動くかはもう少し使わないとわからない。

initializerを足す
 module Mameconf
   def self.included(base)
     base.extend ClassMethods
+    base.__send__(:include, InstanceMethods)
+  end
+
+  module InstanceMethods
+    def initialize_mameconf(options={})
+      options.each do |key, value|
+        method = "#{key}="
+
+        __send__(method, value) if respond_to?(method)
+      end
+    end
+    
+    def initialize(options={})
+      initialize_mameconf(options)
+    end
   end
+
+  describe "#initialize" do
+    subject do
+      Class.new do
+        include Mameconf
+
+        mameconf :host, default: "localhost"
+      end
+    end
+
+    it "can initiate from constructor" do
+      subject.new(host: "sibainu.com").host.should eq "sibainu.com"
+    end
+
+    context "override initializer on Sub Class" do
+      before do 
+        subject.class_eval do
+          def initialize(options)
+            # TODO: 本当はココになにも書かず初期化したい。
+            #       でも多分無理。
+            initialize_mameconf(options)
+          end
+        end
+      end
+
+      it "can initiate from constructor" do
+        subject.new(host: "sibainu.com").host.should eq "sibainu.com"
+      end
+    end
+  end
 end

足したけど、やっぱりここでSub Class制限なしを実現できなかった。。。多分無理そうかな。。。

Review で tap 教えてもらいその場でリファクタ
         def to_hash
-          ret = {}
-          self.class.mameconf_attr_names.each do |attr|
-            attr_sym = attr.to_sym
-            ret[attr_sym] = self.__send__(attr_sym)
-          end
-          ret
+          {}.tap {|ret|
+            self.class.mameconf_attr_names.each do |attr|
+              attr_sym = attr.to_sym
+              ret[attr_sym] = self.__send__(attr_sym)
+            end
+          }
         end

おぉ。ret とか宣言するのださいなと思いつつ書いてたのでいいこと教えてもらった(`・ω・´)

総評

すごい人のプレゼン聞くのも勉強になるけど、やっぱアウトプット楽しいし勉強になる。
もくもく会おすすめ(しつこいw)

西脇.rb & 東灘.rb(第2回)でつくってみたいもの

ruby

西脇.rb & 東灘.rb(第2回)でつくってみたいもの

include すると default 値などが設定できるattr_accessorの拡張見たいな機能が追加される Mixin

仕様

基本は下のような感じ

class Some
  include Configable

  define_config :host, default: "localhost"
  define_config :port, default: 3000
end

Some.new.host #=> "localhost"

そんで、できれば以下の様にコンストラクタで初期化できるようにしたい。
できるなら、サブクラスのコンストラクタに制限なしで。

Some.new(host: "google.com").host #=> "google.com"

そんで、できれば to_hash メソッドが欲しい

Some.new.to_hash #=> { host: "localhost", port: 3000 }

そんで、できれば継承できるようにしたい。

class Sub < Some
end

Sub.new.host #=> "localhost"

Sinatra で twitter bootstrap v2.3.0

ruby sinatra

Sinatra で bootstrap 使おうとしてはまったのでめも。

たぶん今しか使えない情報。

へたれなので現時点の最新のdocumentが使える 2.3 をゲット

https://github.com/twitter/bootstrapをcloneして、

git checkout -b v2.3.0 

で 2.3に

それぞれの js, less, img フォルダをsinatraでpublic_folderに設定したところの下にコピーしてくる。

自分は下のようにした

./public/js/bootstrap
./public/css/bootstrap
./public/img/bootstrap

overrideするlessを置く

自分は./public/css/override.lessに置いた

// import bootstrap
@import "./bootstrap/bootstrap.less";

// change path to image.
@iconSpritePath: "../img/bootstrap/glyphicons-halflings.png";
@iconWhiteSpritePath: "../img/bootstrap/glyphicons-halflings-white.png";

まず普通にGemfileに追加

source :rubygems

gem "sinatra"
gem "less"

それっぽくかいてみる

某掲示板のanswerをそのまま書いてみる。

require 'less'
require 'sinatra/base'

class App < Sinatra::Base
  # Make LESS @import statements work
  Less.paths << settings.views

  # Use LESS for CSS
  get '/stylesheets/:style.css' do
    less(params[:style].to_sym)
  end
end

しかしエラー

pathが見つからない云々いわれる。

いろいろ試しながらソースよんでいくと、cssディレクトリにoverride.less置くには直接viewsオプションを指定しないといけないっぽ。

get "/css/:style.css" do
  less(params[:style].to_sym, views: "#{dirname}/web/public/css")
end

いけた、、とおもってアクセスすると

Less::Error - Cannot call method 'charAt' of undefined

。。。わからん( ̄Д ̄;)

よくよく調べてみると、bootstrap 2.3 で使われている less のversionにless.rbが対応してないっぽい

対応されているこのコミットのverをありがたく使わしていただくことにする。

gem "less", git: "https://github.com/populr/less.rb", branch: "v2.2.2-less1.3.3", submodules: true

submodules: true はなぜか(gitリポジトリを直接指定しているから?)だと submoduleのjs(JSのless)をdlしてこなかったから。

これでやっとみれた!ヾ(@^▽^@)ノ