クラスメソッドについて

クラスメソッドの定義を見ていきます。

class Hoge
  def self.hello
    puts 'hoge'
  end
end

Hoge.hello
=>hoge

これは標準的な書き方だと思います。

この時のselfはクラス自身(ここではHoge)を見ています。

class Hoge
  def Hoge.hello
    puts 'hoge'
  end
end

Hoge.hello
=>hoge

Hogeに対しての特異メソッドになります。

これが成り立つのならば下も成り立ちます。

class Hoge
end

def Hoge.hello
  puts 'hello'
end

Hoge.hello

クラスメソッドは、クラスオブジェクトに対しての特異メソッドということです。

classを動的に作成する

こんなやり方をすると可読性がなくて、よくないと思うのですが、紹介します。

klass = nil

FooClass = Class.new do |f|←ブロック内で定義する
  klass = f
  f == self

  def hello
    puts 'hello'
  end
end←ここで定義終了
=> FooClass
x = klass.new
=> #<FooClass:0x007ff5330a89c8>
x.hello
=> :hello

以上。

コールバックの処理が増えてきたら、メソッド化する

コールバックの処理が増えてきて、可読性が悪くなる場合はまとまりをメソッド化にしましょう。

例えば、下記のようなものがあったとします。

before_save : hoge, :foo, :bar, .... if: :test

これがどんどん膨らんでいくことは、想像できます。

なので、hoge..barの処理をメソッドにします。

before_save :before_save_action, if: :test

def before_save_action
  hoge
  foo
  bar
end

増えてきてもbefore_save_actionに加えていけば大丈夫です。

以上です。

moduleについて

moduleについてなんとなーくでやっていたので、まとめます。

classとの違い

  • インスタンスを生成することはできない
  • 継承することはできない

用途

  • 名前空間を作る
  • モジュールのメソッドを、あるクラスのインスタンスメソッドとして取り込む
  • モジュールのメソッドを、あるオブジェクトの特異メソッド(クラスメソッド)として取り込む
  • モジュール関数を定義して使う

特異メソッドとして使用

インスタンス化はできないですが、特異メソッドとして使用することはできます。 self.をつけて定義したメソッドはモジュールの特異メソッドです。

module Greet
  def self.hello(name)
    puts "#{name}さん、こんにちは#{self.class}"
  end
end

Greet.hello('foo')
=>fooさん、こんにちはModule

メソッドをクラスのインスタンスメソッドとして取り込む

module Greet
  def self.hello(name)
    puts "#{name}さん、こんにちは#{self.class}"
    puts '特異メソッド'
  end

  def hello(name)
    puts "#{name}さん、こんにちは#{self.class}"
    puts 'メソッド'
  end
end

class Japanese
  include Greet
end
foo = Japanese.new
foo.hello('foo')
=>fooさん、こんにちはJapanese
=>メソッド

ここで特異メソッドは使用できるのか?という疑問が浮かびました。

実験してみましょう。

Japanese.hello('japan')
=>undefined method `hello' for Japanese:Class (NoMethodError)

どうやら特異メソッドはincludeするだけではダメなようです。

特異メソッドを使用できるようにする

moduleの特異メソッドを使用するには、extendを使用します。

class Japanese
  include Greet
  extend Greet
end
Japanese.hello('japan')
=>japanさん、こんにちはClass
=>メソッド

selfの方が呼ばれないのは、selfはあくまでも、moduleに関しての特異メソッドなんですね。

extendはクラスメソッド(特異メソッド)として使用できるようにしてくれます。

以上です。

参考:Amazon CAPTCHA

特異メソッド

特異メソッドは、特定のオブジェクト固有のメソッドを作成することができます。

class Japanese
  def hello(name)
    puts "#{name} こんにちは"
  end
end

foo = Japanese.new
foo.hello("foo")
=>foo こんにちは
bar = Japanese.new
bar.hello("bar")
=> bar こんにちは

def foo.hello(name)
  puts "#{name}は特異メソッドを使用した"
end

foo.hello("foo")
=>fooは特異メソッドを使用した←foo固有のメソッドになっています。
bar.hello("bar")
=> bar こんにちは

fooのみ、メソッドが変化しています。

ちなみに特異メソッドはオーバーライドもできます。

def foo.hello(name)
  super
  puts "#{name}は特異メソッドを使用した"
end

foo.hello("foo")
=>foo こんにちは
=>fooは特異メソッドを使用した

特異メソッドを辿っていくと、rubyの仕組みを深めれるようですが、そこまでは分かってないので、特異メソッドがあるということだけの報告です。

加工したデータのf.selectの作成方法

f.collection_selectを使いたいけど、なんか違う・・・

少し加工したデータをセレクトで表示したいという時の方法です。

状況

選手名(Playerモデル)のデータを表示しようとしています。

  • first_name, last_nameとカラムが分かれている
  • full_nameというメソッド(first_name, last_nameをつなげる)があるので、姓名をまとめて表示したい

こういう状況が発生しました。

やり方

controller

before_action :set_players

def set_players
  @players = Player.all.map { |x| %W(#{x.full_name} #{x.id}) }←ここで必要な情報を作成する
end

view

= f.select :player_id, @players

最終的な結果を得るための方法(雑談)

最近は行数を減らすのにはまっています。

どういう風に他の人が考えているのかわからないですが、自分のやり方です。

まずは、動いている結果を見ます。

=> [["ダルビッシュ有", "1"], ["田中将大", "2"]]

上記のようになっていたので、上記のように加工したかったのです。

動く形を作成。

Player.all.map do |player|
  x = []
  x << player.full_name
  x << player.id
  x
end

無駄に行数が多いので、なんとか削除したい・・・

x <<の部分は配列で処理できるんじゃないのか?

Player.all.map do |player|
  x = []
  x = %W(#{x.full_name} #{x.id})
end

%Wが配列を作成してくれるので、初期値いらなくない?

なら、{}で一行で書けるな。

Player.all.map { |x| %W(#{x.full_name} #{x.id}) }

という風に作成しました。

他の人はどうやって処理を簡潔に書こうとしているのだろうか?

最近はとりあえず動かすってのを意識して、後から処理を変更するようにしています。

ブロックについて

今までブロックについて意識することができていませんでした。

do..endもしくは{}の範囲内って認識でした。

備忘録的として書いておきます。

ブロックとは

上で説明しているようにdo..endもしくは{}の範囲内がブロックです。

languages = %w(Html PHP Ruby)
languages.each do(ここから) |language|
  puts language
end(ここまで)

仮引数としてブロックを受け取る

ブロックに引数を渡して処理をさせることができます。 &をつけることで、ブロックの引数として認識されます。 block.callで戻り値を返します

def section_block(&block)
  puts 'foo'
  puts block.call
end

section_block do
  bar
end
=>foo
=>bar

あくまでもブロックのみ認識するということならば、当然下記でも反応します。

section_block {"bar"}
=>foo
=>bar

{}もブロックなので、成り立ちます

ブロックの戻り値

これは最後に評価された式の値を返します。

section_block do
  'bar'
  'hoge'
end

=>foo
=>hoge

barの中身は評価されてないですね。

wat-aroさんからの指摘を受けて、間違えていたので修正します。

barの中身は評価はされています。

例えば、下記の場合は標準出力に反映されます。

def section_block(&block)
  puts 'foo'
  puts block.call
end

section_block do
  puts 'bar'
  'hoge'
end
=>foo
=>bar
=>hoge

オブジェクトをブロックとして渡す

&をつけることで、Procオブジェクトをブロックとして渡せます。

bar = Proc.new {'bar'}
section_block &bar
=>foo
=>bar

Procオブジェクトはブロックをオブジェクト化したものです。

参考:[Ruby] ブロックやProcの使いどころを理解する - Qiita

メソッドのオーバーライド

親クラスから小クラスへと継承した際に、元のメソッドをオーバーライドすると、くさいコードが消えました。

そこで、オーバーライドについて調べました。

例えば、下記のようなクラスがあったとします。

class Japanese
  def hello
    puts 'こんにちは'
  end
end

class American < Japanese
end

japanese = Japanese.new
japanese.hello
=>こんにちは

american = American.new
american.hello
=>こんにちは

こういうことになります。

これだと、アメリカ人が日本語をしゃべるので、英語で挨拶をしたいのに!ってなります(そういう設定)

なので、Japaneseのメソッドをオーバーライド(上書き)します。

class Japanese
  def hello
    puts 'こんにちは'
  end
end

class American < Japanese
  def hello
    puts 'hello'
  end
end

japanese = Japanese.new
japanese.hello
=>こんにちは

american = American.new
american.hello
=>hello

いやいや、継承しているので、バイリンガルでしょ?って場合はこういう風にします。

class Japanese
  def hello
    puts 'こんにちは'
  end
end

class American < Japanese
  def hello
    super
    puts 'hello'
  end
end

japanese = Japanese.new
japanese.hello
=>こんにちは

american = American.new
american.hello
=>こんにちは
     hello

追記

オーバーライドした方に引数がある場合です

その場合は元のメソッドにはないので、super()と書いてあげます。

class Japanese
  def hello
    puts 'こんにちは'
  end
end

class American < Japanese
  def hello(name)
    super()
    puts "#{name} hello"
  end
end

american = American.new
american.hello("foo")
=>こんにちは
=>foo hello

以上です。

特定のcontroller配下で指定のレイアウトを使用する方法

railsadmin配下のレイアウトはapplication.html.slimとは別のものを使用したいため、テンプレートを分けたいということがありました。

やり方

  1. 対象のcontrollerのviewをlayouts以下に作成する
  2. 特定のcontrollerの配下で使用するため、layoutsを指定するようにする

まずは対象となるadmin controllerのlayoutsを変更します。

layouts/admin.html.slim

doctype html
html
  head
    title
      | Hoge
    = stylesheet_link_tag    'pc/application', media: 'all', 'data-turbolinks-track' => true
    = javascript_include_tag 'application', 'data-turbolinks-track' => true
    = yield :javascript
    = csrf_meta_tags
  body
    = render "header"
    .l__admin_container
      = render "admin/sidebar"
      = yield

このように、admin専用のlayoutsを作成します。

これでadimin controllerでは呼ばれるようになります。

ただし、adminを継承したcontrollerには適用されません。

そこで、継承で使用できるようにlayoutを指定してあげます。

class AdminController < ApplicationController
  layout 'admin'
end

これでAdminController を継承したcontrollerはlayoutのテンプレートがlayouts/admin.html.slimが適用されます。

coffeescriptで引数にメソッドの返り値を渡す

メソッドの引数で、めんどくさいんで返り値にメソッドを渡せないのかなと思っていたのですが、可能でした。

javascriptでも返り値に引数にメソッドの値を渡せるんですね。

$ ->
  delete_submit(blog_count ->)

  $('[data-delete-blog-no]').on 'click', ->
    if(confirm("削除してよろしいですか?"))
      $(this).closest('[data-blog-no]').remove()
      delete-submit(blog_count ->)
    else
      false

delete_submit = (count) ->
  if count == 0
    $('[data-send]').remove()

blog_count = ->
  $('[data-blog-no]').length

blog_countは要素数を返すだけのメソッドです。

delete_submitは引数の要素数0なら送信ボタンを削除するというものです。

引数の中で関数を使えば、そのまま返せます。