yucatio@システムエンジニア

趣味で作ったものいろいろ

Rubyでの動的なクラスメソッドの追加方法

メソッドの実行でクラスメソッドが追加される

以下はrailsのscope定義文です。このように書くと、Book.costlyでレコードが取得できます。 メソッド呼び出しでメソッドが動的に追加されます。 メソッドの引数が新しいメソッドのメソッド名になるのです。不思議!Javaではこのようなことはできません。

class Book < ActiveRecord::Base
  scope :costly, -> { where("price > ?", 3000) }
end

どのような方法でメソッドを追加できるのでしょうか。

singleton_class とdefine_methodでクラスメソッドを追加する

Rubyでは動的なメソッド追加は割と普通に行われるようで、define_methodで行うことができます。 クラスメソッドの追加は特異クラスへのメソッド追加として行うことができます。

class Sample
  def self.my_define_method(name)
    singleton_class.send(:define_method, name, -> { puts "This method name is '#{name}'" })
  end

  # クラス内でクラスメソッドを追加
  my_define_method :sample_method
end

# クラス外からクラスメソッドを追加
Sample.my_define_method :sample_method2

# 追加したクラスメソッドを実行
Sample.sample_method
Sample.sample_method2

これを実行すると、以下のように表示され、メソッドの追加に成功したことが分かります

$ ruby sample_01.rb 
This method name is 'sample_method'
This method name is 'sample_method2'

moduleをextendする方法

上記の方法で動的にクラスメソッドを追加することができました。 Ruby on Railsのscopeのコード*1を見ると、Moduleのコードを呼んでいるので、同じような実装を行ってみます。

module ParentModule
  def my_define_method(name)
    singleton_class.send(:define_method, name, -> { puts "This method name is '#{name}'" })
  end
end

class Sample
  extend ParentModule
  
  my_define_method :sample_method
end

Sample.sample_method

実行します。

$ ruby sample_02.rb 
This method name is 'sample_method'

メソッドの追加ができました。Railsのコードに近い形になりました。

クラス定義内でモジュールをextendすることで、モジュールのメソッドを特異メソッド(クラスメソッド)として取り込むことができます。

うまく行かなかった方法たち

define_methodをsingleton_classをレシーバとして呼び出した場合

priveteなメソッドと呼ぶなと怒られます。

class Sample
  def self.my_define_method(name)
    # エラー。priveteメソッドは呼び出せない
    singleton_class.define_method name, -> { puts "This metho name is #{name}" }
    # 正しくは下記
    # singleton_class.send(:define_method, name, -> { puts "This method name is '#{name}'" })
  end
end

# クラスメソッドを追加
Sample.my_define_method :sample_method

# 追加したクラスメソッドを実行
Sample.sample_method
$ ruby sample_01_bad.rb 
sample_01.rb:3:in `my_define_method': private method `define_method' called for #<Class:Sample> (NoMethodError)

singleton_class(オブジェクトの特異クラス)のdefine_methodはプライベートメソッドなので外部から呼び出せません。

ただし、sendメソッドを使用することでprivateメソッドを(強制的に)呼び出すことができます。

define_methodをインスタンスメソッドから実行した場合

インスタンスメソッドとして定義した場合は、そのインスタンスの特異メソッドとして定義され、クラスメソッドにはなりません。

class Sample
  def my_define_method(name)
    singleton_class.send(:define_method, name, -> { puts "This method name is '#{name}'" })
  end
end

instance = Sample.new
# インスタンスのメソッドを追加 
instance.my_define_method :sample_method

# インスタンスメソッドとして実行
instance.sample_method
# エラー。クラスメソッドとしては定義されていない
Sample.sample_method
$ ruby sample_01.rb 
This method name is 'sample_method'
sample_01.rb:14:in `<main>': undefined method `sample_method' for Sample:Class (NoMethodError)

インスタンスの特異クラス(singleton_class)にメソッドを追加したため、インスタンスにメソッドが追加されました。

後記

define_methodの実装も気になりましたが、今回はこの辺で。

環境

Ruby初心者 AcriveRecordのscope定義式の意味を理解する

Ruby on Railsの環境構築が終わったので、"パーフェクト Ruby on Rails"に載っていサンプルアプリを作っていきます。

プロジェクトの作成とモデルの作成

rails new プロジェクト名

で新しいプロジェクトを作成します。本のサンプルに従って、プロジェクト名は book_admin としました。

$ rails new book_admin

モデルの生成には rails g model コマンドを使用します。カラム名とデータ型の対は複数指定できます

rails g model モデル名 カラム名 : データ型 カラム名 : データ型 ...

$ ./bin/rails g model Book name:string published_on:date price:integer number_ofpage:integer

scope定義の文

scopeを定義することでwhere文に名前をつけて管理することができます。 値段(price)が3000以上のデータを抽出するスコープを定義します。

class Book < ActiveRecord::Base
  scope :costly, -> { where("price > ?", 3000) }
end

Java エンジニア、悩む

本に従ってこのコードを書いてみたのですが、文の意味がわかりません。どのような要素で成り立っているのか、costlyの最初のコロン(:)はシンボルを表すらしいけれどシンボルとは何なのか、カンマは何なのか矢印(->)は何を意味するのか。

ひとつひとつ解読していく

クラス宣言

まずはクラス宣言の部分をみていきます

class Book < ActiveRecord::Base
  # 略
end

Bookクラスを宣言しています。 "< ActiveRecord::Base" はActiveRecord::Baseクラスの拡張していることを意味します。 ActiveRecord::BaseはActiveRecordモジュールのBaseクラスを表します。

スコープ定義文
scope :costly, -> {where("price > ?", 3000) }

はじめ、この文を見たときに、矢印(->)で文が区切られているのかと思って悩みました("scope :costly," と "{where(..)}" で分かれるのかと思った)

実際はscopeメソッドとその引数なので、対応関係は以下のようになります

役割
メソッド scope
第1引数 :costly
第2引数 -> { where("price > ?", 3000) }

第1引数はシンボルです。シンボルについてまだよく理解できてはいませんが、以下の記事が役に立ちました。シンボルは"名前に対応した整数"だそうです。なので、引数に渡したシンボルで新たなメソッドを定義できるというわけです。(上記の例だとBook.costlyで第2引数に渡したブロックが実行される)

Rubyのシンボルは文字列の皮を被った整数だ!

第2引数は手続きオブジェクトです。矢印とカッコ(-> {...})はlambda記法です。lambda { where("price > ?", 3000) } と書いても同じです。

whereメソッドSQLインジェクション

where()はSQLのwhere条件を追加するためのメソッドです。"price > ?" の クエスチョンマークはプレースホルダーと呼ばれ、第2引数以降の値が割り当てられます。where("price > ?", 3000) と where("price > 3000") はほぼ同じ内容を表します(完全に同じではない)。

where("price > 3000") と書かずに、プレースホルダーを使ってwhere("price > ?", 3000) と書くのは、SQLインジェクションという攻撃を無効にするためです。今回、第2引数は数値でしたが、通常はユーザの任意の入力をSQL文として組み立てることになります。ユーザの入力をSQL文として組み立てるとき、開発者の意図しないSQL文が組み立てられることがあります(ユーザ入力にSQLのキーワードが含まれる場合)。プレースホルダーを使うことで、この脆弱性をなくすことができます。

SQLインジェクションに対する脆弱性を作らないようにするため、検索条件には必ずプレースホルダーを使わなければなりません。

パーフェクトRuby on Rails

パーフェクトRuby on Rails
著者:すがわらまさのり
価格:3,110円(税込、送料込)
楽天ブックスで詳細を見る

環境

Railsのインストール

Rubyのバージョンアップ、bundlerのインストールも終わったのでRailsのインストールをします。

インストールするRailsのバージョン決定

参考にしている" パーフェクトRuby on Rails" では、Rubyのバージョン2.1.2とRailsバージョン4.1.1を利用していますがなるべく新しいバージョン使っておいた方が後が楽な気がするので参考書とは別バージョンで開発を進めます。

Ruby gems のページを見ると、最新の安定板が4.2.6、rc版が5.0.0.rc1となっています(2016年5月27日現在)。 バージョン5も気になるところですが、今回は4.2.6をインストールします。

All versions of rails | RubyGems.org | your community gem host

Railsのインストール

gemコマンドでインストールします。関連するライブラリが多いので13分かかりました。

$ gem install rails -v 4.2.6
Fetching: rack-1.6.4.gem (100%)
Successfully installed rack-1.6.4
Fetching: concurrent-ruby-1.0.2.gem (100%)
Successfully installed concurrent-ruby-1.0.2
# 中略
Installing ri documentation for mail-2.6.4
Parsing documentation for actionmailer-4.2.6
Installing ri documentation for actionmailer-4.2.6
Parsing documentation for rails-4.2.6
Installing ri documentation for rails-4.2.6
Done installing documentation for rack, concurrent-ruby, sprockets, thread_safe, tzinfo, i18n, activesupport, mini_portile2, nokogiri, loofah, rails-html-sanitizer, rails-deprecated_sanitizer, rails-dom-testing, rack-test, erubis, builder, actionview, actionpack, sprockets-rails, thor, railties, arel, activemodel, activerecord, globalid, activejob, mime-types-data, mime-types, mail, actionmailer, rails after 778 seconds
31 gems installed
パーフェクトRuby on Rails

パーフェクトRuby on Rails
著者:すがわらまさのり
価格:3,110円(税込、送料込)
楽天ブックスで詳細を見る

環境

  • OS : Mac OS X バージョン 10.8.4
  • Ruby バージョン 2.3.1

関連記事

yucatio.hatenablog.com

yucatio.hatenablog.com

全出力

gem install rails -v 4.2.6 したときの全出力を記録のために残しておきます

続きを読む

bundlerのインストールと初期設定

Rubyを無事インストールできたので、次はbundlerをインストールします

yucatio.hatenablog.com

パッケージ管理とは

パッケージ管理とは、使用するライブラリ(ある機能のソースファイルの集まり)をインストール、更新するための機能です。 Rubyではgemコマンドでライブラリをインストール、更新できます。

さらに、あるライブラリがさらに他のライブラリを必要としていた場合、自動で必要なライブラリをインストールしてくれます。 この機能を使わないと、以下のようになります (実体験)

  1. 便利なライブラリAを見つけた!ダウンロードして使おう
  2. クラスZが見つからないエラー発生。ライブラリAで使われているようだ
  3. クラスZはライブラリBに入っているそうなので、ライブラリBをダウンロード
  4. プログラム再実行
  5. クラスYが見つからないエラー発生。ライブラリBで使われているようだ
  6. クラスYはライブラリCに入っているそうなので、ライブラリCをダウンロード
  7. プログラム再実行
  8. 以下繰り返し

gemとbundler

gemでコンピュータにライブラリをインストールできます。 インストールされたライブラリはコンピュータ内に存在するすべてのプログラムから利用することができます。 gemはPHPPEARにあたります。

しかし、1つ問題があります。1つのコンピュータで使用できるライブラリのバージョンが1つに限られてしまいます。 例えば、アプリAではライブラリXのバージョン1.3.4を使用しているのにアプリBではバージョン2.8.1が必要という場合にどちらかのバージョンしか使えないという問題に出くわします。 新しい方のバージョンに合わせられればよいのですが、バージョンをあげることで挙動が変わっている場合もあるので、そう簡単にあげられない場合もあります。すでにサービス開始しているアプリの場合は特に。

そこで、アプリ(プロジェクト、プロダクトと呼んだりもする)ごとにどのライブラリのどのバージョンを指定できるかを管理する必要が出てきました。これがbundlerです。 bundlerはphpのComposer, mavendependency管理に相当します。

bundlerのインストールと初期設定

gemコマンドでbundlerをインストールする

下記コマンドを入力します。"1 gem installed" という文字が出てくれば成功です。

$ gem install bundler
Fetching: bundler-1.12.5.gem (100%)
Successfully installed bundler-1.12.5
Parsing documentation for bundler-1.12.5
Installing ri documentation for bundler-1.12.5
Done installing documentation for bundler after 9 seconds
1 gem installed

# コマンドの確認
$ which bundler
/Users/user/.rbenv/shims/bundler

# gemの情報管理場所
$ ls -la ~/.gem
total 0
drwxr-xr-x  3 xxxx  xxxxx  102 May 26 08:42 specs

bundlerの初期設定

# rubyプロジェクトフォルダを作成して移動
$ mkdir -p ~/Ruby/sample01
$ cd       ~/Ruby/sample01

# bundler初期設定
$ bundle init
Writing new Gemfile to /Users/user/Ruby/sample01/Gemfile

ファイルが作成されたので中身を確認してみます

$ cat ~/Ruby/sample01/Gemfile
# frozen_string_literal: true
# A sample Gemfile
source "https://rubygems.org"

# gem "rails"

bundlerでrdefsのインストール

"パーフェクトRuby on Rails"のサンプル通り、試しにrdefsをインストールします

$ cd           ~/Ruby/sample01

# Gemfileにgem rdefsを追記
$ echo 'gem "rdefs"' >> Gemfile
$ cat ~/Ruby/sample01/Gemfile
# frozen_string_literal: true
# A sample Gemfile
source "https://rubygems.org"

# gem "rails"
gem "rdefs"

# Gemfileに書かれているパッケージをインストール
$ bundle install
Fetching gem metadata from https://rubygems.org/
Fetching version metadata from https://rubygems.org/
Resolving dependencies...
Installing rdefs 0.0.2
Using bundler 1.12.5
Bundle complete! 1 Gemfile dependency, 2 gems now installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.

# Gemfile.lockの確認
$ cat Gemfile.lock
GEM
  remote: https://rubygems.org/
  specs:
    rdefs (0.0.2)

PLATFORMS
  ruby

DEPENDENCIES
  rdefs

BUNDLED WITH
   1.12.5

# bundlerの管理情報
$ ls -l ~/.bundle
drwxr-xr-x  3 xxxx  xxxxx  102 May 26 22:35 cache

bundlerでrdefsの実行

まずは適当なrubyスクリプトを作成します

$ cd ~/Ruby/sample01
$ vi  sample.rb
$ cat sample.rb
class Sample
  def hello
    puts 'hello'
  end
end

rdefsのターゲットに作成したスクリプトを指定します

$ bundle exec rdefs sample.rb
class Sample
  def hello

環境

  • OS : Mac OS X バージョン 10.8.4
  • Ruby バージョン 2.3.1

参考文献

パーフェクトRuby on Rails

パーフェクトRuby on Rails
著者:すがわらまさのり
価格:3,110円(税込、送料込)
楽天ブックスで詳細を見る

make実行時にerror: invalid instruction mnemonic 'adoxq' が発生した原因

開発環境

症状

make実行時にerror: invalid instruction mnemonic 'adcxq' が出る。

下記ログは、rubyのバージョンアップ時にopensslをインストールしており、そのなかでmakeが実行されているもの。

$ rbenv install 2.3.1
Downloading openssl-1.0.2h.tar.gz...
-> https://dqw8nmjcqpjn7.cloudfront.net/1d4007e53aad94a5b2002fe045ee7bb0b3d98f1a47f8b2bc851dcd1c74332919
Installing openssl-1.0.2h...

BUILD FAILED (OS X 10.8.4 using ruby-build 20160426-10-g2bd59bd)

Inspect or clean up the working tree at /var/folders/hz/b4j1y31166xchfpzkhmg1_jr0000gn/T/ruby-build.20160508225226.46928
Results logged to /var/folders/hz/b4j1y31166xchfpzkhmg1_jr0000gn/T/ruby-build.20160508225226.46928.log

Last 10 log lines:
 ^~~~~
x86_64-mont.s:1038:2: error: invalid instruction mnemonic 'adcxq'
 adcxq %rax,%r13
 ^~~~~
x86_64-mont.s:1039:2: error: invalid instruction mnemonic 'adoxq'
 adoxq %rbp,%r15
 ^~~~~
make[2]: *** [x86_64-mont.o] Error 1
make[1]: *** [subdirs] Error 1
make: *** [build_crypto] Error 1

原因

Failure to build openssl on OS X 10.9.5 · Issue #44282 · Homebrew/legacy-homebrew · GitHub

ここで書いてある通り、Xcodeコマンドラインツールのバージョンの不一致が原因のようです

間違った手順

Xcodeをインストール

https://developer.apple.com/downloads/?=xcode からインストールできます。

4.6.3をインストールしました

f:id:yucatio:20160518101145p:plain

コマンドラインツールをインストール

Xcode > Preference > Downloads > Components にある Command Line Tools を選択してインストールします

f:id:yucatio:20160518101252p:plain

'The package "DeveloperToolsCLI.pkg" is untrusted." というエラー発生が発生

'The package "DeveloperToolsCLI.pkg" is untrusted." というエラー発生が発生して、コマンドラインツールのインストールができませんでした

f:id:yucatio:20160518101612p:plain

セキュリティレベルを変更

mountain lion - Installing Xcode command line tools error: The package “DeveloperToolsCLI.pkg” is untrusted - Ask Different

こちらを参考に、Macのセキュリティレベルを変更しましたが、今回は効果がありませんでした。

コマンドラインツールをインストール

Xcodeからのインストールはあきらめて、Appleダウンロードサイト https://developer.apple.com/downloads/?=command%20line%20tools からインストールします。

Command Line Tools (OS X Mountain Lion) for Xcode - September 2013 を選択しました。

f:id:yucatio:20160518102242p:plain

参考 : Command Line Tools fail to install OS X Lion 10.7.3 Xcode 4.3 - Ask Different

エラー発生
$ rbenv install 2.3.1
Downloading openssl-1.0.2h.tar.gz...
-> https://dqw8nmjcqpjn7.cloudfront.net/1d4007e53aad94a5b2002fe045ee7bb0b3d98f1a47f8b2bc851dcd1c74332919
Installing openssl-1.0.2h...

BUILD FAILED (OS X 10.8.4 using ruby-build 20160426-10-g2bd59bd)

Inspect or clean up the working tree at /var/folders/hz/b4j1y31166xchfpzkhmg1_jr0000gn/T/ruby-build.20160508225226.46928
Results logged to /var/folders/hz/b4j1y31166xchfpzkhmg1_jr0000gn/T/ruby-build.20160508225226.46928.log

Last 10 log lines:
 ^~~~~
x86_64-mont.s:1038:2: error: invalid instruction mnemonic 'adcxq'
 adcxq %rax,%r13
 ^~~~~
x86_64-mont.s:1039:2: error: invalid instruction mnemonic 'adoxq'
 adoxq %rbp,%r15
 ^~~~~
make[2]: *** [x86_64-mont.o] Error 1
make[1]: *** [subdirs] Error 1
make: *** [build_crypto] Error 1

対応

Xcode 4.6.3 に対応するコマンドラインツールが見つからなかったので、別のバージョン(5.1.1)をインストールしました。こちらのバージョンではXcodeのダウンロード画面からインストールすることができました。

yucatio.hatenablog.com

過去バージョンのXcodeの入手方法

前回の続き。 makeコマンドが無かったのでXcodeと付属のコマンドラインツールをインストールしようとしたときの話。

yucatio.hatenablog.com

開発環境

症状

最新のXcodeがダウンロードできない

最新のXcodeはAppStoreからダウンロードできますが、Xcodeを選択してインストール開始したところ、バージョン非対応エラー。そうですよね、こんな古いMacに対応してませんよね。

f:id:yucatio:20160516081911p:plain

対応

さて、ここで選択肢は2つ。

  1. OSをバージョンアップする
  2. 過去バージョンのXcodeを探してインストールする

OSバージョンアップをするべきだとは思うのですが、今まで動いていたアプリが動かなくなるという懸念があるので、今回は見送りました。

過去バージョンのXcodeをダウンロードする

こちらのリンクから探すことが可能です。Xcodeで検索します。詳細を表示して対応バージョンを確認します。

https://developer.apple.com/downloads/?=xcode

バージョン 5.1.1がOS X バージョン 10.8.4(Mountain Lion)に対応しているのでこちらをダウンロードします

f:id:yucatio:20160516083308p:plain

インストールできました

f:id:yucatio:20160516084806p:plain

Macでmake:command not foundとなったときの対処法

開発環境

症状

rubyのインストール中、 makeコマンドが見つからないというエラーが出た

$ rbenv install 2.3.1
Downloading openssl-1.0.2h.tar.gz...
-> https://dqw8nmjcqpjn7.cloudfront.net/1d4007e53aad94a5b2002fe045ee7bb0b3d98f1a47f8b2bc851dcd1c74332919
Installing openssl-1.0.2h...

BUILD FAILED (OS X 10.8.4 using ruby-build 20160426)

Inspect or clean up the working tree at /var/folders/hz/b4j1y31166xchfpzkhmg1_jr0000gn/T/ruby-build.20160508134848.45570
Results logged to /var/folders/hz/b4j1y31166xchfpzkhmg1_jr0000gn/T/ruby-build.20160508134848.45570.log

Last 10 log lines:
PROCESSOR    =
RANLIB       =true
ARFLAGS      =
PERL         =/usr/bin/perl
SIXTY_FOUR_BIT_LONG mode
DES_UNROLL used
DES_INT used
RC4_CHUNK is unsigned long
sh: make: command not found
/Users/user/.rbenv/plugins/ruby-build/bin/ruby-build: line 564: make: command not found

whichで確認すると、何も表示されません

$ which make

対応と解決策

パスの確認

まずはパスの確認から。rbenvのインストールでパスを書き換えた際に、コマンドをミスしていないかどうかを確認します。

$ echo $PATH
/Users/user/.rbenv/shims:/Users/user/.rbenv/bin:/Users/user/.rbenv/shims:/Users/user/git/rbenv/bin:/Users/user/.rbenv/shims:/Users/user/git/rbenv/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/opt/X11/bin

/usr/bin, /binなどが含まれているので、パスは問題なさそうですね。 makeは通常 /usr/bin/make にインストールされているので、一応確認します

$ ls /usr/bin/make
ls: /usr/bin/make: No such file or directory

やっぱり無い

makeコマンドのインストール

Googleで調べたところ、Xcodeコマンドラインツールをインストールすればmakeコマンドもインストールされるようです。

Xcodeからのコマンドラインインストール

Xcode バージョン 5.1.1の場合、 Xcode > Preference > Downloads > Components にある Command Line Tools を選択してインストールします

f:id:yucatio:20160516000020p:plain

インストール後、makeコマンドのありかを確認してみると、/usr/bin/makeが表示されました。無事makeコマンドがインストールされました。

$ which make
/usr/bin/make

こちらの記事が大変参考になりました。

Mac OS X Lion で、makeコマンドないやんけ!という時の対処方法 - @kaoritter BLOG


コマンドラインコマンドラインツールをインストールする方法

試していませんが、以下のコマンドでもコマンドラインツールをインストールできるようです。

$ xcode-select --install

また、ruby-buildの公式のwikiでは、コマンドラインツールおよびhomebrew The missing package manager for macOS (or Linux) — Homebrew のインストールを実施後、下記コマンドを実施することを推奨しています。

brew install openssl libyaml libffi

github.com

追記

調べてみると、OS X のバージョンによってコマンドラインツールのインストール方法が違うようで、最近のOSの場合は、コマンドを実行してインストールする必要がありそうです。

関連記事

yucatio.hatenablog.com

yucatio.hatenablog.com