Madogiwa Blog

主に技術系の学習メモに使っていきます。

Git:cherry-pickを使って特定のコミットだけをブランチに反映する

ブランチを切り忘れて、複数の対応が含まれてしまったブランチから特定の対応を含んだブランチを作成してプルリクを作りたくてcherry-pickについて調べてのでメモφ(..)

やりかた

前提

今回は下記のようなdevelopブランチに下記のようなコミットログがあった際にead2ed604e6e14を指定して、reviewブランチに反映させmasterブランチにプルリクを出すケースを想定して手順をやってみますφ(..)

f:id:madogiwa0124:20171228093635j:plain

特定のコミットをreviewブランチに反映してみる

特定のコミットをreviewブランチに反映するまでの手順概要は下記のとおりです。

  1. masterをチェックアウト
  2. reviewブランチをmasterから作成
  3. cherry-pickを使って特定のコミットを反映

ポイントは、cherry-pickです。cherry-pickを使うことでコミットを指定し、反映することが出来ます!

git cherry-pick コミットハッシュ

実際に私が作業したコンソールのログが下記です。

$ git cherry-pick ead2ed6
[review 67bbc40] rename snake_case username,schoolname
 Date: Mon Dec 25 22:39:46 2017 +0900
 5 files changed, 17 insertions(+), 11 deletions(-)
 create mode 100644 db/migrate/20171225133337_change_clumn_name_to_users.rb
$ git cherry-pick 04e6e14
 Date: Tue Dec 26 22:30:30 2017 +0900
 1 file changed, 1 insertion(+), 1 deletion(-)

masterにプルリクを出してみる

先程作成したreviewブランチをgit pushし、masterにプルリクを出してみます!

f:id:madogiwa0124:20171228092954p:plain

特定のコミットのみを含んだプルリクが作成できました(._.)

おわりに

cherry-pickを使うと特定のコミットを反映することが出来るので、コードレビューの際に主要なコミットだけを含んだブランチを作成してプルリクを出す等、色々とはかどりそうだなと思いましたφ(..)!

参考

backlog.com

Ruby on Rails:ModelのプロパティにEnumを使う※おまけ:ラジオボタンでEnumを扱う

Railsでステータス等の特定の値のリストを用いて値設定を行う場合にはEnumを使うと便利なので、手順をメモしておきますφ(..)

ちなみにEnumはRails4.1から導入されたActiveRecordの機能なので、Ruby単独では使えない点に注意してください(._.)
RubyEnumっぽいことやるには下記に手順をまとめているので、参考にしてください:D

madogiwa0124.hatenablog.com

手順

テーマ:性別(男女)をEnumで設定する

今回は、性別というプロパティについて男と女という値をEnumで管理するケースを想定しました。
既存のUserモデルについて、性別のプロパティを追加し、それをEnumで管理出来るようにします:D

実装

テーブル定義の変更

まずは既存のUserモデルについて性別(sex)プロパティを追加します。ポイントはInteger型でプロパティを定義することですφ(..)

rails g migration add_sex_to_users sex:integer
class AddPropertiesToUsers < ActiveRecord::Migration[5.1]
  def change
    add_column :users, :sex, :integer
  end
end

実装

テーブル定義を変更後、Enumをモデルに定義していきます。今回は1:男2:女という定数をEnumで管理していきます。
Enumは、enum プロパティ名: { 値1: 1, 値2: 2 }という形でモデルに定義します(._.)

class User < ApplicationRecord
  
  # 省略

  enum sex: { man: 1, woman: 2 }
  
  # 省略
  
end

実際に登録されたデータを見てみると男は1女は2で表示されます。

# DB上は、モデルで定義した数字で設定される
e-navigator_development=# select id,username,sex from users;
 id | username | sex
----+----------+-----
  1 | hogehoge |   2
  3 | fuga_1   |   1
(2 rows)

登録したデータをアプリケーション上で確認してみると、モデルで定義した値womanとなります。

irb(main):001:0> user.id
=> 1
# モデルで定義された値"woman"が表示される。
irb(main):002:0> user.sex
=> "woman"
# Enumの一覧を確認
irb(main):003:0> User.sexes
=> {"man"=>1, "woman"=>2}

おまけ:Enumのプロパティをラジオボタンとして表示する

Enumで定義したプロパティはテキストボックスではなく、ラジオボタンやセレクトボックスで定義したいので、今回はラジオボタンで表示するコードを下記に示しましたφ(..)

  <div class="field">
    <%= f.label :sex %><br />
    <%= f.label :man %>
    <%= f.radio_button :sex, :man %>
    <%= f.label :woman %>
    <%= f.radio_button :sex, :woman %>
  </div>

Enumを使うとマジックナンバーソースコードに記述しなくても良くなるので、可読性があがるので定数のリストを扱うプロパティには積極的にEnumを活用していきたいですね!

参考

qiita.com

Ruby on Rails:モデルに独自のバリデーションを実装する

Railsはvalidationを使って必須等の色々なデータの制約を付与することが出来ますが、それらに該当しない独自のvalidationを付与する方法をメモφ(..)

手順

テーマ:生年月日の過去日を不可にする

今回は、生年月日に過去日が入力された際にエラーとなるように独自のvalidationを追加していきます。

f:id:madogiwa0124:20171224162947p:plain

前提条件

生年月日はUserモデルに属し、データ構造は下記となっていることを想定します。

論理名 物理名
生年月日 birthday date

独自のバリデーションを実装する

独自のバリデーションを追加するのは意外と簡単でした!Userモデルにメソッドを定義し、それをvalidateで設定するだけです:D

class User < ApplicationRecord

  # 省略

  # validateに定義したメソッドを設定
  validate :birthday_cannot_be_in_the_future
  
  # 生年月日の未来日のチェックメソッド
  def birthday_cannot_be_in_the_future
    # 生年月日が入力済かつ未来日(現在日付より未来)
    if birthday.present? && birthday > Date.today
      # エラー対象とするプロパティ(birthday)とエラーメッセージを設定
      errors.add(:birthday, "can not specify your future date as your birth date.")
    end
  end

end

参考

railsguides.jp

以上

アイデア:本を読んだので個人開発しているサービス「Moook」をもう一度考えてみる。

最近、サービス考案の書籍を読んだので、 その学びの振り返りも兼ねて自分の開発中のサービス 「いつもの更新、いつでも確認 | Moook」を再検討してみるφ(..)

moook.herokuapp.com

↓ちなみに読んだ本はこれ・ω・

ITエンジニアとして生き残るための創造的発想術

ITエンジニアとして生き残るための創造的発想術

サービスの再検討

まずは「目的・目標」を考える!

目的や目標を明確化し、それを簡単な文章にする 何かに迷ったときは、ここに立ち返る

Moookに当てはめてみると下記のような感じでしょうかφ(..)

  • 目的
    • 快適なWeb漫画ライフを実現する
  • 目標
    • 各サイトに散らばったWeb漫画を1つのページで管理出来る
    • Web漫画の更新を漏らさず、更新したタイミングで確認出来る

目的・目標達成に必要なもの「issue」を考える!

issueは「現時点で何を考え、何に答えを出すべきか」を明確にしたもの 考え抜かれた疑問形になっている方がよい

Moookに当てはめると下記のような形でしょうか。

  • issue
    • 様々なサイトに散らばったWeb漫画を一括で管理するにはどうすれば良いか?
    • Webページの更新を見逃さないようにするにはどうすれば良いか?

判断基準及び測定指標を決める

その考え、行動が上手くいっているかどうかの判断基準を明確にしましょう 客観的に誰が聞いても同じ意味になるような言葉で定義しましょう

Moookに当てはめると下記のような形でしょうか。

  • 様々なサイトに散らばったWeb漫画を一括で管理するにはどうすれば良いか?
    • 1つのWebアプリケーション内で各Web漫画を管理出来れば、一括で管理出来たものとする。
  • Webページの更新を見逃さないようにするにはどうすれば良いか?
    • Web漫画の更新後1時間以内に通知を遅れれば、更新を見逃さない状態となっているものとする。

やり方を考えてみる

一旦は、やり方の手段は問わずに考える

Moookに当てはめると下記のような形でしょうか。※今回は現在のやり方に注目しました。

  • 様々なサイトに散らばったWeb漫画を一括で管理するにはどうすれば良いか?
    • 1つのWebアプリケーション内で各Web漫画を管理出来れば、一括で管理出来たものとする。

→Web漫画のページを登録してもらい、アプリケーション上で複数のWeb漫画を管理出来るWebアプリケーションを開発する。

  • Webページの更新を見逃さないようにするにはどうすれば良いか?
    • Web漫画の更新後1時間以内に通知を遅れれば、更新を見逃さない状態となっているものとする。
    • Web漫画のHTMLに何らかの変更が発生した場合に更新されたとみなす。

→Webスクレイピングを用いて1時間おきに該当ページのHTMLを取得し、差分があれば更新を通知する。

周りの人の意見を聞きながら改善する

ユーザーからの意見を製品に反映するとあるのですが、全然出来ていません(T_T)
完全に私個人のWeb漫画管理ツールとなっています。。。

moook.herokuapp.com

私個人としては、そこそこ満足しているのですが、あんまりデザインとかは綺麗じゃないですけど、Web漫画が好きで更新管理に苦労している方がいれば、ユーザーを作成しなくてもページの一覧等は見れますので、使ってみて感想を教えてくださいm( )m
(β版のため、不具合や急な仕様変更等が発生する可能性はございます。。。)

メールアドレスを登録すると、1時間おきに更新を以下のような形で送ってくれるので結構便利だと私は思っています・・・。

Moookからのお知らせ

あなたのお気に入りのページが更新されました! 更新されたページはこちらです。

-------------

* マギ シンドバッドの冒険:http://urasunday.com/magi/index.html
* 侠:http://enjoykorea.web.fc2.com/kyoindex.htm
* 愚連街:http://urasunday.com/gurengai/index.html
-------------

********************************

発行元

いつもの更新、いつでも確認 Moook URL:https://moook.herokuapp.com/

********************************

画面だとこんな感じです(._.)※赤い目印が更新ページです。

f:id:madogiwa0124:20171220211551p:plain

※ちなみに、HTMLで差分を比較しているため、ランキング等によって毎日該当ページに変更が入る場合、更新が正常に検知出来ない場合がありますので、お問い合わせ頂ければと思います。。。

おわりに

いまさら考えると当時は汎用的なツールにした方がいいんじゃないか?という考えもあり、機能は揃っているけど目標達成を意識したデザインや説明になっていないような気がしました。。。

自分ですごく使っているので、これから色々とデザイン等も勉強してより良いツールにして行きたいですね。

書籍の中では、実際のサービスの事例が記載されていたり、もっと深い説明があるので、Webサービスを個人で作っている方にはサービスを見直す良いきっかけになるんじゃないかなと思いました!

興味のある方は、読んでみて下さいφ(..)

Ruby:プログラム内で出てくる記号の意味(<、::、&、@、$、**、<<-)

Rubyで書かれたプログラムを見た時に、記号の意味が良くわからないなぁと思うことがあったので、整理してみました(._.)!

記号一覧

<:継承

<は「大なり小なり」で使われるイメージが強いですがRubyではClassの継承にも使われます。

class Hoge
  def hello
    puts 'hello'
  end
end

# Hogeを継承したクラス
class Fuga < Hoge
  def bye
    puts 'good bye'
  end
end

# Hogeを継承しているため、helloメソッドが呼び出せる
puts Fuga.new.hello

:::定数の参照、クラス内クラスの参照

::は、ModuleやClassに定義された定数・Classの呼び出しを意味します。

class Hoge
  # 定数
  CONST_STRING = "定数"
  # Class内に定義されたClass
  class Piyo
    def bye
      'good bye!!'
    end
  end
end

# Module内に定数を定義
module Fuga
  FOO = 'Foo'
  BAR = 'BAR'
end

# Class内に定義した定数の呼び出し
puts Hoge::CONST_STRING
# Class内に定義したClassの呼び出し
puts Hoge::Piyo.new.bye
# Moduke内で定義した定数の呼び出し
puts Fuga::FOO

&:&演算子、ぼっち演算子

&&&としてif文等の中でAND演算子として使われることが多いですが、&演算子と呼ばれる:&の形式や、ぼっち演算子と呼ばれる.&の形式で使わることもあります。

# === &演算子 ===
num = [0, 1, 2]
# 下記は、num.map{ |n| n.to_s }と同じ
p num.map(&:to_s)

# === ぼっち演算子 ===
hoge = nil
# NoMethodError
p hoge.fuga
# 存在しないmメソッドの場合nilが返却される
p hoge&.fuga

@:インスタンス変数、クラス変数

Class内で使用される@付き変数は、インスタンス変数を表します。また@@付きの変数はクラス変数を表します。

class Hoge
  @@piyo = "piyo"
  def initialize(str)
    @var = str
  end
  def piyo
    @@piyo
  end
  def var
    @var
  end
end

#<Hoge:0x000055e6bbff9a40 @var="hoge">
p hoge = Hoge.new("hoge")
#<Hoge:0x000055e6bbff9888 @var="fuga">
p fuga = Hoge.new("fuga")
# 下記からは、両方"piyo"が返却される※クラス変数で定義された値はインスタンス間で共有される
p hoge.piyo
p fuga.piyo

$:グローバル変数

$が付与された変数がグローバル変数となり、実行中のプログラムのどこでも参照可能な変数となります。

$var = "global_var"

class Hoge
  def self.global_var
    # こんなところでも参照できます。
    $var
  end
end

# "global_var"
p Hoge.global_var

** : 累乗

**演算子として使うと累乗を取得することが出来ます。

# 3の3乗である27が返却される
p 3 ** 3

<<-:ヒアドキュメント

<<-で定義された文字列は、ヒアドキュメントと呼ばれる複数行の文字列を扱うことが出来るものです。

hoge = <<-EOF
複数行の
文字列を
扱えます
EOF

# "複数行の\n文字列を\n扱えます\n"
p hoge

参考

docs.ruby-lang.org

以上です、また何か気づいたら追記しようと思いますφ(..)

Ruby:はてなブログAPIを使って記事の一覧をJSON形式で取得する

最近、技術系の記事はQiitaへの移行を考えているのですが、過去の記事の移行がめんどくさいなぁ。。。と思っていたので、記事の一覧をはてなブログAPIで取得するスクリプトを作ってみたので、その手順をメモしておきますφ(..)

ちなみに成果物は下記に公開してます。ソースコードだけ見たいという人は下記を、ご参照ください。

はてなブログAPIを使っての記事一覧をJSONで取得する · GitHub

やり方

APIKEYとルートエンドポイントを取得する

まずは、自分のブログにアクセスするためのAPIKEYとルートエンドポイントを取得します。それぞれの役割は下記の通りです。

名前 役割
APY KEY APIで自身のブログにアクセスする際のパスワード
ルートエンドポイント APIへRESTでアクセスする際に指定するURL

APIKEY及びルートエンドポイントは、自分のブログの詳細設定画面で確認することが出来ます。

https://blog.hatena.ne.jp/アカウント/ブログID.hatenablog.com/config/detail

f:id:madogiwa0124:20171210224903p:plain

記事を取得してみる

それでは、実際にはてなブログAPIを使って記事を取得していきます。
基本的な流れは下記の通りです。

  1. ルートエンドポイント/entryにGETリクエストを作成
  2. ベーシック認証でのアカウント情報をGETリクエストに付与
  3. GETリクエストを発行
  4. レスポンスボディに設定された記事を取得

実際のソースコードと解説は下記の通りです。

require 'uri'
require 'net/http'

# ルートエンドポイントからURLを生成
uri = URI(ROOT_END_POINT)
# GETリクエストを作成
req = Net::HTTP::Get.new(uri)
# ベーシック認証でアカウント情報を付与
req.basic_auth(USER_ID, APY_KEY)
# HTTPリクエストを送付
res = Net::HTTP.start(uri.host,
                      uri.port,
                      use_ssl: true) { |http| http.request(req) }
# 記事情報を取得(XML形式)
res.body

記事のタイトルと本文を取得する

取得結果からタイトルと本文といった特定部分を取得するためには、取得したXML形式の記事情報を解析する必要があります。
今回はNokogoriを使用してXMLを解析してタイトルと本文を取得しました。Nokogiriの説明は下記の記事にまとめておりますので、そちらを参照してください。

madogiwa0124.hatenablog.com

実際にレスポンスボディに設定される記事(XML形式)は下記のような形になります。

<entry>
  <id>tag:blog.hatena.ne.jp,2013:blog-madogiwa0124-6653812171402746641-8599973812325431185</id>
  <link rel="edit" href="https://blog.hatena.ne.jp/madogiwa0124/madogiwa0124.hatenablog.com/atom/entry/8599973812325431185"/>
  <link rel="alternate" type="text/html" href="http://madogiwa0124.hatenablog.com/entry/2017/12/10/232320"/>
  <author><name>madogiwa0124</name></author>
  <title>タイトル</title>
  <updated>2017-12-10T23:23:20+09:00</updated>
  <published>2017-12-10T23:23:20+09:00</published>
  <app:edited>2017-12-10T23:23:20+09:00</app:edited>
  <summary type="text">本文</summary>
  <content type="text/x-markdown">本文</content>
  <hatena:formatted-content type="text/html" xmlns:hatena="http://www.hatena.ne.jp/info/xmlns#">&lt;p&gt;本文&lt;/p&gt;</hatena:formatted-content>
  <app:control>
    <app:draft>no</app:draft>
  </app:control>
</entry>

そのため、entry配下のtitlecontentを取得するには下記のようなコードで行うことが出来ます。今回はタイトルと本文をHash形式で取得出来るようにしてみました。

# Nokogiriで取得したXMLをパース
entry = Nokogiri::XML.parse(res.body, nil, 'utf-8')
# タイトルの一覧を取得
titles = entry.css('entry title').map(&:text)
# 本文の一覧を取得
contents = entry.css('entry content').map(&:text)
# Hash形式でタイトルと本文のリストを作成
titles.length.times do |i|
  entry_hash_list << { title: titles[i], content: contents[i]}
end

全ての記事を取得する

はてなブログAPIでは、1回のレスポンスで10記事までしか返却出来ないため、全ての記事を取得するためには次の記事の一覧が取得出来るURLを取得し、リクエストを複数回送らないといけません。

次の記事の一覧へのURLは、レスポンスボディに下記のような形で設定されます。

<link rel="next" href="https://blog.hatena.ne.jp/madogiwa0124/madogiwa0124.hatenablog.com/atom/entry?page=1509279003" />

上記XMLを解析し、URL部分を取得するコードは下記の通りです。

# 次の記事一覧へのリンク部分を取得
link_urls = entry.css("link[rel^='next']")
# リンク部分からURLの文字列を取得※取得でいなかった場合はnil を設定
next_url = link_urls.empty? ? nil : link_urls.attribute('href').value

上記で取得したURLに再度記事を取得してみるで記載した処理を行えば、次の記事の一覧が取得することが出来るので、次のページのURLが取得出来なくなるまで処理を繰り返せば全ての記事を取得することが出来ます。

全ての記事のタイトル、本文、カテゴリをJSON形式で取得する

下記は私が作成したはてなブログAPIを使った記事のタイトル、本文、カテゴリを取得するスクリプトです。

gist.github.com

参考

はてなブログAtomPub - Hatena Developer Center

cartman0.hatenablog.com

以上です

Ruby:seleniumを使って動的ページをスクレイピングしてみる

以前、Nokogiriを使ったスクレイピングについてブログを書きましたが、JavaScriptで初期表示時にページを動的に変更しているページ等上手く値を取得するこが出来ません。。。
※httpレスポンスに設定されたHTMLを取得しているため

madogiwa0124.hatenablog.com

動的ページのスクレイピングには、Webブラウザでの操作を自動化できるseleniumが便利です。

github.com

はじめに

今回は、Google翻訳の結果をスクレイピングするツールを作ったので、それをもとにseleniumの使い方を書いていきます。 ちなみにソースコードRubyです。

使い方

環境構築

seleniumを使うのに必要なのは、selenium用のgemとWebDriverです。今回はGoogleChromeのドライバをインストールしてます。

# selenium用のgemをインストール
$ gem install selenium-webdriver
# Chrome用のドライバをインストール
$ brew install chromedriver

これで準備完了です!

実際にスクレイピングしてみる

スクレイピングの基本的な手順は、下記の通りです。

  1. ドライバーの起動
  2. URLへアクセス
  3. 要素を取得

実際にGoogle翻訳の結果を取得するソースコードが以下のものです。

# Google翻訳にアクセスして、翻訳結果を取得
# target_lang:翻訳前言語、result_lang:翻訳後言語、word:翻訳対象文字列
def get_translate_result(target_lang, result_lang, word)
  # URL生成
  url = "#{GOOGLE_TRANSLATE_URL}##{target_lang}/#{result_lang}/#{word}"
  # ドライバーの起動
  driver = Selenium::WebDriver.for :chrome
  # URLへアクセス
  driver.navigate.to(url)
  # タイムアウト値の設定
  wait = Selenium::WebDriver::Wait.new(timeout: 10)
  begin
    # タイムアウトになるまで、翻訳結果部からテキスト情報を取得
    wait.until{ html = driver.find_element(id: 'result_box'); html.text }
  rescue RuntimeError => e
    # ERROR処理、メッセージを出力してドライバーを終了
    puts e.message
    driver.quit
  end
end

ヘッドレスブラウザを使ってみる

ヘッドレスブラウザを使うと、ブラウザを起動せずにスクレイピングを行うことができます。
先程のソースコードをヘッドレスブラウザを使用するように書き替えたものが以下です。

# Google翻訳にアクセスして、翻訳結果を取得
def get_translate_result(target_lang, result_lang, word)
  # URL生成
  url = "#{GOOGLE_TRANSLATE_URL}##{target_lang}/#{result_lang}/#{word}"
  # オプションの生成(ヘッドレスブラウザで動作するように)
  options = Selenium::WebDriver::Remote::Capabilities
  options = options.chrome('chromeOptions' => { args: ['--headless'] })
  # ドライバーの起動
  driver = Selenium::WebDriver.for :chrome, desired_capabilities: options
  # URLへアクセス
  driver.navigate.to(url)
  # タイムアウト値の設定
  wait = Selenium::WebDriver::Wait.new(timeout: 10)
  begin
    # タイムアウトになるまで、翻訳結果部からテキスト情報を取得
    wait.until{ html = driver.find_element(id: 'result_box'); html.text }
  rescue RuntimeError => e
    # ERROR処理、メッセージを出力してドライバーを終了
    puts e.message
    driver.quit
  end
end

ソースコードの完全版は下記です。

github.com

参考

qiita.com

qiita.com