20190607のRubyに関する記事は16件です。

ゲッターとセッターについて

1.ゲッターとは

ゲッターとは,インスタンス変数をクラス内から参照するメソッドのことをいう。
以下のようなコードにおいて、本来Rubyではインスタンス変数の値はクラス内からでしか取得することはできない。

class Movie
  def initialize(name)
    @name = name
  end
end

obj1 = Movie.new('Forrest Gump')

p obj1.name #=>undefined method `name' for #<Movie:0x00007fd4558526f0 @name="Forrest Gump">

そこで、それを可能にするため、クラス内にインスタンス変数を参照する専用のメソッド「ゲッター」を定義する。

class Movie
  def initialize(name)
    @name = name
  end

  def getName #「ゲッター」
    @name
  end
end

obj1 = Movie.new('Forrest Gump')

p obj1.getName #=> 'Forrest Gump'

2.セッターとは

セッターとは、インスタンス変数をクラス内で更新するメソッドのことをいう。
以下のようなコードにおいて、セッターにおいてもインスタンス変数の更新もクラス内でしかでない。

class Movie
  def initialize(name)
    @name = name
  end
end

obj1 = Movie.new('Forrest Gump')

p obj1.name = 'Fight Club' #=>undefined method `name=' for #<Movie:0x00007ffd4a83b400 @name="Forrest Gump"> (NoMethodError)

そこで、それを可能にするため、クラス内にインスタンス変数の内容を更新する専用のメソッド「セッター」を定義する。

class Movie
  def initialize(name)
    @name = name
  end

  def changeName=(name) #「セッター」
    @name = name
  end
end

obj1 = Movie.new('Forrest Gump')
obj1.changeName = 'Fight Club'
p obj1.getName #=> 'Fight Club

この時、セッターの定義において、
メソッド名の右側に「=()」が必要となる。
 
                           以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsチュートリアル3日目:1章クリア!

今日やったこと

・1章初めからやり直し
・1章クリア!

初めからやり直したら、Gitの設定がうまくいった!

昨日はマージをしようとしたら、.c9/metadata...というファイルが邪魔をしてマージ出来ませんでした。.gitのファイル位置がおかしいことに気づき、今日はgit initする時のディレクトリに注意しながら、最初から1章をやり直してみました!

昨日の気づき(.gitの位置がおかしい)
無題2.png

その結果、無事にマージすることが出来ました!

ec2-user:~/environment/hello_app (master) $ git branch
* master
  modify-README
ec2-user:~/environment/hello_app (master) $ git merge modify-README
Updating f807c10..fd0d9f8
Fast-forward
 README.md | 9 +++++++++
 1 file changed, 9 insertions(+)

その後は順調に進んで、1章をクリアすることができました!
(写真撮り損ねた…)

ただ、また疑問が出てきて、cloud9を閉じてからもう一回herokuに入ろうとすると、

ec2-user:~/environment/hello_app (master) $ heroku --versionbash: heroku: command not found

あれ?herokuのバッシュが消えてる?
まだcloud9の仕様がわからず、一度閉じたら何がリセットされて何がそのままの設定で残っているのか、全然分かっていない気がします。
明日はこの辺りをググってみてcloud9やバッシュなど全体的に学んでいきたいと思いました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails モデルについて

modelの作成

$ rails g model モデル名

上記をを実行するとマイグレーションファイルが作成される。  
これを使うことでSQL文を書かずして、railsからmysqlなどのデータベースを操作することができる。作成するだけでは操作できない。  

マイグレーションファイルの実行

$ rails db:migrate

このコマンドでデータベースにモデル名のテーブルが作成される。
railsのルールとして一度実行されたファイルに変更をしてはならない。  
データベースに変更を加えたい場合、rails db:migrate:resetでもう一度作り直すかカラムを追加する方法がある。

カラムの追加

$ rails g migration Addカラム名Toテーブル名

テーブル名はモデル名の小文字複数形ですが、上記ではキャメルケース記法なので大文字になります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[error]rails aborted! I18n::MissingTranslationData: translation missing: en.faker.name.first_name

rails aborted!I18n::MissingTranslationData: translation missing: en.faker.name.first_name

bundle exec rails db:resetしようとしたら、上記のエラーがでた

解決方法

下記をコピペ*日本語での設定とかそこらへん

local.rb
require 'i18n'
I18n.locale # => :en
I18n.locale = :ja
I18n.locale # => :ja
require 'faker'
Faker::Config.locale # => :ja
Faker::Internet.email # => ".@.com"

$ bundle exec rails db:reset
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[rails error]rails aborted! I18n::MissingTranslationData: translation missing: en.faker.name.first_name

rails aborted!I18n::MissingTranslationData: translation missing: en.faker.name.first_name

bundle exec rails db:resetしようとしたら、上記のエラーがでた

解決方法

下記をコピペ*日本語での設定とかそこらへん

local.rb
require 'i18n'
I18n.locale # => :en
I18n.locale = :ja
I18n.locale # => :ja
require 'faker'
Faker::Config.locale # => :ja
Faker::Internet.email # => ".@.com"

$ bundle exec rails db:reset
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsでTwitterログイン認証を実装

RailsアプリでTwitterログインを実装する方法をまとめます。

deviseの導入

メールアドレスやSNSアカウントを用いてのログインを、簡単に実装するGem「devise」と、OAuth認証に必要なGem「omniauth」「omniauth-twitter」を導入します。

Gemfile.rb
gem 'devise'
gem 'omniauth'
gem 'omniauth-twitter'

続いて、bundle installを行います。

bundle install

※ライブラリはサーバーの起動時に読み込まれるので、ここでいったんサーバーを停止し、再起動しましょう。

deviseの設定

アプリケーションにdeviseをインストールします。

rails g devise:install

1から4までのステップが書かれた英文が表示されるので、指示通りにコーディングします。

デフォルトURL

config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }をconfig/environments/development.rbに追加します。

config/environments/development.rb
Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

・・・(:場所はどこでも良いです)

  config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
end

root_urlを指定

ルートURLにアクセスした際のページを作成します。

rails g controller Pages index

Pagesコントローラーには、indexアクションを、ビューファイルindex.html.erbを追加しています。

フラッシュメッセージを指定

ログインが成功した際に表示する、フラッシュメッセージを、application.html.erbの

下に指定します。
app/views/layouts/application.html.erb
・・・()
</head>
<body>
  <p class="notice"><%= notice %></p>
  <p class="alert"><%= alert %></p>

    <%= yield %>
</body> 

devise用のviewを作成

rails g devise:views

app/views/devise/以下にファイルが作成されます。

Userモデルを作成

rails g devise User

※モデル名なので単数形

これにより、dbフォルダ下にマイグレーションファイルが作成されます。

ユーザーモデルを編集

user.rbのdevise:以下に「:omniauthable」を追加します。

model/user.rb
devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :omniauthable

データベースにカラムを追加

deviseはデフォルトでメールアドレスとパスワードした扱うことができません。ツイッターログインで扱うデータのカラムをあらかじめ追加しておく必要があります。

以下のカラムを追加します。

  • ユーザーID(uid)
  • プロバイダー(provider)
  • アカウント名(name)
  • アカウントID(nickname)
  • 場所(location)
  • 画像(image)
rails g migration AddColumnsToUsers uid:string provider:string name:string nickname:string location:string image:string

ここまで終わったら、以下マイグレーションを実行します。

rails db:migrate

Twitter Developerの登録

API Keyと、Secret Keyを取得します。(省略)

取得した2つのキーを貼り付け

config/initializer/devise.rb
Devise.setup do |config|
・・・()
  config.omniauth :twitter, 'API key(各自取得したキーを入れてください)', 'API secret(各自取得したキーを入れてください)'
end

Userモデルを編集する

model/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :omniauthable


    def self.find_for_oauth(auth)
    user = User.where(uid: auth.uid, provider: auth.provider).first

    unless user
      user = User.create(
        uid:      auth.uid,
        provider: auth.provider,
        email:    User.dummy_email(auth),
        password: Devise.friendly_token[0, 20],
        image: auth.info.image,
        name: auth.info.name,
        nickname: auth.info.nickname,
        location: auth.info.location
      )
    end

    user
  end

  private

  def self.dummy_email(auth)
    "#{auth.uid}-#{auth.provider}@example.com"
  end

end

Teitter認証の際に取得した各データを、データベースに格納しています。メールアドレスがない場合はdevise側でエラーが発生してしまうので、ダミーのアドレスを作成しています。

コールバックの設定

app/controllers/以下に「users」フォルダを作成し、その下に「omniauth_callbacks_controller.rb」ファイルを作成します。

omniauth_callbacks_controller.rbは以下のように編集します。

omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def twitter
    callback_from :twitter
  end

  private
  def callback_from(provider)
    provider = provider.to_s

    @user = User.find_for_oauth(request.env['omniauth.auth'])

    if @user.persisted?
      print("persisted true")
      flash[:notice] = I18n.t('devise.omniauth_callbacks.success', kind: provider.capitalize)
      sign_in_and_redirect @user, event: :authentication
    else
      print("persisted false")
      session["devise.#{provider}_data"] = request.env['omniauth.auth']
      redirect_to controller: 'sessions', action: 'new'
    end
  end
end

ルーティングの設定

config/route.rbのdevise_forを、以下のように編集します。

config/route.rb
devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }

ビューファイルを整える

ビューファイルを以下のように整えます。

index.html.rb
<% if user_signed_in? %>
  <%=current_user.email%>
  <%=current_user.name%>
  <%=current_user.nickname%>
  <%=current_user.image%>
  <%=current_user.location%>
  <%= link_to "ログアウト", destroy_user_session_path, method: :delete %>
<% else %>
  <%= link_to "ログイン", new_user_session_path %>
  <%= link_to " Twitterでログイン/登録", user_twitter_omniauth_authorize_path %>
<% end %>

以上でツイッター認証ログインが可能になりました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby on Railsを開発する時にいれておきたいAtomのパッケージ

ファイル検索・移動系

Rails Transporter

Controller/Model/View間の移動をコマンド一つできる。

RubyMineにもそんな機能があったからAtomにもないかなと思って探したら発見。
ControllerからViewを探すのは結構めんどくさいからかなり重宝している。

atom-fuzzy-grep

高速簡易グレップ。

Atomの標準でプロジェクト内全体の検索はできるが、このfuzzy-grepはより早く検索したい時におすすめ。

advanced-open-file

現在いるファイルから別のファイルを検索したい時に使える。

RailsだとViewでpartialの中身を確認したくて、別のファイルに移動する時に使える。
Viewって似たようなファイル名が多いから、grepしても検索結果が多くて判別できないため、これを使っている。

goto-definition

関数定義場所のファイルにジャンプする。

私はatom-fuzzy-grepでも十分かなと思うが、直接関数に飛んでくれるのでたまに使う。

Git系

git-blame

行毎に最終変更者が確認できる。

その名の通り、誰かのせいしたい時に使う。(笑)

git-time-machine

ファイルの過去の変更履歴を時系列で確認できる。

git-blameでは追えない犯人を追う時に使う。

その他

platformio-ide-terminal

Atom上にターミナルを開く。

初回起動の場所をプロジェクトのrootパス等に設定できるため、gitのコマンド等使いたい時に使用。
ちょっと重い。。

(更新中。。)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

cloud9→ローカルに移したら写真投稿ができなくなった

はじめに

SNS風の写真投稿アプリを久々にローカルで確認してみたところ、肝心の写真投稿がバリデーションに引っかかり、投稿はもちろん、テストなども通らなくなってしまっていました。

原因

結果を言ってしまうと、Minimagickを使うのに必要なImageMagickを、ローカルでインストールしていませんでした。

cloud9時代にImageMagickをインストールした自体あまり覚えていなかったので、気付くのが遅れてしまいました。

$ brew install imagemagick

これで今まで通り、問題なく写真投稿できるようになりました!

参考

Rails gem MiniMagick を利用して画像ファイルをリサイズする
RubyでImageMagick使うにはMiniMagickの方がよい

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Ruby]コピペでOpenWeatherMap

OpenWeatherMapからAPIで天気情報を取得する*ターミナル実行版とwebアプリ(Rails)実装版

OpenWeatherMapの登録は下記参照
https://qiita.com/nownabe/items/aeac1ce0977be963a740

ターミナル実行版

OpenWeatherMap.rb
API_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
BASE_URL = "https://api.openweathermap.org/data/2.5/forecast/"

require "json"
require "open-uri"

response = open(BASE_URL + "?id=1859171&APPID=#{"xxxxxxxxxxxxxxxxxxxxxxxxxxx"}")
puts JSON.pretty_generate(JSON.parse(response.read))

ターミナルで実行
[*注意]ruby -v '2.6.0'以上だとエラーになる

ターミナル
$ ruby OpenWeatherMap.rb                          //実行

実行結果

{
  "cod": "200",
  "message": 0.0073,
  "cnt": 40,
  "list": [
    {
      "dt": 1559887200,
      "main": {
        "temp": 296.86,
        "temp_min": 296.86,
        "temp_max": 301.4,
        "pressure": 1002.13,
        "sea_level": 1002.13,
        "grnd_level": 980.88,
        "humidity": 67,
        "temp_kf": -4.54
      },
      "weather": [
        {
          "id": 500,
          "main": "Rain",
          "description": "light rain",
          "icon": "10d"
        }
      ],
      "clouds": {
        "all": 94
      },
      "wind": {
        "speed": 4.82,
        "deg": 213.786
      },
      "rain": {
        "3h": 1.376
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-07 06:00:00"
    },
    {
      "dt": 1559898000,
      "main": {
        "temp": 294.7,
        "temp_min": 294.7,
        "temp_max": 298.1,
        "pressure": 1001.03,
        "sea_level": 1001.03,
        "grnd_level": 980.11,
        "humidity": 80,
        "temp_kf": -3.4
      },
      "weather": [
        {
          "id": 500,
          "main": "Rain",
          "description": "light rain",
          "icon": "10d"
        }
      ],
      "clouds": {
        "all": 100
      },
      "wind": {
        "speed": 2.71,
        "deg": 213.267
      },
      "rain": {
        "3h": 0.188
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-07 09:00:00"
    },
    {
      "dt": 1559908800,
      "main": {
        "temp": 294.43,
        "temp_min": 294.43,
        "temp_max": 296.7,
        "pressure": 1001.62,
        "sea_level": 1001.62,
        "grnd_level": 980.95,
        "humidity": 68,
        "temp_kf": -2.27
      },
      "weather": [
        {
          "id": 804,
          "main": "Clouds",
          "description": "overcast clouds",
          "icon": "04n"
        }
      ],
      "clouds": {
        "all": 100
      },
      "wind": {
        "speed": 5.25,
        "deg": 249.352
      },
      "sys": {
        "pod": "n"
      },
      "dt_txt": "2019-06-07 12:00:00"
    },
    {
      "dt": 1559919600,
      "main": {
        "temp": 293.37,
        "temp_min": 293.37,
        "temp_max": 294.5,
        "pressure": 1001.61,
        "sea_level": 1001.61,
        "grnd_level": 980.99,
        "humidity": 69,
        "temp_kf": -1.13
      },
      "weather": [
        {
          "id": 804,
          "main": "Clouds",
          "description": "overcast clouds",
          "icon": "04n"
        }
      ],
      "clouds": {
        "all": 100
      },
      "wind": {
        "speed": 5.86,
        "deg": 252.839
      },
      "sys": {
        "pod": "n"
      },
      "dt_txt": "2019-06-07 15:00:00"
    },
    {
      "dt": 1559930400,
      "main": {
        "temp": 292.8,
        "temp_min": 292.8,
        "temp_max": 292.8,
        "pressure": 1000.95,
        "sea_level": 1000.95,
        "grnd_level": 980.71,
        "humidity": 75,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 804,
          "main": "Clouds",
          "description": "overcast clouds",
          "icon": "04n"
        }
      ],
      "clouds": {
        "all": 100
      },
      "wind": {
        "speed": 5.69,
        "deg": 248.281
      },
      "sys": {
        "pod": "n"
      },
      "dt_txt": "2019-06-07 18:00:00"
    },
    {
      "dt": 1559941200,
      "main": {
        "temp": 292.3,
        "temp_min": 292.3,
        "temp_max": 292.3,
        "pressure": 1001.49,
        "sea_level": 1001.49,
        "grnd_level": 980.83,
        "humidity": 82,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 802,
          "main": "Clouds",
          "description": "scattered clouds",
          "icon": "03d"
        }
      ],
      "clouds": {
        "all": 45
      },
      "wind": {
        "speed": 5.06,
        "deg": 241.092
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-07 21:00:00"
    },
    {
      "dt": 1559952000,
      "main": {
        "temp": 296.5,
        "temp_min": 296.5,
        "temp_max": 296.5,
        "pressure": 1002.88,
        "sea_level": 1002.88,
        "grnd_level": 981.36,
        "humidity": 62,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 803,
          "main": "Clouds",
          "description": "broken clouds",
          "icon": "04d"
        }
      ],
      "clouds": {
        "all": 58
      },
      "wind": {
        "speed": 3.98,
        "deg": 249.845
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-08 00:00:00"
    },
    {
      "dt": 1559962800,
      "main": {
        "temp": 296.2,
        "temp_min": 296.2,
        "temp_max": 296.2,
        "pressure": 1003.73,
        "sea_level": 1003.73,
        "grnd_level": 981.81,
        "humidity": 70,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 803,
          "main": "Clouds",
          "description": "broken clouds",
          "icon": "04d"
        }
      ],
      "clouds": {
        "all": 67
      },
      "wind": {
        "speed": 1.5,
        "deg": 340.308
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-08 03:00:00"
    },
    {
      "dt": 1559973600,
      "main": {
        "temp": 295.4,
        "temp_min": 295.4,
        "temp_max": 295.4,
        "pressure": 1004.25,
        "sea_level": 1004.25,
        "grnd_level": 982.75,
        "humidity": 72,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 500,
          "main": "Rain",
          "description": "light rain",
          "icon": "10d"
        }
      ],
      "clouds": {
        "all": 72
      },
      "wind": {
        "speed": 4.61,
        "deg": 357.365
      },
      "rain": {
        "3h": 0.438
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-08 06:00:00"
    },
    {
      "dt": 1559984400,
      "main": {
        "temp": 293.048,
        "temp_min": 293.048,
        "temp_max": 293.048,
        "pressure": 1004.99,
        "sea_level": 1004.99,
        "grnd_level": 983.92,
        "humidity": 76,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 802,
          "main": "Clouds",
          "description": "scattered clouds",
          "icon": "03d"
        }
      ],
      "clouds": {
        "all": 31
      },
      "wind": {
        "speed": 4.5,
        "deg": 0.42
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-08 09:00:00"
    },
    {
      "dt": 1559995200,
      "main": {
        "temp": 289.7,
        "temp_min": 289.7,
        "temp_max": 289.7,
        "pressure": 1006.06,
        "sea_level": 1006.06,
        "grnd_level": 985.13,
        "humidity": 88,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 802,
          "main": "Clouds",
          "description": "scattered clouds",
          "icon": "03n"
        }
      ],
      "clouds": {
        "all": 42
      },
      "wind": {
        "speed": 2.18,
        "deg": 6.09
      },
      "sys": {
        "pod": "n"
      },
      "dt_txt": "2019-06-08 12:00:00"
    },
    {
      "dt": 1560006000,
      "main": {
        "temp": 289.72,
        "temp_min": 289.72,
        "temp_max": 289.72,
        "pressure": 1006.28,
        "sea_level": 1006.28,
        "grnd_level": 985.69,
        "humidity": 86,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 803,
          "main": "Clouds",
          "description": "broken clouds",
          "icon": "04n"
        }
      ],
      "clouds": {
        "all": 83
      },
      "wind": {
        "speed": 1.76,
        "deg": 334.496
      },
      "sys": {
        "pod": "n"
      },
      "dt_txt": "2019-06-08 15:00:00"
    },
    {
      "dt": 1560016800,
      "main": {
        "temp": 288.042,
        "temp_min": 288.042,
        "temp_max": 288.042,
        "pressure": 1005.9,
        "sea_level": 1005.9,
        "grnd_level": 984.97,
        "humidity": 92,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 803,
          "main": "Clouds",
          "description": "broken clouds",
          "icon": "04n"
        }
      ],
      "clouds": {
        "all": 57
      },
      "wind": {
        "speed": 1.71,
        "deg": 359.597
      },
      "sys": {
        "pod": "n"
      },
      "dt_txt": "2019-06-08 18:00:00"
    },
    {
      "dt": 1560027600,
      "main": {
        "temp": 289.247,
        "temp_min": 289.247,
        "temp_max": 289.247,
        "pressure": 1006.09,
        "sea_level": 1006.09,
        "grnd_level": 985.07,
        "humidity": 86,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 804,
          "main": "Clouds",
          "description": "overcast clouds",
          "icon": "04d"
        }
      ],
      "clouds": {
        "all": 87
      },
      "wind": {
        "speed": 2.01,
        "deg": 356.186
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-08 21:00:00"
    },
    {
      "dt": 1560038400,
      "main": {
        "temp": 294.176,
        "temp_min": 294.176,
        "temp_max": 294.176,
        "pressure": 1006.33,
        "sea_level": 1006.33,
        "grnd_level": 985.33,
        "humidity": 63,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 803,
          "main": "Clouds",
          "description": "broken clouds",
          "icon": "04d"
        }
      ],
      "clouds": {
        "all": 56
      },
      "wind": {
        "speed": 3.34,
        "deg": 17.913
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-09 00:00:00"
    },
    {
      "dt": 1560049200,
      "main": {
        "temp": 295.8,
        "temp_min": 295.8,
        "temp_max": 295.8,
        "pressure": 1005.59,
        "sea_level": 1005.59,
        "grnd_level": 984.65,
        "humidity": 59,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 803,
          "main": "Clouds",
          "description": "broken clouds",
          "icon": "04d"
        }
      ],
      "clouds": {
        "all": 69
      },
      "wind": {
        "speed": 4.09,
        "deg": 35.192
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-09 03:00:00"
    },
    {
      "dt": 1560060000,
      "main": {
        "temp": 298.405,
        "temp_min": 298.405,
        "temp_max": 298.405,
        "pressure": 1004.47,
        "sea_level": 1004.47,
        "grnd_level": 983.01,
        "humidity": 53,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 802,
          "main": "Clouds",
          "description": "scattered clouds",
          "icon": "03d"
        }
      ],
      "clouds": {
        "all": 44
      },
      "wind": {
        "speed": 2.69,
        "deg": 34.783
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-09 06:00:00"
    },
    {
      "dt": 1560070800,
      "main": {
        "temp": 294.41,
        "temp_min": 294.41,
        "temp_max": 294.41,
        "pressure": 1004.04,
        "sea_level": 1004.04,
        "grnd_level": 982.98,
        "humidity": 72,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 500,
          "main": "Rain",
          "description": "light rain",
          "icon": "10d"
        }
      ],
      "clouds": {
        "all": 31
      },
      "wind": {
        "speed": 4,
        "deg": 19.591
      },
      "rain": {
        "3h": 0.25
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-09 09:00:00"
    },
    {
      "dt": 1560081600,
      "main": {
        "temp": 289.351,
        "temp_min": 289.351,
        "temp_max": 289.351,
        "pressure": 1005.35,
        "sea_level": 1005.35,
        "grnd_level": 984.46,
        "humidity": 89,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 801,
          "main": "Clouds",
          "description": "few clouds",
          "icon": "02n"
        }
      ],
      "clouds": {
        "all": 20
      },
      "wind": {
        "speed": 2.87,
        "deg": 11.416
      },
      "rain": {
      },
      "sys": {
        "pod": "n"
      },
      "dt_txt": "2019-06-09 12:00:00"
    },
    {
      "dt": 1560092400,
      "main": {
        "temp": 288.4,
        "temp_min": 288.4,
        "temp_max": 288.4,
        "pressure": 1005.21,
        "sea_level": 1005.21,
        "grnd_level": 984.18,
        "humidity": 90,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 800,
          "main": "Clear",
          "description": "clear sky",
          "icon": "01n"
        }
      ],
      "clouds": {
        "all": 6
      },
      "wind": {
        "speed": 2.52,
        "deg": 7.258
      },
      "sys": {
        "pod": "n"
      },
      "dt_txt": "2019-06-09 15:00:00"
    },
    {
      "dt": 1560103200,
      "main": {
        "temp": 287.939,
        "temp_min": 287.939,
        "temp_max": 287.939,
        "pressure": 1004.63,
        "sea_level": 1004.63,
        "grnd_level": 983.37,
        "humidity": 94,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 800,
          "main": "Clear",
          "description": "clear sky",
          "icon": "01n"
        }
      ],
      "clouds": {
        "all": 5
      },
      "wind": {
        "speed": 2.32,
        "deg": 12.261
      },
      "sys": {
        "pod": "n"
      },
      "dt_txt": "2019-06-09 18:00:00"
    },
    {
      "dt": 1560114000,
      "main": {
        "temp": 289.358,
        "temp_min": 289.358,
        "temp_max": 289.358,
        "pressure": 1005.1,
        "sea_level": 1005.1,
        "grnd_level": 983.94,
        "humidity": 90,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 800,
          "main": "Clear",
          "description": "clear sky",
          "icon": "01d"
        }
      ],
      "clouds": {
        "all": 7
      },
      "wind": {
        "speed": 2.3,
        "deg": 16.687
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-09 21:00:00"
    },
    {
      "dt": 1560124800,
      "main": {
        "temp": 296.343,
        "temp_min": 296.343,
        "temp_max": 296.343,
        "pressure": 1005.53,
        "sea_level": 1005.53,
        "grnd_level": 984.13,
        "humidity": 66,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 800,
          "main": "Clear",
          "description": "clear sky",
          "icon": "01d"
        }
      ],
      "clouds": {
        "all": 4
      },
      "wind": {
        "speed": 2.04,
        "deg": 45.815
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-10 00:00:00"
    },
    {
      "dt": 1560135600,
      "main": {
        "temp": 298.408,
        "temp_min": 298.408,
        "temp_max": 298.408,
        "pressure": 1005,
        "sea_level": 1005,
        "grnd_level": 983.49,
        "humidity": 63,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 500,
          "main": "Rain",
          "description": "light rain",
          "icon": "10d"
        }
      ],
      "clouds": {
        "all": 12
      },
      "wind": {
        "speed": 1.79,
        "deg": 21.528
      },
      "rain": {
        "3h": 2.125
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-10 03:00:00"
    },
    {
      "dt": 1560146400,
      "main": {
        "temp": 296.257,
        "temp_min": 296.257,
        "temp_max": 296.257,
        "pressure": 1004.36,
        "sea_level": 1004.36,
        "grnd_level": 983.06,
        "humidity": 76,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 501,
          "main": "Rain",
          "description": "moderate rain",
          "icon": "10d"
        }
      ],
      "clouds": {
        "all": 7
      },
      "wind": {
        "speed": 3.57,
        "deg": 21.13
      },
      "rain": {
        "3h": 6.437
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-10 06:00:00"
    },
    {
      "dt": 1560157200,
      "main": {
        "temp": 293.947,
        "temp_min": 293.947,
        "temp_max": 293.947,
        "pressure": 1005.03,
        "sea_level": 1005.03,
        "grnd_level": 983.77,
        "humidity": 84,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 501,
          "main": "Rain",
          "description": "moderate rain",
          "icon": "10d"
        }
      ],
      "clouds": {
        "all": 13
      },
      "wind": {
        "speed": 3.2,
        "deg": 21.107
      },
      "rain": {
        "3h": 3.75
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-10 09:00:00"
    },
    {
      "dt": 1560168000,
      "main": {
        "temp": 290.3,
        "temp_min": 290.3,
        "temp_max": 290.3,
        "pressure": 1006.84,
        "sea_level": 1006.84,
        "grnd_level": 985.56,
        "humidity": 90,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 500,
          "main": "Rain",
          "description": "light rain",
          "icon": "10n"
        }
      ],
      "clouds": {
        "all": 8
      },
      "wind": {
        "speed": 1.98,
        "deg": 7.495
      },
      "rain": {
        "3h": 0.188
      },
      "sys": {
        "pod": "n"
      },
      "dt_txt": "2019-06-10 12:00:00"
    },
    {
      "dt": 1560178800,
      "main": {
        "temp": 289.445,
        "temp_min": 289.445,
        "temp_max": 289.445,
        "pressure": 1007.43,
        "sea_level": 1007.43,
        "grnd_level": 986.32,
        "humidity": 92,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 800,
          "main": "Clear",
          "description": "clear sky",
          "icon": "01n"
        }
      ],
      "clouds": {
        "all": 0
      },
      "wind": {
        "speed": 1.61,
        "deg": 13.709
      },
      "sys": {
        "pod": "n"
      },
      "dt_txt": "2019-06-10 15:00:00"
    },
    {
      "dt": 1560189600,
      "main": {
        "temp": 288.941,
        "temp_min": 288.941,
        "temp_max": 288.941,
        "pressure": 1007.49,
        "sea_level": 1007.49,
        "grnd_level": 986.05,
        "humidity": 92,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 800,
          "main": "Clear",
          "description": "clear sky",
          "icon": "01n"
        }
      ],
      "clouds": {
        "all": 0
      },
      "wind": {
        "speed": 0.95,
        "deg": 22.657
      },
      "sys": {
        "pod": "n"
      },
      "dt_txt": "2019-06-10 18:00:00"
    },
    {
      "dt": 1560200400,
      "main": {
        "temp": 291.4,
        "temp_min": 291.4,
        "temp_max": 291.4,
        "pressure": 1008.18,
        "sea_level": 1008.18,
        "grnd_level": 986.81,
        "humidity": 88,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 800,
          "main": "Clear",
          "description": "clear sky",
          "icon": "01d"
        }
      ],
      "clouds": {
        "all": 0
      },
      "wind": {
        "speed": 0.7,
        "deg": 67.893
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-10 21:00:00"
    },
    {
      "dt": 1560211200,
      "main": {
        "temp": 296.4,
        "temp_min": 296.4,
        "temp_max": 296.4,
        "pressure": 1008.44,
        "sea_level": 1008.44,
        "grnd_level": 987.3,
        "humidity": 67,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 800,
          "main": "Clear",
          "description": "clear sky",
          "icon": "01d"
        }
      ],
      "clouds": {
        "all": 1
      },
      "wind": {
        "speed": 1.97,
        "deg": 174.658
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-11 00:00:00"
    },
    {
      "dt": 1560222000,
      "main": {
        "temp": 299.559,
        "temp_min": 299.559,
        "temp_max": 299.559,
        "pressure": 1007.41,
        "sea_level": 1007.41,
        "grnd_level": 986.23,
        "humidity": 50,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 500,
          "main": "Rain",
          "description": "light rain",
          "icon": "10d"
        }
      ],
      "clouds": {
        "all": 6
      },
      "wind": {
        "speed": 2.13,
        "deg": 214.981
      },
      "rain": {
        "3h": 1.062
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-11 03:00:00"
    },
    {
      "dt": 1560232800,
      "main": {
        "temp": 299.7,
        "temp_min": 299.7,
        "temp_max": 299.7,
        "pressure": 1006.16,
        "sea_level": 1006.16,
        "grnd_level": 984.58,
        "humidity": 46,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 801,
          "main": "Clouds",
          "description": "few clouds",
          "icon": "02d"
        }
      ],
      "clouds": {
        "all": 20
      },
      "wind": {
        "speed": 3.15,
        "deg": 221.401
      },
      "rain": {
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-11 06:00:00"
    },
    {
      "dt": 1560243600,
      "main": {
        "temp": 296.559,
        "temp_min": 296.559,
        "temp_max": 296.559,
        "pressure": 1006.38,
        "sea_level": 1006.38,
        "grnd_level": 985.28,
        "humidity": 65,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 804,
          "main": "Clouds",
          "description": "overcast clouds",
          "icon": "04d"
        }
      ],
      "clouds": {
        "all": 95
      },
      "wind": {
        "speed": 1.96,
        "deg": 223.261
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-11 09:00:00"
    },
    {
      "dt": 1560254400,
      "main": {
        "temp": 292.7,
        "temp_min": 292.7,
        "temp_max": 292.7,
        "pressure": 1007.86,
        "sea_level": 1007.86,
        "grnd_level": 986.74,
        "humidity": 79,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 804,
          "main": "Clouds",
          "description": "overcast clouds",
          "icon": "04n"
        }
      ],
      "clouds": {
        "all": 97
      },
      "wind": {
        "speed": 1.2,
        "deg": 222.469
      },
      "sys": {
        "pod": "n"
      },
      "dt_txt": "2019-06-11 12:00:00"
    },
    {
      "dt": 1560265200,
      "main": {
        "temp": 291.041,
        "temp_min": 291.041,
        "temp_max": 291.041,
        "pressure": 1007.44,
        "sea_level": 1007.44,
        "grnd_level": 986.36,
        "humidity": 87,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 803,
          "main": "Clouds",
          "description": "broken clouds",
          "icon": "04n"
        }
      ],
      "clouds": {
        "all": 77
      },
      "wind": {
        "speed": 0.8,
        "deg": 212.856
      },
      "sys": {
        "pod": "n"
      },
      "dt_txt": "2019-06-11 15:00:00"
    },
    {
      "dt": 1560276000,
      "main": {
        "temp": 290.141,
        "temp_min": 290.141,
        "temp_max": 290.141,
        "pressure": 1006.83,
        "sea_level": 1006.83,
        "grnd_level": 985.68,
        "humidity": 92,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 802,
          "main": "Clouds",
          "description": "scattered clouds",
          "icon": "03n"
        }
      ],
      "clouds": {
        "all": 39
      },
      "wind": {
        "speed": 0.43,
        "deg": 109.654
      },
      "sys": {
        "pod": "n"
      },
      "dt_txt": "2019-06-11 18:00:00"
    },
    {
      "dt": 1560286800,
      "main": {
        "temp": 292.2,
        "temp_min": 292.2,
        "temp_max": 292.2,
        "pressure": 1007.35,
        "sea_level": 1007.35,
        "grnd_level": 986.15,
        "humidity": 84,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 800,
          "main": "Clear",
          "description": "clear sky",
          "icon": "01d"
        }
      ],
      "clouds": {
        "all": 0
      },
      "wind": {
        "speed": 0.64,
        "deg": 102.032
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-11 21:00:00"
    },
    {
      "dt": 1560297600,
      "main": {
        "temp": 297.3,
        "temp_min": 297.3,
        "temp_max": 297.3,
        "pressure": 1007.36,
        "sea_level": 1007.36,
        "grnd_level": 986.29,
        "humidity": 64,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 800,
          "main": "Clear",
          "description": "clear sky",
          "icon": "01d"
        }
      ],
      "clouds": {
        "all": 0
      },
      "wind": {
        "speed": 1.85,
        "deg": 157.755
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-12 00:00:00"
    },
    {
      "dt": 1560308400,
      "main": {
        "temp": 299.986,
        "temp_min": 299.986,
        "temp_max": 299.986,
        "pressure": 1006.59,
        "sea_level": 1006.59,
        "grnd_level": 985.33,
        "humidity": 55,
        "temp_kf": 0
      },
      "weather": [
        {
          "id": 800,
          "main": "Clear",
          "description": "clear sky",
          "icon": "01d"
        }
      ],
      "clouds": {
        "all": 2
      },
      "wind": {
        "speed": 1.6,
        "deg": 241.438
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-06-12 03:00:00"
    }
  ],
  "city": {
    "id": 1859171,
    "name": "Kobe-shi",
    "coord": {
      "lat": 34.6913,
      "lon": 135.183
    },
    "country": "JP",
    "timezone": 32400
  }
}

webアプリ(Rails)実装版

weather.js
$(document).ready(function() {
  $.post("http://api.openweathermap.org/data/2.5/weather?id=" + '1850147' + "&appid=xxxxxxxxxxxxxxxxxxxxxxxxxxx&lang=ja&units=metric",
      function(json){
          $("#weather").html(json.weather[0].description);
          $("#humidity").html(json.main.humidity);
          $("#temp").html(Math.round(json.main.temp));
          switch (json.weather[0].main){
          case 'Clouds':
          $("#weatherMark").html("<img src='http://openweathermap.org/img/w/04d.png' >");
          break;
          case 'Snow':
          $("#weatherMark").html("<img src='http://openweathermap.org/img/w/13d.png' >");
          break;
          case 'Rain':
          $("#weatherMark").html("<img src='http://openweathermap.org/img/w/09d.png' >");
          break;
          case 'Clear':
          $("#weatherMark").html("<img src='http://openweathermap.org/img/w/01d.png' >");
          break;
          case 'Fog':
          $("#weatherMark").html("<img src='http://openweathermap.org/img/w/50d.png' >");
          break;
          case 'Mist':
          $("#weatherMark").html("<img src='http://openweathermap.org/img/w/50n.png' >");
          break;
          case 'Haze':
          $("#weatherMark").html("<img src='http://openweathermap.org/img/w/50d.png' >");
          break;
          default:
          $("#weatherMark").html("<img src='http://openweathermap.org/img/w/01n.png' >");
        }
    }
  );
});

_weather.html
<h2>東京の天気</h2>
<div>
    <span id = "weather" class="bold"></span>
</div>
<div>
    <span id = "weatherMark" class="bold"></span>
</div>
<p id="icon"></p>
<div>気温<span id = "temp"  class="bold"></span></div>
<div>降水確率<span id = "humidity" class="bold"></span> %</div>

参考サイト

http://www.torutsume.net/openweathermap/
https://qiita.com/nownabe/items/aeac1ce0977be963a740

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS LambdaでNokogiriを動かす

近々、AWS Lambdaを使って簡易的なクローラを作る予定があったので
Lambda上でNokogiriが使えるか 検証しました。

開発環境

ローカルの開発環境はMac、クローラはRubyで実装します。

ポイント

クローリングに必要となるGemである「Nokogiri」はネイティブライブラリを含むGemであるため、
Mac上でビルドしてAWSへデプロイすると「cannot load such file -- nokogiri」が発生します。

これを回避するため、AWS SAMを用いて雛形作成した上で、
sam build --use-container」を実行しAmazonLinux2のDockerコンテナ上でビルドを行いました。

AWS SAM CLIでHello World

AWS SAM CLIを使ってRubyのLambdaハンドラの雛形を作成します。
アプリの名前が実際に作ろうとしているクローラの名前になっていますので、適宜置き換えてください。

$ sam init --runtime ruby2.5 --name sample_app
$ cd sample_app
$ sam build
$ sam local invoke HelloWorldFunction --event event.json

参考: https://aws.amazon.com/jp/about-aws/whats-new/2018/04/aws-sam-cli-releases-new-init-command/

Nokogiriのインストール

今回はLambda上でスクレイピングツールであるNokogiriを動かす必要があります。
このGemはネイティブライブラリが含まれるため、ローカル環境(今回はMac)でインストールするとLambda上でエラーとなります。

が、AWS SAM CLIは以下コマンドでネイティブライブラリも含めて良い感じにビルドしてくれるコマンドを提供してくれています。便利ですね。

hello_world/Gemfile
source "https://rubygems.org"

gem "httparty"
gem "nokogiri" # 追記
gem "robotex"  # 追記

追記先はルートのGemfileではありませんので注意してください。
cannot load such file -- nokogiri が起こる原因となります。

Nokogiriを使うLambdaハンドラを実装

動作確認のためLambdaハンドラの処理も修正します。

hello_world/app.rb
require "bundler/setup"
Bundler.require

URL='https://www.google.com/'

def lambda_handler(event:, context:)
  charset = nil

  robotex = Robotex.new
  robotex.allowed?(URL)
  robotex.delay!(URL)

  html = open(URL) do |f|
    charset = f.charset
    f.read
  end

  doc = Nokogiri::HTML.parse(html, nil, charset)

  {
    statusCode: 200,
    body: {
      page_title: doc.title,
    }.to_json
  }
end

ローカルの開発環境で動作確認

ビルドして実行してみましょう。
GoogleのTOPページのタイトルが取得できました。

$ sam build --use-container
$ sam local invoke HelloWorldFunction --event event_file.json 
{"statusCode":200,"body":"{\"page_title\":\"Google\"}"}

デプロイ

AWS SAM CLIを使ってデプロイします。

$ sam package --output-template-file output.yaml --s3-bucket sample_app
$ sam deploy --template-file output.yaml --stack-name sample_app --capabilities CAPABILITY_NAMED_IAM

ただし、 sam package--template-file オプションを指定すると、vendor配下がアップロードされない問題が発生しました。 --template-file は設定しないようにしましょう。

AWSコンソールにログインして、Lambdaを実行すると無事成功しました。

Lambda_Management_Console.png

参考

https://aws.amazon.com/jp/blogs/developer/announcing-ruby-build-support-for-aws-sam-cli/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HomebrewをMacに初めてインストール。エラー発生。そして解決できました。

Rubyを少し勉強した初心者です。
今までCloud9でrubyを書いていました。

書籍 「プロを目指す人のためのRuby入門」を購入し、自身のMACにrubyをインストールしようとしたのですが、エラーでインストールできませんでした。
いろいろ調べた結果、インストールに成功したので、ここにその方法を記載します。

環境 Mac OS Mojave 10.14.5

Homebrewのインストール
https://brew.sh/index_ja.html
このサイトより
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
このキーワードをターミナルで実行しました。

自動的にインストールされていきます。

パスワードを聞かれるのでこのMacのパスワードを入れます。

Xcodeが必要とのことでポップアップが立ち上がりました。
XcodeをダウンロードするためにApp StoreからXcodeをダウンロードしました。
Xcodeのインストールは成功しました。
しばらくするとこのような文字が帰ってきます。

xcode-select: error: invalid developer directory '/Library/Developer/CommandLineTools'
Failed during: /usr/bin/sudo /usr/bin/xcode-select --switch /Library/Developer/CommandLineTools

エラーということで、Homebrewのインストールはできませんでした。

いろいろ調べましたが、回答がみつからず1日経過。

ターミナルの基礎を勉強し直しましたがこれといって解決できず。

エラーの文章をそのままネットで調べると下記のサイトへ到着。
https://discourse.brew.sh/t/xcode-select-error-invalid-developer-directory-library-developer-commandlinetools-failed-during-usr-bin-sudo-usr-bin-xcode-select-switch-library-developer-commandlinetools/693/40

そこに記載されていたのは、アップルのデベロッパーツールからcommand lineをインストールするようにとのことだったので、下記URLにアクセスし、『command line tools (macOS 10.14) for Xcode』をダウンロードし、インストール。
https://developer.apple.com/download/more/

再度ターミナルで、Homebrewのインストールのスクリプトを実行すると
無事にできました。

解決できて非常にうれしかったです。

誰かの参考になれば幸いです。

Rubyの基礎勉強がんばります。
皆さんよろしくお願い致します。
初めての投稿でした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Serverspec(Specinfra)にcheck_is_installed_by_pip2を実装しました

TL;DR

serverspecでは、pip2コマンドでモジュールをチェックする機能が実装されていないので、独自でリソースを実装し、以下のように利用できるようにする。

describe package(module_name) do
  it { should be_installed.by('pip2') }
  it { should be_installed.by('pip2').with_version(version) }
end

追記

check_is_installed_by_pip2を実装して、SpecinfraのGitHubにPull requestを出したらマージされたので、独自実装する必要がなくなりました。
https://github.com/mizzy/specinfra/pull/688

経緯

serverspecで、pip2, pip3のモジュールをそれぞれチェックしたかった。
チェック対象のサーバーは、pippip3コマンドではpip3が呼ばれ、pip2コマンドではpip2が呼ばれるようにしている。

しかし、serverspecでは、以下のように、pip、pip3コマンドでのチェックしかできない。

# check_is_installed_by_pip
it { should be_installed.by('pip') }
# check_is_installed_by_pip3
it { should be_installed.by('pip3') }

理由は、specinfraでは、check_is_installed_by_pipcheck_is_installed_by_pip3しか用意されていないからだ。

これではpip2のチェックができない。

そこで、check_is_installed_by_pip2を独自で用意し、serverspecで以下のように定義できるようにする。

it { should be_installed.by('pip2') }

独自リソースを作成する

packageリソースを拡張するため、check_is_installed_by_pip2を定義する。

参考:specinfra/lib/specinfra/command/base/package.rb

check_is_installed_by_pip2を定義

以下のファイルにcheck_is_installed_by_pip2を定義する。
spec/lib/extension/specinfra/command/base/package.rb

spec/lib/extension/specinfra/command/base/package.rb
class Specinfra::Command::Base::Package < Specinfra::Command::Base
  class << self
    def check_is_installed_by_pip2(name, version=nil)
      regexp = "^#{name}"
      cmd = "pip2 freeze | grep -iw -- #{escape(regexp)}"
      cmd = "#{cmd} | grep -w -- #{escape(version)}" if version
      cmd
    end
  end
end

spec/lib以下のファイルがautoloadされるようにする

spec/libの*.rbのファイルがautoloadされるように、以下の一行をspec/spec_helper.rbに追加する。

spec/spec_helper.rb
Dir[File.expand_path "#{__dir__}/lib/**/*.rb"].each{|f| require(f)}

specファイルで呼び出す

be_installed.by('pip2')とすることで、check_is_installed_by_pip2が呼び出される。

spec/sample/sample_spec.rb
require 'spec_helper'

describe package('boto3') do
  it { should be_installed.by('pip2') }
  it { should be_installed.by('pip2').with_version('1.9.156') }
end

追記

check_is_installed_by_pip2を実装して、SpecinfraのGitHubにPull requestを出したらマージされたので、独自実装する必要がなくなりました。
https://github.com/mizzy/specinfra/pull/688

参考にしたserverspec, specinfraのファイル

その他参考記事

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ActiveRecordのinclude、joins、eager_load、preloadの表まとめ

表題の件について完全に自分用の備忘録としてまとめる

メソッド JOIN associationのキャッシュ クエリ数 その他
joins LEFT JOIN × 1 JOINしたテーブルで絞込ができる。
eager_load LEFT OUTER JOIN 1 JOINしたテーブルで絞込ができる。
preload - 複数 preloadしたテーブルによって絞り込もうとすると、例外を投げる。
includes - - - eager_load か preloadの動きをする

それぞれ使いどころがあると思うが、個人的には使っていてeager_loadが全体的に一番使いやすいのではと思う。

参考:https://qiita.com/k0kubun/items/80c5a5494f53bb88dc58

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】バッテリーホイミ機能を実装しました

個人開発のWebアプリまちかどルート」v6.1に、名づけて《バッテリーホイミ機能》なるものを実装したときのメモです。

TL; DR

まちかどルートは

「道ばたのゴミを拾おう」
「お年寄りに席をゆずろう」
「〇〇に行って〇〇しよう」 など。

みんなの日常からちょっとした一日一善やおつかいなどをお題にしてサブクエストを作りクリアしあうことで世界がちょっと明るくなる、そんなリアルRPG系Webアプリです。

そしてユーザーには、レベルや経験値のほかHP (ヒットポイント:ドラクエのアレ)という属性値を持たせています。

Battery Status API

どうやらPCとAndroidのChrome限定のようですが、バッテリーの状態変化でJavaScriptを発火させることのできるBattery Status APIというものがあります。

今回はそれを応用してみました。

こんな流れです。
1. まちかどルートを開く
2. 充電を始める
3. APIが充電を検知したことを画面に表示
4. バッテリー残量値を取得
5. 「ホイミ!」ボタンを押す
6. バッテリー残量値をHPに加算してDBに保存

view / JavaScript

index.html.erb
<div id="posts">
 # ここに下記のJavaScriptからDOM要素が挿入されます
</div>
application.js
navigator.getBattery().then(function(battery) {
  // 充電を検知したら発火 
  battery.onchargingchange = function(){
    // 充電開始(true)のときだけ処理
    if (battery.charging === true) {
    // viewのid="posts"のところにDOM要素を挿入
    var element = document.getElementById("posts");
    element.insertAdjacentHTML("afterbegin", "
       <p>充電を検知</p>
       <form action='battery' accept-charset='UTF-8' method='get'>
       <input name='utf8' type='hidden' value='✓'>
          <input type='text' id='battery' name='battery' readonly=''>
          </input>
          <button name='button' id='battery_btn' type='submit' disabled>
            ホイミ!
          </button>
       </form>
     ");
    }
  }
  // 充電の残量が変化したら発火
  battery.onlevelchange = function(){
    // 充電の残量値を入力フォームに表示
    document.getElementById( "battery" ).value = battery.level * 100;
    // 入力フォームのdisabledを無効にして「ホイミ!」ボタンを押せるようにする
    document.getElementById( "battery_btn" ).disabled = "";
  }
 });

controllerでホイミ!

posts_controller.rb
    def battery
        # viewから送られてきたバッテリー残量値をHPに加算してDB保存
        if params[:battery] != nil
            battery = params[:battery]
            @current_user.hp += battery.to_i
            @current_user.save
            flash[:notice] = "HPが回復しました!"
            redirect_to root_path
        else
            flash[:error] = "呪文が失敗しました!"
            redirect_to root_path
        end
    end

ルーティング

config\routes.rb
  get 'battery' , as: 'battery', to: 'posts#battery'

あとがき

とてもザックリな感じになってしまいましたが、要点は上記のとおりです。HPが残り少なくなったらバッテリーを充電して「ホイミ!」

けっこう楽しいです。

オープンソース開発用のリポジトリを公開しました。今回の件、こちらにもアップしてあります。
https://github.com/west2538/machiroute_oss

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Sonic Piによる音楽生成

概要

コードを書くことで音楽を生成できるシステム環境「Sonic Pi」(ソニックパイ)のノウハウをストックしていきます。
学び始めたばかりの初心者なので、学習してアップデートがあれば随時、加筆修正をしていく予定です。

Sonic Piとは

Rubyの文法をベースとしたコーディングで音楽を作って鳴らせるシステム環境。
以下の公式サイトからインストールしただけですぐに使えます。

・公式サイト
Sonic Pi - The Live Coding Music Synth for Everyone
https://sonic-pi.net/

DAWとどう違う?

LogicやCUBASEなどのいわゆるDAWと比較した際のコードによる音楽生成の特徴は以下のようなものがあると感じます。

  • 繰り返しが主体
    └例えば、単純に何かのメロディーを入力していくという場合、DAWであればピアノロールで意図した音程、場所、長さなどをコントルールして打ち込みことができますが、コーディングによる入力はそれなりの手間がかかります。
    反面、繰り返しの処理は得意なので、ループ構造をメインとして、その中身に色々な工夫を足していくことで音楽を生成するということが基本路線かなと感じます。

  • アルゴリズムを駆使できる
    └プログラミングで音楽を生成するので、何らかのアルゴリズムを作ってそれをループさせることで、ミニマルミュージックのような規則性のある音楽を少ない工数で作れる可能性がある。

  • 乱数や変数を駆使して動的な音楽を作れる
    └プログラムで乱数を扱うことで、繰り返しの度に生成される音やエフェクトかかり方が変わるような「変化し続ける音楽」を作れるというのは大きな特徴かなと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Sonic Piによる音楽生成の基礎

概要

コードを書くことで音楽を生成できるシステム環境「Sonic Pi」(ソニックパイ)のノウハウをストックしていきます。
私も学び始めたばかりの初心者なので、学習して知識がアップデートされたら加筆修正をしていく予定です。

Sonic Piとは

Rubyの文法をベースとしたコーディングで音楽を作って鳴らせるシステム環境。
以下の公式サイトからインストールしただけですぐに使えます。

・公式サイト
Sonic Pi - The Live Coding Music Synth for Everyone
https://sonic-pi.net/

DAWとどう違う?

LogicやCUBASEなどのいわゆるDAWと比較した際のコードによる音楽生成の特徴は以下のようなものがあると感じます。

  • 繰り返しが主体
    └例えば、単純に何かのメロディーを入力していくという場合、DAWであればピアノロールで意図した音程、場所、長さなどをコントルールして打ち込みことができますが、コーディングによる入力はそれなりの手間がかかります。
    反面、繰り返しの処理は得意なので、ループ構造をメインとして、その中身に色々な工夫を足していくことで音楽を生成するということが基本路線かなと感じます。

  • アルゴリズムを駆使できる
    └プログラミングで音楽を生成するので、何らかのアルゴリズムを作ってそれをループさせることで、ミニマルミュージックのような規則性のある音楽を少ない工数で作れる可能性がある。

  • 乱数や変数を駆使して動的な音楽を作れる
    └プログラムで乱数を扱うことで、繰り返しの度に生成される音やエフェクトかかり方が変わるような「変化し続ける音楽」を作れるというのは大きな特徴かなと思います。

音を鳴らす

use_bpm 120  # BPMを指定する。デフォルトは60。
use_synth :piano  # 使用するシンセを選択。デフォルトは「:beep」。
play 60  # 指定のノート番号の音を鳴らす。60はドの音。
sleep 1  # 休符を指定。1拍休み。
play :C4  # 英語の音名で音程を、後ろの数値でオクターブを指定。「:」が必要。
sleep 2 # 2拍休み。
play :Cs4 # Cシャープを鳴らす。
sleep 0.5 # 半拍休み。
play :Db4 # Dフラットを鳴らす。
sleep 0.25 # 16分休み。
play [:C4, :E4, :G4] # []で囲うことで音を同時に鳴らせる。Cのコードを鳴らしている。

ノート番号と音名の対応は以下を参照。
ノート番号を覚えるのは大変なので基本的には英語表記で書くとよいと思います。

・MIDIノート番号と音名、周波数の対応表
http://www.asahi-net.or.jp/~HB9T-KTD/music/Japan/Research/DTM/freq_map.html

イテレーション(反復)

# 4回繰り返す
4.times do
  play :C4
  sleep 1
end

[回数].times ~ end
で囲うことで反復処理を行える。

# 反復の中に反復をネスト
4.times do
  2.times do
    play :C4
    sleep 1
  end
  2.times do
    play :D4
    sleep 1
  end
end

このように、繰り返しの中に入れ子で繰り返しを入れることもできる。

ループ

#  無限ループ
loop do
  play :C4
  sleep 1
end
play :D4  # 上でループが動いているので実行されない。 

loop do ~ end
で囲うことで無限にループを繰り返します。
基本的にStopボタンを押すまで永遠と繰り返し続けます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む