マッスル・メモリー

筋肉エンジニアのブログ

Google Analytics APIを使用しページごとのセッション数を取得

この記事に書かれていること

背景

コーポレートサイトのブログ記事の人気ランキングを実装のために、ブログの記事ごとのセッション数を取得する必要がありました。

そこで、Google Analytics APIを使用しデータを取得し、実装しました。

Google Analyticsでできること。

Google Analyticsは、シェアNo.1のアクセス解析ツールで、Googleアナリティクスを使えば、Webサイト運営に必要なデータはほぼ見ることができます。

  • リアルタイムの利用状況
  • ユーザーの基本属性
  • ユーザーがどこから来たか
  • サイト内でのユーザーの動き
  • Webサイトの成果

などです。

実装

準備

まず必要な登録や、設定、情報を取得する必要がありますが、 以下の記事をご参照ください。

また今回の実装においては、大変参考になった記事です🙇‍♂️

https://qiita.com/Sa2Knight/items/0b61efc2be0bdf33ec48

https://qiita.com/ryota-sasabe/items/a5efd2aac244cfcce5c7

全体像

lib/tasks/google_analytics.rakeに具体的な処理、

lib/google_analytics_reporting.rbにGoogleAnalyticsReportingクラスを定義、

必要な情報、秘匿情報は各種ファイルに記述しました。

config/environments/

config/credentials

# lib/tasks/google_analytics.rake
namespace :google_analytics do
  desc 'update_articles_sessions_count_with_Google_Analytics'
  task update_articles_sessions_count: :environment do

    reporting = GoogleAnalyticsReporting.new(
      '2019-01-01',
      'today',
      'ga:uniquePageviews',
      'ga:pagePath',
    )
    reporting = reporting.get_reporting
    articles_pages = reporting.rows.select { |row| row.dimensions.first.match(%r{articles/.+}) }

    articles = []
    Article.published.find_each do |article|
      article = article.attributes
      article['category'] = Article.categories[article['category']]
      article['public_status'] = Article.public_statuses[article['public_status']]

      articles_page = articles_pages.find { |articles_page| articles_page.dimensions.first.gsub("/articles/",  "") == article['uid'] }
      next if articles_page.nil?
      article['sessions_count'] = articles_page.metrics.first.values.first.to_i
      articles << article
    end

    if articles.present?
      Article.upsert_all(articles)
      Article.reindex
    end
  end
end
# lib/google_analytics_reporting.rb
class GoogleAnalyticsReporting
  require 'google/apis/analyticsreporting_v4'
  SCOPE = ['https://www.googleapis.com/auth/analytics.readonly'].freeze
  def initialize(start_date, end_date, metric_type, dimension_type)
    @analytics = Google::Apis::AnalyticsreportingV4
    @client = @analytics::AnalyticsReportingService.new
    ENV['GOOGLE_PRIVATE_KEY'] = Rails.application.credentials.google_analytics[:private_key]
    ENV['GOOGLE_CLIENT_EMAIL'] = Rails.application.credentials.google_analytics[:client_email]
    ENV['GOOGLE_PROJECT_ID'] = Rails.application.credentials.google_analytics[:project_id]
    @start_date = start_date
    @end_date = end_date
    @metric_type = metric_type
    @dimension_type = dimension_type

    @client.authorization = Google::Auth::ServiceAccountCredentials.make_creds(
      scope: SCOPE
    )
  end

  def get_reporting
    request = build_request
    response = @client.batch_get_reports(request)
    response.reports.first.data
  end

  def build_request
    date_range = @analytics::DateRange.new(start_date: @start_date, end_date: @end_date)
    metric = @analytics::Metric.new(expression: @metric_type)
    dimension = @analytics::Dimension.new(name: @dimension_type)
    @analytics::GetReportsRequest.new(
      report_requests: [@analytics::ReportRequest.new(
        view_id: Rails.configuration.google_analytics_view_id,
        metrics: [metric],
        dimensions: [dimension],
        date_ranges: [date_range]
      )]
    )
  end
end

コード解説

# lib/google_analytics_reporting.rb
class GoogleAnalyticsReporting
  require 'google/apis/analyticsreporting_v4'
  SCOPE = ['https://www.googleapis.com/auth/analytics.readonly'].freeze
  def initialize(start_date, end_date, metric_type, dimension_type)
    @analytics = Google::Apis::AnalyticsreportingV4
    @client = @analytics::AnalyticsReportingService.new

まずクラスので必要な情報を定義していきます。

# lib/google_analytics_reporting.rb
    ENV['GOOGLE_PRIVATE_KEY'] = Rails.application.credentials.google_analytics[:private_key]
    ENV['GOOGLE_CLIENT_EMAIL'] = Rails.application.credentials.google_analytics[:client_email]
    ENV['GOOGLE_PROJECT_ID'] = Rails.application.credentials.google_analytics[:project_id]

は、認証のために必要な情報で、参考にしたqiitaにはjsonファイルで記述されていますが、環境変数に定義しても認証が行えるので 今回はこのように定義しました。 中身は、秘匿情報、環境ごとに取得したいのでcredentialsに記述しました。

# lib/google_analytics_reporting.rb
    @start_date = start_date
    @end_date = end_date
    @metric_type = metric_type
    @dimension_type = dimension_type

データの取得の期間、メトリックとディメンション(条件のようなもの)を引数で渡せるように定義しました。 将来、異なる情報を取得したいときにもこのクラスを使用できます。

# lib/google_analytics_reporting.rb
    @client.authorization = Google::Auth::ServiceAccountCredentials.make_creds(
      scope: SCOPE
    )
  end

initializeが呼び出されたときに認証を行なっています。

# lib/tasks/google_analytics.rake
    reporting = GoogleAnalyticsReporting.new(
      '2019-01-01',
      'today',
      'ga:uniquePageviews',
      'ga:pagePath',
    )

rake taskにてクラスをインスタンス化させます。 その際に、今回取得したいデータの条件を引数として渡してあげます。

# lib/tasks/google_analytics.rake
    reporting = reporting.get_reporting
    articles_pages = reporting.rows.select { |row| row.dimensions.first.match(%r{articles/.+}) }

get_reportingメソッドでGAからのデータを取得します。中身は、

# lib/google_analytics_reporting.rb
def get_reporting
    request = build_request
    response = @client.batch_get_reports(request)
    response.reports.first.data
  end
# lib/google_analytics_reporting.rb
  def build_request
    date_range = @analytics::DateRange.new(start_date: @start_date, end_date: @end_date)
    metric = @analytics::Metric.new(expression: @metric_type)
    dimension = @analytics::Dimension.new(name: @dimension_type)
    @analytics::GetReportsRequest.new(
      report_requests: [@analytics::ReportRequest.new(
        view_id: Rails.configuration.google_analytics_view_id,
        metrics: [metric],
        dimensions: [dimension],
        date_ranges: [date_range]
      )]
    )
  end

となっており、先ほど引数で渡した条件からデータを取得しています。 その際にview_idを渡していますが、これはconfig/environments/以下の環境ごとのファイルにそれぞれ定義しています。

# lib/tasks/google_analytics.rake
    articles = []
    Article.published.find_each do |article|
      article = article.attributes
      article['category'] = Article.categories[article['category']]
      article['public_status'] = Article.public_statuses[article['public_status']]

      articles_page = articles_pages.find { |articles_page| articles_page.dimensions.first.gsub("/articles/",  "") == article['uid'] }
      next if articles_page.nil?
      article['sessions_count'] = articles_page.metrics.first.values.first.to_i
      articles << article
    end

    if articles.present?
      Article.upsert_all(articles)
      Article.reindex
    end

ここで、dbに存在する。ブログ記事のデータを取得し、GAと同じuid(url)のもののsessions_countを更新させる処理を行なっています。 レコード数は少ないとは思いますが、大量にデータを扱っても良いようにfind_eachを使用しました。 https://blog.toshimaru.net/rails-find_each/

また、1件ずつsqlを発行するのは、処理的によくないのでbulk_insertのupsert_allを使いました。 最後に、reindexをしているのは、elastic searchを使用しているため、upsert_allして値を更新したらreindexをしないといけないからです。

最後に

APIを使用するのは初めてだったので、最初はqiitaの丸写しに近いものでしたが、コードレビューを行なってもらい、より自分の実装したいものになっていきました。 また、内容の理解やrubyの理解も深まりました。 同じ処理でも、よりパフォーマンスの良い処理、実は省くことのできる記述などがありました。例えばjsonファイルではなく必要な情報だけ環境変数に記述すれば良いところなど。

最初にqiitaからヒントを得るのは悪くないと思いますが、そこから

  • コピペしたものの中身はどうなっているのか
  • 何が必要なもので、何が不必要なのか
  • 削れる処理はないか
  • よりパフォーマンスをあげるには

をもう少し自分で考え、実装できるようにしないとなと思いました。

gitで派生元branchを間違えた時の対処法

作業ブランチの派生元を修正する

  1. git rebase --onto {本来親にしたかったブランチ} {間違って親にしてしまったブランチ名} {親を変更する作業ブランチ名}

  2. conflictを解消する。

  3. ブランチの派生元を調べる。
    ブランチがどこまでマージされているかを調べる
    git show-branch branchA branchB
    ブランチの派生元を調べる
    git show-branch branchA branchB | tail -1

  4. 一度pushしている場合は、コミット履歴の改変になってしまうので、注意が必要。
    他の人も同じ作業をしている場合はNG。自分だけなら-fオプションをつけて強制的にpushする。
    git push -f origin {親を変更する作業ブランチ名}

新しいブランチを作り直す。

新しい作業ブランチを正しく作っておき、git cherry-pickを使って欲しいコミットだけを適用する。

  1. 正しくブランチを作り直す
    git checkout -b {新しく作るブランチ} {本来親にしたかったブランチ}

  2. 欲しい作業だけを適用する
    git cherry-pick {コミットID}

  3. ブランチの派生元を調べる。
    ブランチがどこまでマージされているかを調べる。
    git show-branch branchA branchB
    ブランチの派生元を調べる
    git show-branch branchA branchB | tail -1

オブジェクト指向でなぜつくるのか?いや、、そもそもオブジェクト指向って?

オブジェクト指向でなぜつくるのか」という本についてのアウトプットをしていきます。

 

 

オブジェクト指向でなぜつくるのか 第2版

オブジェクト指向でなぜつくるのか 第2版

 

 

この本を読むと


オブジェクト指向とは、何者なのかがわかる。

オブジェクト指向でなぜつくるのかという問いに答えられるようになる。

 

プログラミング言語の進化の歴史


まず、オブジェクト指向とは一体何者なのか?

それを理解するために、この本ではまずプログラミング言語の進化の歴史について解説をしています。

 

f:id:takuma521:20190715210300j:plain

このように、プログラミング言語の進化の歴史は、今まで使っていたものの欠点を改善することで新しい言語が生み出されてきました。

 

最初は機械語で0と1の羅列だったのが、人間が読めるような英文っぽくなり、混乱しないように処理が行われる順番が確立、似たようなコードは1つにまとめるように進化しました。

 

そこで、たどり着いたのが構造化言語でしたが、大きな欠点がまだ2つありました。

 

グローバル変数の存在

・サブルーチンしか共通化できない。 

 

 なぜこれが欠点なのかというと、

まずグローバル変数はプログラムのどこからでも書き換えることができます。そうすると、変数に不正な値が入って直さないといけない場合にプログラム全体を見直す必要があります。もしプログラムが何千、何万行だとしたら骨の折れる作業ですよね。

 

 また、サブルーチンしか共通化できないということも、大規模なアプリケーションからするとまだまだ足りないものだったそうです。

 

 そこで、オブジェクト指向の登場です!!

f:id:takuma521:20190715210228j:plain

 簡単に説明すると、クラスでインスタンス変数という変数が生まれました。これはグローバル変数とローカル変数(サブルーチン内でしか使えない)のいいとこ取りです。

またそのクラスはサブルーチンとインスタンス変数をまとめることで、サブルーチンではできなかったロジックの共通化ができるようになりました!!

 

結果、フレームワークやライブラリなどの大規模な再利用が可能になりました。

いま私たちがrailsを触れるのもオブジェクト指向のおかげですね!

 

最初に戻りますが、じゃあオブジェクト指向

・一体何者なのか?

・なぜ使うのか?

について、本にも書かれていますが、自分なりにまとめます。

 

・一体何者なのか?

コードを整理整頓し、共通なところは1箇所にまとめ再利用しやすくし、制約をつけて事前に間違いを防ぐというプログラミングの考え方

 

・なぜ使うのか?

コードが見やすくまとまっていて、書き方のルールで事前に間違いを防いでくれることで、楽にプログラミングできるようになるから。

また、共通なコードが1箇所にまとまっていることで楽に修正を行えるから。

そして、作ったものを再利用できるので、プログラミングがまた楽になるから。

 

最後に

 

今日お話ししたのは、この本の一部ですが他にもプログラミングから派生した応用技術やオブジェクト指向の次の開発技術である関数型言語についても書かれていますので、ぜひ読んでみてください!(ちなみに私はまだ全部読んでません!笑)

 

ActiveRecordってなんぞ?

ActiveRecordとは

ActiveRecordとは、ウェブアプリケーションフレームワークであるRuby on railsO/Rマッピングを担うライブラリ。

https://magazine.rubyist.net/articles/0004/0004-RLR.html

 

 

で、そのO/Rマッピング(Object-relational mapping、オブジェクト関係マッピング)とはデータベースとオブジェクト指向プログラミング言語の間の非互換なデータを変換するプログラミング技法のこと。

https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E9%96%A2%E4%BF%82%E3%83%9E%E3%83%83%E3%83%94%E3%83%B3%E3%82%B0

 

 

うんうん、なるほど。

簡単にいうと、ActiveRecordとは、DBとRuby(rails)の間でデータの変換を行ってくれる便利なライブラリって感じですかね!


ちなみに関係的にはこんな感じだそうです。
f:id:takuma521:20190707193046j:plain

汚なっ!

 

ActiveRecordのメリット

 

ActiveRecordを使うと、

 「どのDBに対してもRubyで直感的に記述することができる。」

そうです。

 

DBっていろいろ種類があります。
Oracle Database
MySQL
PostgreSQL
SQLite
Microsoft SQL Server
などなど、、、

 

しかし、どれも全く同じ書き方ではなく、微妙に違ったりします。
なので、文法を気にせず書けるっていうのは、大きなメリットですね。

 

あと、Rubyで直感的に書けます。例えば

User.where("age >= 25").delete_all

っていうのは、見たまんまでuserのageが25以上に合致するものを全件削除してくれます。
わかりやすいですね!

 

ActiveRecord のデメリット

 

ただ、ActiveRecordについて調べていくとデメリットも存在しました。
・複雑なデータベース設計とは相性が悪い
ActiveRecordから逸脱したことをやろうとすると面倒

 

まあ自分的にはこれらは今後、より難しいことをやる時に考えればいい話で、
まずはActiveRecordを使いこなせるようになってrailsのDB周りをできるようになりたいなと思いました。

 

命名ルール


Active Recordには、モデルとデータベースのテーブルとのマッピング作成時に従うべきルールがいくつかあります。

 

・データベースのテーブル - 複数形、語はアンダースコアで区切られる (例: book_clubs)

・モデルのクラス - 単数形、語頭を大文字にする (例: BookClub)

 

で、この命名ルールかなり厳格であり、例えば"Mouse"という単語、複数形にすると"mice"なのですが、このように書かないといけないらしいです。
単数形から複数形へ不規則な変換でも、探索してしまうなんてすごいですね。

 

ルールって厳しいと窮屈に感じられますが、rails初心者の自分からしたらむしろそれの通りにやればいいんだって寧ろありがたいです。

 

スキーマのルール


Active Recordでは、データベースのテーブルで使うカラム名についても利用目的に応じたルールがあります。

 

外部キーは、は"テーブル名の単数形_id"にする必要があります
例えば、1人のuserが複数のcommentを持つ時に、commentsテーブルにどのuserが持つcommentかを識別するキーとして、user_idを持ちます。

 

主キーは、デフォルトでidという名前のintegerカラムとして使われます。Active Recordマイグレーションでテーブルを作成すると、このカラムが自動的に作成されます。

 

他にも、いくつかルールがあります。
・created_at: レコード作成時に現在の日付時刻が自動的に設定されます。
・updated_at: レコード更新時に現在の日付時刻が自動的に設定されます。
・lock_version: モデルにoptimistic lockingを追加します。
・type: モデルでSingle Table Inheritanceを使う場合に指定します。
・関連付け名_type: ポリモーフィック関連付けの種類を保存します。

 

まとめ


ActiveRecordは、どのDBに対してもRubyで直感的に記述することができる便利なライブラリで、命名ルールやスキーマのルールが厳格に決められている。

 

うーん、ブログ書くって大変だなぁ笑

続けるためにももう少し適当にシンプルにやった方がいいかな笑

 

 

ブログはじめました💪💪💪

はじめまして、たくまです。 この度ブログを始めることにしました!!😆😆😆


始めた理由は、"学びをアウトプットすることによってより成長🏋️‍♀️していきたい!!"からです。

なぜアウトプットなのか🤔それはアウトプットが記憶を強固なものにしてくれるからです。

記憶するには、繰り返しインプットすることが必要ですが、それだけだと脳には似た情報が繰り返し入ってきた場合に、その情報を無視する現象があります。🐛
役に立たない情報を無視するためです。

そこでアウトプットをすることにより、インプットとは別の刺激(脳波が異なるそうです。)が入り、記憶をより定着させてくれるそうです。

難しい話をしてしまいましたが、要は、
インプット→アウトプット→インプット→アウトプットをしまくれば、学びが身について成長🏋️‍♀️するだろうってことです。


今まで、続けるのが苦手だった自分ですが、ブログを通じてアウトプットを習慣化していきたいと思います!!💪