アトトック代表の開発ブログ

プログラミングのことや趣味のことなど書いてます。

ActiveRecordのenum

ActiveRecordenumが便利なので最近よく使っています。
例えば、Spotモデルに下書き(draft)と公開(published)の状態をもつステータスを追加するとします。

Spotモデルにstatusカラムを追加するマイグレーションを作成

rails g migration add_status_to_spot status:integer

Spotモデルにenumを追加

class Spot < ApplicationRecord
  belongs_to :prefecture
  belongs_to :area

  has_many :spot_photos
  has_many :spot_galleries
  validates :title, presence: true
  validates :prefecture_id, presence: true
  validates :area_id, presence: true
  validates :latitude, numericality: true
  validates :longitude, numericality: true

  enum status: { draft: 0, published: 1}
end

statusの値をenumで定義します。
statusが0の場合はdraft、1の場合はpublishedにします。

statusの判定

rails consoleで動作を確認してみます。

$ rails c
Running via Spring preloader in process 67570
Loading development environment (Rails 5.0.5)
irb(main):001:0> spot = Spot.new
=> #<Spot id: nil, title: nil, prefecture_id: nil, address: nil, overview: nil, des
cription: nil, area_id: nil, created_at: nil, updated_at: nil, url: nil, title_kana
: nil, address_description: nil, latitude: nil, longitude: nil, category_id: nil, i
dentifier: nil, status: nil>
irb(main):002:0>

irb(main):003:0* spot.status = 0
=> 0
irb(main):004:0> spot.draft?
=> true
irb(main):005:0> spot.published?
=> false

この例では、soptのstatusに0を代入しています。
この場合、spot.draft? でstatusの値が0なのか、spot.published? でststusの値が1なのかを判定できるようになります。
ststusに0を代入しているので、spot.draft? はtrueになっています。

次にspot.statusに1を代入してみます。

irb(main):017:0* spot.status = 1
=> 1
irb(main):018:0> spot.draft?
=> false
irb(main):019:0> spot.published?
=> true

spot.draft?はfalseになり、spot.published?はtrueになります。
このようにstatusの状態を判定します。

statusのハッシュ

下記のように属性名の複数形でenumで定義したハッシュが取得できます。

irb(main):032:0* Spot.statuses
=> {"draft"=>0, "published"=>1}

statusは数値ではなく文字列を返す

Spotのstatusはintegerですが、下記のようにspot.statusは文字列を返します。

irb(main):034:0* spot.status = 0
=> 0
irb(main):035:0> spot.status
=> "draft"
irb(main):036:0> spot.status = 1
=> 1
irb(main):037:0> spot.status
=> "published"
irb(main):038:0> spot.status.class
=> String

enumで指定した値以外は設定できない

enumで定義した値以外を代入すると例外が発生します。

irb(main):039:0> spot.status = 2
ArgumentError: '2' is not a valid status

写真がおもしろい

最近、写真にハマってます。

高校の頃は写真部で20代の頃までいろいろ撮ってましたが、長い間、カメラから離れていました。

写真は撮っていましたが、手軽なこともありiPhoneで撮影することが多くなっていました。

昨年、RICOHのTHETAを購入し360°カメラにハマり、今年になってOLYMPUSのOM-Dを購入し出かける時はいつも持ち歩いています。

 

本日、荒川の土手から撮影した夕日

f:id:kurusaki:20171111233033j:image

f:id:kurusaki:20171111233129j:image

 

ランニング中にTHETAで撮影

f:id:kurusaki:20171111233235j:image

 

https://theta360.com/ja/

 

昨年、THETA用のアプリを開発しようと思い購入しましたが、今では撮影する方にハマってしまいました。

昨年、始めたアプリ開発は一時停止中ですが、360°も含め写真を使った自社プロダクトの企画も進めています。

公開はまだ先になりそうです。

 

しばらく自社プロダクトの開発は止まっていましたが、少しずつですが再開しています。

まず、手が回っていなかったTalkingBookシリーズなどのiOSアプリのバージョンアップ、10Yearsのウェブ版とiOSアプリのバージョンアップを進めています。

アトトックのプロダクト開発再開です。

コーポレートサイトもリニューアル中です。

http://atotok.co.jp

アトトックラボもリニューアルし開発ネタなど発信していきます。

 

また、いろいろ動き出してます。

 

あと、写真のこと勉強し直してます。

https://www.amazon.co.jp/dp/4844134213

 

 

 

 

 

Rubyの可変長引数

Rubyでメソッドに可変長引数を渡したい場合は、引数の前に*を付けます。

普通の引数と可変長引数を受け取るメソッドと可変長引数だけ受け取るメソッドで動作確認してみます。
下記のサンプルのfunc1では2つ目までの引数を必須にし、そのあとの引数を可変にしています。
func2では全ての引数を可変にしています。

サンプルコード

#
# 普通の引数と可変長引数を受け取るメソッド
#
def func1(arg1, arg2, *args)
  puts '<<<<< func1 >>>>>'
  puts "arg1 : #{arg1}"
  puts "arg2 : #{arg2}"
  puts "args : #{args}"
  puts "args.class : #{args.class}"
  puts
end

#
# 可変長引数だけ受け取るメソッド
#
def func2(*args)
  puts '<<<<< func2 >>>>>'
  puts "args : #{args}"
  puts "args.class : #{args.class}"
  puts
end

func1('test-1-1', 'test-1-2', 'a')

func1('test-2-1', 'test-2-2', 'a', 'b', 'c', 1, 2, 3)

func1('test-3-1', 'test-3-2')

# 固定引数を渡さないと例外が発生する
begin
  func1()
rescue Exception => e
  puts "例外発生 : #{e}"
  puts "例外クラス : #{e.class}"
end

# こっちは可変引数なので引数なしでもOK
func2()

func2('a')

func2('a', 'b', 'c', 1, 2, 3)

実行結果を確認すると可変引数の方はArrayになっています。
固定引数と可変長引数を渡したい場合は、func1のように可変長引数の前に固定の引数を渡ます。
func2の引数は可変引数のみなので引数なしで呼び出しても例外は発生しませんが、 func1には固定引数があるので引数なしで呼び出すとArgumentErrorが発生します。

実行結果

<<<<< func1 >>>>>
arg1 : test-1-1
arg2 : test-1-2
args : ["a"]
args.class : Array

<<<<< func1 >>>>>
arg1 : test-2-1
arg2 : test-2-2
args : ["a", "b", "c", 1, 2, 3]
args.class : Array

<<<<< func1 >>>>>
arg1 : test-3-1
arg2 : test-3-2
args : []
args.class : Array

例外発生 : wrong number of arguments (given 0, expected 2+)
例外クラス : ArgumentError
<<<<< func2 >>>>>
args : []
args.class : Array

<<<<< func2 >>>>>
args : ["a"]
args.class : Array

<<<<< func2 >>>>>
args : ["a", "b", "c", 1, 2, 3]
args.class : Array

画像アップローダ CarrierWaveの使い方

Railsのアプリでで画像をアップロードするGemのCarrierWaveの使い方をまとめてみます。

CarrierWaveはこれ↓
https://github.com/carrierwaveuploader/carrierwave

Gemfile

Gemfileにcarrierwaveとrmagickを追加する。

gem 'carrierwave'
gem 'rmagick'

bundle install

$ bundle install --path vendor/bundle

uploaderを作成

$ rails g uploader image

最後のimageの部分がクラス名に付くのでアップロードする画像毎に、
アップロード先のディレクトリやその他の設定を変更したい場合は名前を変えて指定する。
上記の場合は、app/uploaders/image_uploader.rbが作成される。

uploaderクラスの設定例

# encoding: utf-8

class ActionImageUploader < CarrierWave::Uploader::Base

  # Include RMagick or MiniMagick support:
  include CarrierWave::RMagick
  # include CarrierWave::MiniMagick

  # Choose what kind of storage to use for this uploader:
  storage :file
  # storage :fog

  # Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # Provide a default URL as a default if there hasn't been a file uploaded:
  # def default_url
  #   # For Rails 3.1+ asset pipeline compatibility:
  #   # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
  #
  #   "/images/fallback/" + [version_name, "default.png"].compact.join('_')
  # end

  # Process files as they are uploaded:
  # process :scale => [200, 300]
  #
  def scale(width, height)
     # do something
  end

  # Create different versions of your uploaded files:
  version :thumb do
    process :resize_to_fit => [100, 100]
  end

  # Add a white list of extensions which are allowed to be uploaded.
  # For images you might use something like this:
  def extension_white_list
    %w(jpg jpeg gif png)
  end

  # Override the filename of the uploaded files:
  # Avoid using model.id or version_name here, see uploader/store.rb for details.
  # def filename
  #   "something.jpg" if original_filename
  # end
  def filename
    time = Time.now
    name = time.strftime('%Y%m%d%H%M%S') + '.jpg'
    name.downcase
  end

end

モデルにuploaderクラスを指定

class Hoge < ActiveRecord::Base

  mount_uploader :picture, ImageUploader

end

_form.html.hamlの修正

  .form-group
    = f.label :picture, class: 'control-label'
    .controls
      
      - if @hoge.picture?
        = image_tag @hoge.picture.url
        %br
      = f.file_field :picture, class: 'form-control'
      = f.hidden_field :picture_cache
      -if @hoge.persisted? && @hoge.picture?
        = f.check_box :remove_picture
        画像を削除
{/code}

_show.html.hamlの修正

%p
  %strong= model_class.human_attribute_name(:picture) + ':'
  %br
  - if @hoge.picture?
    =image_tag @hoge.picture.url

サムネイルの表示

          -if button_action.picture?
            = image_tag button_action.picture_url(:thumb).to_s

Controllerを修正

対象のカラムに合わせてキャッシュ用の:xxxx_cache, 削除用の:remove_xxxxを追加する

    def hoge_params
      params.require(:hoge).permit(:title, :picture, :picture_cache, :remove_picture)
    end

Rubyでメソッドを動的に呼び出す

メソッドを動的に呼び出すにはObjectのsendを使います。

class Hoge
  def medhod_01(val)
    puts "val : #{val}"
  end
end

hoge = Hoge.new
# 普通にメソッドを呼び出す
hoge.medhod_01(10)

# 動的にメソッドを呼び出す
hoge.send(:medhod_01, 20)

sendメソッドの第一引数にメソッド名を指定します。
メソッド名はシンボルか文字列で渡します。

実行結果

val : 10
val : 20

Rubyのブロック付きのメソッド

Rubyでブロック付きのメソッドを作ってみます。

def func(value1, value2)
  puts "value1 : #{value1}, value2 : #{value2}"
  a = value1 + value2
  b = value1 - value2
  c = value1 * value2
  d = value1 / value2
  yield(a, b, c, d) if block_given?
  return a, b, c, d
end

puts 'メソッドをブロック付きで実行'
func(1, 2) do |a1, b1, c1, d1|
  puts "a1 : #{a1}"
  puts "b1 : #{b1}"
  puts "c1 : #{c1}"
  puts "d1 : #{d1}"
end

puts 'メソッドをブロックなしで実行'
a2,b2,c2,d2 = func(10, 20)
puts "a2 : #{a2}"
puts "b2 : #{b2}"
puts "c2 : #{c2}"
puts "d2 : #{d2}"

yieldは、定義したブロック付きメソッドでブロックを呼び出します。

block_given?は、メソッドにブロックがあればtrueを返します。

実行結果

メソッドをブロック付きで実行
value1 : 1, value2 : 2
a1 : 3
b1 : -1
c1 : 2
d1 : 0
メソッドをブロックなしで実行
value1 : 10, value2 : 20
a2 : 30
b2 : -10
c2 : 200
d2 : 0

開発関係のネタはこちらで

今までhttp://d.hatena.ne.jp/kurusaki/ の方でブログ書いてましたが、最近は更新も少なくってました。
いろいろネタも増えてきそうなので
心機一転、開発関係のネタはこちらのブログで書こうと思います。