Skip to content

Support Facebook API in Statistics::Aggregation #164

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Nov 3, 2017
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ script:
env:
global:
- TZ='Asia/Tokyo'
- FACEBOOK_ACCESS_TOKEN='dummy_token'
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ gem 'faraday'
# https://github.com/bundler/bundler/issues/5332
gem 'faraday_middleware', '0.10'

gem 'koala'

group :development do
gem 'web-console'
gem 'spring'
Expand Down
5 changes: 5 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ GEM
jquery-ui-rails (6.0.1)
railties (>= 3.2.16)
json (2.1.0)
koala (3.0.0)
addressable
faraday
json (>= 1.8)
kramdown (1.15.0)
launchy (2.4.3)
addressable (~> 2.3)
Expand Down Expand Up @@ -305,6 +309,7 @@ DEPENDENCIES
font-awesome-rails
jbuilder
jquery-rails
koala
kramdown
listen
minitest-retry
Expand Down
2 changes: 1 addition & 1 deletion app/models/dojo_event_service.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class DojoEventService < ApplicationRecord
belongs_to :dojo
enum name: %i( connpass doorkeeper )
enum name: %i( connpass doorkeeper facebook )
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

facebookサポートにより追加

end
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class IntegerToStringOnGroupIdInDojoEventServices < ActiveRecord::Migration[5.0]
def up
change_column :dojo_event_services, :group_id, :string, null: false
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

facebookのgroup idが15桁なのでintegerからstringに変更した。
bigintにしておくという手もあるが、別のイベントサイトをサポートする際に数値である保証もないので今のうち(?)にstringに変更した。

end

def down
change_column :dojo_event_services, :group_id, :integer, null: false
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class IntegerToStringOnServiceGroupIdAndEventIdInEventHistories < ActiveRecord::Migration[5.0]
def up
change_column :event_histories, :service_group_id, :string, null: false
change_column :event_histories, :event_id, :string, null: false
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

end

def down
change_column :event_histories, :service_group_id, :integer, null: false
change_column :event_histories, :event_id, :integer, null: false
end
end
8 changes: 4 additions & 4 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 20170820090605) do
ActiveRecord::Schema.define(version: 20171029071514) do

create_table "dojo_event_services", force: :cascade do |t|
t.integer "dojo_id", null: false
t.integer "name", null: false
t.string "url"
t.integer "group_id", null: false
t.string "group_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["dojo_id"], name: "index_dojo_event_services_on_dojo_id"
Expand All @@ -38,8 +38,8 @@
t.integer "dojo_id", null: false
t.string "dojo_name", null: false
t.string "service_name", null: false
t.integer "service_group_id", null: false
t.integer "event_id", null: false
t.string "service_group_id", null: false
t.string "event_id", null: false
t.string "event_url", null: false
t.integer "participants", null: false
t.datetime "evented_at", null: false
Expand Down
33 changes: 31 additions & 2 deletions lib/statistics/aggregation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ class << self
def run(date:)
cnps_dojos = Dojo.joins(:dojo_event_service).where(dojo_event_services: { name: :connpass }).to_a
drkp_dojos = Dojo.joins(:dojo_event_service).where(dojo_event_services: { name: :doorkeeper }).to_a
fsbk_dojos = Dojo.joins(:dojo_event_service).where(dojo_event_services: { name: :facebook }).to_a

Connpass.run(cnps_dojos, date)
Doorkeeper.run(drkp_dojos, date)
Facebook.run(fsbk_dojos, date)
end
end

Expand All @@ -20,7 +22,7 @@ def run(dojos, date)

dojos.each do |dojo|
cnps.fetch_events(params.merge(series_id: dojo.dojo_event_service.group_id)).each do |e|
next unless e.dig('series', 'id') == dojo.dojo_event_service.group_id
next unless e.dig('series', 'id').to_s == dojo.dojo_event_service.group_id
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

カラムの型変更に伴いto_sするようにした


EventHistory.create!(dojo_id: dojo.id,
dojo_name: dojo.name,
Expand All @@ -47,7 +49,7 @@ def run(dojos, date)

dojos.each do |dojo|
drkp.fetch_events(params.merge(group_id: dojo.dojo_event_service.group_id)).each do |e|
next unless e['group'] == dojo.dojo_event_service.group_id
next unless e['group'].to_s == dojo.dojo_event_service.group_id
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto


EventHistory.create!(dojo_id: dojo.id,
dojo_name: dojo.name,
Expand All @@ -62,5 +64,32 @@ def run(dojos, date)
end
end
end

class Facebook
class << self
def run(dojos, date)
fsbk = Client::Facebook.new

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fsbkってfacebookを省略した言葉なんですね🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ローカル変数なので雑に省略しています😅

params = {
since_at: date.beginning_of_month,
until_at: date.end_of_month
}

dojos.each do |dojo|
fsbk.fetch_events(params.merge(group_id: dojo.dojo_event_service.group_id)).each do |e|
next unless e.dig('owner', 'id') == dojo.dojo_event_service.group_id

EventHistory.create!(dojo_id: dojo.id,
dojo_name: dojo.name,
service_name: dojo.dojo_event_service.name,
service_group_id: dojo.dojo_event_service.group_id,
event_id: e['id'],
event_url: "https://www.facebook.com/events/#{e['id']}",
participants: e['attending_count'],
evented_at: Time.zone.parse(e['start_time']))
end
end
end
end
end
end
end
25 changes: 25 additions & 0 deletions lib/statistics/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,5 +101,30 @@ def fetch_events(group_id:, since_at: @default_since, until_at: @default_until)
events
end
end

class Facebook
def initialize
@client = Koala::Facebook::API.new(ENV.fetch('FACEBOOK_ACCESS_TOKEN'))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Facebook Graph APIを使うためのaccess tokenは環境変数経由で取得する

end

def fetch_events(group_id:, since_at: nil, until_at: nil)
params = {
fields: %i(attending_count start_time owner),
limit: 100,
since: since_at,
until: until_at
}.compact
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

valueがnilのものがない場合はparamsがnilになってしまうのでbangは不要


events = []

collection = @client.get_object("#{group_id}/events", params)
events.push(*collection.to_a)
while !collection.empty? && collection.paging['next'] do
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

collection.empty? == trueの場合、collection.pagingがnilなのでcollectionがemptyかどうかチェックしておく

events.push(*collection.next_page.to_a)
end

events
end
end
end
end
6 changes: 6 additions & 0 deletions lib/tasks/oauth.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace :oauth do
desc 'Facebookのaccess tokenを取得します'
task :facebook_access_token, [:app_id, :app_secret] => :environment do |_tasks, args|
puts 'Access Token: ' + Koala::Facebook::OAuth.new(args[:app_id], args[:app_secret]).get_app_access_token
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Facebookのapp_idとapp_secretでaccess tokenを取得するRake task

end
end
8 changes: 6 additions & 2 deletions spec/lib/statistics/aggregation_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
require 'statistics'

RSpec.describe Statistics::Aggregation do
include_context 'Use stubs for Faraday'
include_context 'Use stubs for Connpass'
include_context 'Use stubs for Doorkeeper'
include_context 'Use stubs for Facebook'

before(:all) do
Dojo.destroy_all
Expand All @@ -16,14 +18,16 @@
before do
d1 = Dojo.create(name: 'Dojo1', email: 'info@dojo1.com', description: 'CoderDojo1', tags: %w(CoderDojo1), url: 'https://dojo1.com')
d2 = Dojo.create(name: 'Dojo2', email: 'info@dojo2.com', description: 'CoderDojo2', tags: %w(CoderDojo2), url: 'https://dojo2.com')
d3 = Dojo.create(name: 'Dojo3', email: 'info@dojo3.com', description: 'CoderDojo3', tags: %w(CoderDojo3), url: 'https://dojo3.com')
DojoEventService.create(dojo_id: d1.id, name: :connpass, group_id: 9876)
DojoEventService.create(dojo_id: d2.id, name: :doorkeeper, group_id: 5555)
DojoEventService.create(dojo_id: d3.id, name: :facebook, group_id: 123451234512345)
end

subject { Statistics::Aggregation.run(date: Time.current) }

it do
expect{ subject }.to change{EventHistory.count}.from(0).to(2)
expect{ subject }.to change{EventHistory.count}.from(0).to(3)
end
end
end
21 changes: 19 additions & 2 deletions spec/lib/statistics/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
require 'statistics'

RSpec.describe Statistics::Client do
include_context 'Use stubs for Faraday'

context 'Connpass' do
include_context 'Use stubs for Connpass'

describe '#search' do
subject { Statistics::Client::Connpass.new.search(keyword: 'coderdojo') }

Expand Down Expand Up @@ -32,6 +32,8 @@
end

context 'Doorkeeper' do
include_context 'Use stubs for Doorkeeper'

describe '#search' do
subject { Statistics::Client::Doorkeeper.new.search(keyword: 'coderdojo') }

Expand All @@ -54,4 +56,19 @@
end
end
end

context 'Facebook' do
include_context 'Use stubs for Facebook'

describe '#fetch_events' do
subject { Statistics::Client::Facebook.new.fetch_events(group_id: 123451234512345) }

it do
expect(subject).to be_instance_of(Array)
expect(subject.size).to eq 1
expect(subject.first['id']).to eq '125500978166443'
expect(subject.first.dig('owner', 'id')).to eq '123451234512345'
end
end
end
end
57 changes: 43 additions & 14 deletions spec/support/shared_contexts/statistics.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,37 @@
RSpec.shared_context 'Use stubs for Faraday' do
RSpec.shared_context 'Use stub connection of Faraday' do
let(:stub_connection) do
Faraday.new do |f|
f.response :json, :content_type => /\bjson$/
f.adapter :test, Faraday::Adapter::Test::Stubs.new do |stub|
# connpass
stub.get('/event/') { connpass_response }

# doorkeeper
stub.get('/events') { doorkeeper_response }
stub.get('/groups/5555/events') { doorkeeper_response }
end
end
end

before do
allow_any_instance_of(Statistics::Client).to receive(:connection_for).and_return(stub_connection)
end
end

RSpec.shared_context 'Use stubs for Connpass' do
include_context 'Use stub connection of Faraday'

let(:connpass_response) do
[
200,
{ 'Content-Type' => 'application/json' },
'{"results_returned": 1, "events": [{"event_url": "https://coderdojo-okutama.connpass.com/event/12345/", "event_type": "participation", "owner_nickname": "nalabjp", "series": {"url": "https://coderdojo-okutama.connpass.com/", "id": 9876, "title": "CoderDojo series"}, "updated_at": "2017-04-29T14:59:30+09:00", "lat": "35.801763000000", "started_at": "2017-05-07T10:00:00+09:00", "hash_tag": "CoderDojo", "title": "CoderDojo title", "event_id": 12345, "lon": "139.087656000000", "waiting": 2, "limit": 10, "owner_id": 2525, "owner_display_name": "nalabjp", "description": "CoderDojo description", "address": "Okutama-cho Tokyo", "catch": "CoderDojo catch", "accepted": 10, "ended_at": "2017-05-07T12:00:00+09:00", "place": "Tokyo"}], "results_start": 200, "results_available": 518}'
]
end
end

RSpec.shared_context 'Use stubs for Doorkeeper' do
include_context 'Use stub connection of Faraday'

let(:doorkeeper_response) do
[
Expand All @@ -14,22 +40,25 @@
'[{"event":{"title":"CoderDojo title","id":1234,"starts_at":"2017-05-28T01:00:00.000Z","ends_at":"2017-05-28T04:00:00.000Z","venue_name":"奥多摩町","address":"奥多摩町","lat":"35.801763000000","long":"139.087656000000","ticket_limit":30,"published_at":"2017-04-22T03:43:04.000Z","updated_at":"2017-05-10T11:31:21.810Z","group":5555,"banner":null,"description":"CoderDojo description","public_url":"https://coderdojo-okutama.doorkeeper.jp/events/8888","participants":12,"waitlisted":0}}]'
]
end
end

let(:stub_connection) do
Faraday.new do |f|
f.response :json, :content_type => /\bjson$/
f.adapter :test, Faraday::Adapter::Test::Stubs.new do |stub|
# connpass
stub.get('/event/') { connpass_response }

# doorkeeper
stub.get('/events') { doorkeeper_response }
stub.get('/groups/5555/events') { doorkeeper_response }
end
end
RSpec.shared_context 'Use stubs for Facebook' do
let(:facebook_response) do
resp = OpenStruct.new(data: {
"data" => [
{"attending_count"=>1, "start_time"=>"2017-10-29T13:00:00+0900", "owner"=>{"name"=>"CoderDojo ひばりヶ丘", "id"=>"123451234512345"}, "id"=>"125500978166443"}
],
"paging" => {
"cursors" => {
"before" => "QVFIUjVOd2tKSmZA6S01fR0NFNWN2aFJlc01JUnpqRW5aMFFkeHdBS3NTcUt1b3JfazUzM3FtVGhCYlN6bE1OS1lxZAzQ0YjVSNWRRVWRfd182SXh3LUN6VXZAB",
"after" => "QVFIUmZA1cEk5QlV0VFc2Ri1BT3JEOWl3M1gzemRZAZAkpkaFdjNTEwUDdtaERLdFpwYV9CejVuX3hLV2kyVm5Gem9KSTAzTGg0dUd4SjNLXzBlSTZAJMVAtdmln"
}
}
})
Koala::Facebook::API::GraphCollection.new(resp, nil)
end

before do
allow_any_instance_of(Statistics::Client).to receive(:connection_for).and_return(stub_connection)
allow_any_instance_of(Koala::Facebook::API).to receive(:get_object).and_return(facebook_response)
end
end