From 53b6a03973e32781ed87d1a568f8a45341e83c48 Mon Sep 17 00:00:00 2001 From: Jim Durand Date: Tue, 18 Dec 2018 22:28:54 -0500 Subject: [PATCH 01/10] add meetup_client gem --- Gemfile | 3 +++ Gemfile.lock | 4 +++- config/initializers/meetup_client.rb | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 config/initializers/meetup_client.rb diff --git a/Gemfile b/Gemfile index 6daa472e..2d9aca38 100644 --- a/Gemfile +++ b/Gemfile @@ -61,6 +61,9 @@ gem "colorize" # Authorization system gem "pundit" +# Meetup API +gem 'meetup_client' + group :development do gem "annotate" gem "bullet" diff --git a/Gemfile.lock b/Gemfile.lock index ab940931..43a67be5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -97,7 +97,7 @@ GEM execjs coffee-script-source (1.12.2) colorize (0.8.1) - concurrent-ruby (1.1.3) + concurrent-ruby (1.1.4) crack (0.4.3) safe_yaml (~> 1.0.0) crass (1.0.4) @@ -212,6 +212,7 @@ GEM lumberjack (1.0.12) mail (2.7.0) mini_mime (>= 0.1.1) + meetup_client (1.0.0) memoizable (0.4.2) thread_safe (~> 0.3, >= 0.3.1) method_source (0.9.2) @@ -463,6 +464,7 @@ DEPENDENCIES guard-rubocop jquery-rails jquery-turbolinks + meetup_client omniauth-github omniauth-twitter pg diff --git a/config/initializers/meetup_client.rb b/config/initializers/meetup_client.rb new file mode 100644 index 00000000..5fbae70f --- /dev/null +++ b/config/initializers/meetup_client.rb @@ -0,0 +1,3 @@ +MeetupClient.configure do |config| + config.api_key = ENV['MEETUP_API_KEY'] +end From 572890f33ad549252c40f79ae9492ab35f7380ff Mon Sep 17 00:00:00 2001 From: Jim Durand Date: Tue, 18 Dec 2018 22:32:24 -0500 Subject: [PATCH 02/10] move models/meetup to event namespace --- app/models/event/meetup.rb | 44 ++++++++++++++++++++++++++++++++++++++ app/models/meetup.rb | 42 ------------------------------------ spec/models/meetup_spec.rb | 6 +++--- 3 files changed, 47 insertions(+), 45 deletions(-) create mode 100644 app/models/event/meetup.rb delete mode 100644 app/models/meetup.rb diff --git a/app/models/event/meetup.rb b/app/models/event/meetup.rb new file mode 100644 index 00000000..51ad8358 --- /dev/null +++ b/app/models/event/meetup.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: events +# +# id :integer not null, primary key +# type :string(255) not null +# starts_at :datetime not null +# created_at :datetime +# updated_at :datetime +# + +module Event + class Meetup < ::Event + class NotScheduledYet + def starts_at + date = third_tuesday_of(Time.current) + return third_tuesday_of(date.next_month) if date.end_of_day <= Time.current + date + end + + private + + def third_tuesday_of(time) + date = time.beginning_of_month.to_date + date = date.succ until date.tuesday? + (date + 2.weeks).in_time_zone + 20.hours + end + end + + translates :title, :body + # Someday, we should be able to remove those lines + attribute :title + attribute :body + + globalize_accessors locales: I18n.available_locales, attributes: %i(title introduction conclusion) + # validates_translated :title, :introduction, :conclusion, presence: true + + def self.next + order(starts_at: :asc).find_by("starts_at > ?", Time.now) || NotScheduledYet.new + end + end +end diff --git a/app/models/meetup.rb b/app/models/meetup.rb deleted file mode 100644 index 40d74162..00000000 --- a/app/models/meetup.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -# == Schema Information -# -# Table name: events -# -# id :integer not null, primary key -# type :string(255) not null -# starts_at :datetime not null -# created_at :datetime -# updated_at :datetime -# - -class Meetup < Event - class NotScheduledYet - def starts_at - date = third_tuesday_of(Time.current) - return third_tuesday_of(date.next_month) if date.end_of_day <= Time.current - date - end - - private - - def third_tuesday_of(time) - date = time.beginning_of_month.to_date - date = date.succ until date.tuesday? - (date + 2.weeks).in_time_zone + 20.hours - end - end - - translates :title, :body - # Someday, we should be able to remove those lines - attribute :title - attribute :body - - globalize_accessors locales: I18n.available_locales, attributes: %i(title introduction conclusion) - # validates_translated :title, :introduction, :conclusion, presence: true - - def self.next - order(starts_at: :asc).find_by("starts_at > ?", Time.now) || NotScheduledYet.new - end -end diff --git a/spec/models/meetup_spec.rb b/spec/models/meetup_spec.rb index 728b34c7..d30044d3 100644 --- a/spec/models/meetup_spec.rb +++ b/spec/models/meetup_spec.rb @@ -2,14 +2,14 @@ require "rails_helper" -RSpec.describe Meetup, type: :model do +RSpec.describe Event::Meetup, type: :model do describe ".next" do it "returns a Meetup::NotScheduledYet if there is upcomming meetups" do - expect(Meetup.next).to be_a Meetup::NotScheduledYet + expect(Event::Meetup.next).to be_a Event::Meetup::NotScheduledYet end end - describe Meetup::NotScheduledYet do + describe Event::Meetup::NotScheduledYet do describe "#starts_at" do let(:next_meetup) { described_class.new } From f6bea3c9bc3dbc2f15db2c03af04004f1fa9a1ef Mon Sep 17 00:00:00 2001 From: Jim Durand Date: Tue, 18 Dec 2018 22:33:19 -0500 Subject: [PATCH 03/10] add meetup jobs skeleton --- app/jobs/application_job.rb | 7 +++++++ app/jobs/meetup/base_job.rb | 11 +++++++++++ app/jobs/meetup/fetch_events_job.rb | 18 ++++++++++++++++++ spec/jobs/meetup/fetch_events_job_spec.rb | 5 +++++ 4 files changed, 41 insertions(+) create mode 100644 app/jobs/application_job.rb create mode 100644 app/jobs/meetup/base_job.rb create mode 100644 app/jobs/meetup/fetch_events_job.rb create mode 100644 spec/jobs/meetup/fetch_events_job_spec.rb diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb new file mode 100644 index 00000000..d394c3d1 --- /dev/null +++ b/app/jobs/application_job.rb @@ -0,0 +1,7 @@ +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/app/jobs/meetup/base_job.rb b/app/jobs/meetup/base_job.rb new file mode 100644 index 00000000..9f4b1004 --- /dev/null +++ b/app/jobs/meetup/base_job.rb @@ -0,0 +1,11 @@ +module Meetup + class BaseJob < ApplicationJob + queue_as :default + + protected + def meetup_client + @meetup_client ||= MeetupApi.new + end + + end +end diff --git a/app/jobs/meetup/fetch_events_job.rb b/app/jobs/meetup/fetch_events_job.rb new file mode 100644 index 00000000..8b7709fe --- /dev/null +++ b/app/jobs/meetup/fetch_events_job.rb @@ -0,0 +1,18 @@ +module Meetup + class FetchEventsJob < BaseJob + + def perform() + fetch_events['results'].each do |event| + # TODO: find the closest event (event['time']) + # update events rsvp count count (event['yes_rsvp_count']); + # possibly store the Meetup ID? (event['id']) + end + end + + private + def fetch_events + @events ||= meetup_client.events({ group_urlname: ENV['MEETUP_URLNAME'], status: 'upcoming,past', time: "#{Time.now.beginning_of_month.to_i * 1000},#{Time.now.end_of_month.to_i * 1000}" }) + end + + end +end diff --git a/spec/jobs/meetup/fetch_events_job_spec.rb b/spec/jobs/meetup/fetch_events_job_spec.rb new file mode 100644 index 00000000..83e5a1b7 --- /dev/null +++ b/spec/jobs/meetup/fetch_events_job_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Meetup::FetchEventsJob, type: :job do + pending "add some examples to (or delete) #{__FILE__}" +end From 0b15d566ce4383d5522839816afa4dcdf95c838a Mon Sep 17 00:00:00 2001 From: Jim Durand Date: Tue, 18 Dec 2018 22:37:37 -0500 Subject: [PATCH 04/10] add MeetupApi config to .env.sample --- .env.sample | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.env.sample b/.env.sample index 3c2d25e7..2e9223bc 100644 --- a/.env.sample +++ b/.env.sample @@ -5,3 +5,5 @@ TWITTER_CONSUMER_KEY="your_key" TWITTER_CONSUMER_SECRET="your_secret" TWITTER_ACCESS_TOKEN="your_token" TWITTER_ACCESS_SECRET="your_token_secret" +MEETUP_API_KEY="your_token" +MEETUP_URLNAME="montrealrb" From 28972632ecf1eb146828e86a5e72620f26ddc887 Mon Sep 17 00:00:00 2001 From: Jim Durand Date: Wed, 19 Dec 2018 09:41:01 -0500 Subject: [PATCH 05/10] Event is a class, not a module --- app/models/event/meetup.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/event/meetup.rb b/app/models/event/meetup.rb index 51ad8358..e9be6374 100644 --- a/app/models/event/meetup.rb +++ b/app/models/event/meetup.rb @@ -11,7 +11,7 @@ # updated_at :datetime # -module Event +class Event class Meetup < ::Event class NotScheduledYet def starts_at From 15fc2e7b5d4c19ca3f86866d426bc28af3e44427 Mon Sep 17 00:00:00 2001 From: Jim Durand Date: Wed, 19 Dec 2018 10:12:07 -0500 Subject: [PATCH 06/10] add rsvp_count to events & implement Meetup::FetchEventsJob --- app/jobs/meetup/fetch_events_job.rb | 17 +++++++++++------ db/schema.rb | 3 ++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/app/jobs/meetup/fetch_events_job.rb b/app/jobs/meetup/fetch_events_job.rb index 8b7709fe..e69ff850 100644 --- a/app/jobs/meetup/fetch_events_job.rb +++ b/app/jobs/meetup/fetch_events_job.rb @@ -1,17 +1,22 @@ module Meetup class FetchEventsJob < BaseJob - def perform() - fetch_events['results'].each do |event| - # TODO: find the closest event (event['time']) - # update events rsvp count count (event['yes_rsvp_count']); - # possibly store the Meetup ID? (event['id']) + def perform(from_time=Time.now.beginning_of_day, to_time=nil) + @from_time = from_time + @to_time = to_time + + fetch_events['results'].each do |meetup_event| + time = DateTime.strptime(meetup_event['time'].to_s, '%Q').in_time_zone + if event = Event.where(starts_at: (time - 1.day)..(time + 1.day)).sort_by{|e| (e.starts_at - time).abs}.first + event.update rsvp_count: meetup_event['yes_rsvp_count'] + end end end private def fetch_events - @events ||= meetup_client.events({ group_urlname: ENV['MEETUP_URLNAME'], status: 'upcoming,past', time: "#{Time.now.beginning_of_month.to_i * 1000},#{Time.now.end_of_month.to_i * 1000}" }) + timestamp_range = [(@from_time.to_i * 1000), (@to_time ? @to_time.to_i * 1000 : nil)].join(',') + @events ||= meetup_client.events({ group_urlname: ENV['MEETUP_URLNAME'], status: 'upcoming,past', time: timestamp_range }) end end diff --git a/db/schema.rb b/db/schema.rb index bf2b3b18..ee6c3282 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20181117014059) do +ActiveRecord::Schema.define(version: 20181219144356) do create_table "contacts", force: :cascade do |t| t.string "name" @@ -46,6 +46,7 @@ t.integer "talks_count", default: 0 t.integer "sponsorships_count", default: 0 t.integer "organizer_id" + t.integer "rsvp_count", default: 0 t.index ["location_id"], name: "index_events_on_location_id" t.index ["starts_at"], name: "index_events_on_starts_at" t.index ["user_id"], name: "index_events_on_user_id" From b8a7c6e8907d714616e008733b3f215cb0dadcf4 Mon Sep 17 00:00:00 2001 From: Jim Durand Date: Wed, 19 Dec 2018 11:23:52 -0500 Subject: [PATCH 07/10] add some specs --- app/jobs/meetup/fetch_events_job.rb | 9 +++++--- spec/jobs/meetup/fetch_events_job_spec.rb | 28 ++++++++++++++++++++++- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/app/jobs/meetup/fetch_events_job.rb b/app/jobs/meetup/fetch_events_job.rb index e69ff850..868e6609 100644 --- a/app/jobs/meetup/fetch_events_job.rb +++ b/app/jobs/meetup/fetch_events_job.rb @@ -5,7 +5,7 @@ def perform(from_time=Time.now.beginning_of_day, to_time=nil) @from_time = from_time @to_time = to_time - fetch_events['results'].each do |meetup_event| + fetch_events.each do |meetup_event| time = DateTime.strptime(meetup_event['time'].to_s, '%Q').in_time_zone if event = Event.where(starts_at: (time - 1.day)..(time + 1.day)).sort_by{|e| (e.starts_at - time).abs}.first event.update rsvp_count: meetup_event['yes_rsvp_count'] @@ -15,8 +15,11 @@ def perform(from_time=Time.now.beginning_of_day, to_time=nil) private def fetch_events - timestamp_range = [(@from_time.to_i * 1000), (@to_time ? @to_time.to_i * 1000 : nil)].join(',') - @events ||= meetup_client.events({ group_urlname: ENV['MEETUP_URLNAME'], status: 'upcoming,past', time: timestamp_range }) + @events ||= begin + timestamp_range = [(@from_time.to_i * 1000), (@to_time ? @to_time.to_i * 1000 : nil)].join(',') + data = meetup_client.events({ group_urlname: ENV['MEETUP_URLNAME'], status: 'upcoming,past', time: timestamp_range }) + data['results'] + end end end diff --git a/spec/jobs/meetup/fetch_events_job_spec.rb b/spec/jobs/meetup/fetch_events_job_spec.rb index 83e5a1b7..2753178d 100644 --- a/spec/jobs/meetup/fetch_events_job_spec.rb +++ b/spec/jobs/meetup/fetch_events_job_spec.rb @@ -1,5 +1,31 @@ require 'rails_helper' RSpec.describe Meetup::FetchEventsJob, type: :job do - pending "add some examples to (or delete) #{__FILE__}" + let(:job) { described_class.new } + let(:mock_meetup_client) { + Struct.new(:data) do + def events(attrs = {}) + data[:events] + end + end + } + let!(:event) { create(:event, starts_at: 2.weeks.from_now.noon) } + + it 'attemps to reach the Meetup API' do + expect(job).to receive(:meetup_client) { + mock_meetup_client.new(events: { + 'results' => [ + { 'id' => 'foo', 'yes_rsvp_count' => 7, 'time' => (Time.zone.tomorrow.noon.to_i * 1000) }, + { 'id' => 'bar', 'yes_rsvp_count' => 13, 'time' => (1.month.from_now.noon.to_i * 1000) }, + { 'id' => 'baz', 'yes_rsvp_count' => 42, 'time' => (2.weeks.from_now.noon.to_i * 1000) } + ], + 'meta' => [] + }) + } + + expect { + job.perform + event.reload + }.to change(event, :rsvp_count).from(0).to(42) + end end From c193e89da321b1a9ea2a23e054d6b8d01ba17ee8 Mon Sep 17 00:00:00 2001 From: Jim Durand Date: Wed, 19 Dec 2018 11:49:52 -0500 Subject: [PATCH 08/10] add some specs for meetup/base_job --- app/jobs/meetup/base_job.rb | 4 ++++ spec/jobs/meetup/base_job_spec.rb | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 spec/jobs/meetup/base_job_spec.rb diff --git a/app/jobs/meetup/base_job.rb b/app/jobs/meetup/base_job.rb index 9f4b1004..8dd5483b 100644 --- a/app/jobs/meetup/base_job.rb +++ b/app/jobs/meetup/base_job.rb @@ -2,6 +2,10 @@ module Meetup class BaseJob < ApplicationJob queue_as :default + def perform + raise Exception.new('Your class needs to implement #perform') + end + protected def meetup_client @meetup_client ||= MeetupApi.new diff --git a/spec/jobs/meetup/base_job_spec.rb b/spec/jobs/meetup/base_job_spec.rb new file mode 100644 index 00000000..39338872 --- /dev/null +++ b/spec/jobs/meetup/base_job_spec.rb @@ -0,0 +1,25 @@ +require 'rails_helper' + +RSpec.describe Meetup::BaseJob, type: :job do + include ActiveJob::TestHelper + + let(:fake_job) { Class.new(described_class) } + + describe '#perform' do + it 'raises an error' do + expect { fake_job.perform_now }.to raise_error(Exception) + end + end + + describe 'queueing' do + it 'queues the job' do + expect { + fake_job.perform_later + }.to change(ActiveJob::Base.queue_adapter.enqueued_jobs, :size).by(1) + end + + it 'queues as :default' do + expect(fake_job.new.queue_name).to eq('default') + end + end +end From 0240b1e68f60069649fbc4c39d3144d68f3dcc97 Mon Sep 17 00:00:00 2001 From: Jim Durand Date: Wed, 19 Dec 2018 11:50:22 -0500 Subject: [PATCH 09/10] add forgotten migration (oops) --- db/migrate/20181219144356_add_rsvp_count_to_events.rb | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 db/migrate/20181219144356_add_rsvp_count_to_events.rb diff --git a/db/migrate/20181219144356_add_rsvp_count_to_events.rb b/db/migrate/20181219144356_add_rsvp_count_to_events.rb new file mode 100644 index 00000000..60ebf168 --- /dev/null +++ b/db/migrate/20181219144356_add_rsvp_count_to_events.rb @@ -0,0 +1,5 @@ +class AddRsvpCountToEvents < ActiveRecord::Migration[5.1] + def change + add_column :events, :rsvp_count, :integer, default: 0, after: :sponsorships_count + end +end From c2de54ad52f3582311685c5eb4cb88145bc04d01 Mon Sep 17 00:00:00 2001 From: Jim Durand Date: Wed, 19 Dec 2018 12:04:10 -0500 Subject: [PATCH 10/10] remove trailing comma form open ended start time range --- app/jobs/meetup/fetch_events_job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/meetup/fetch_events_job.rb b/app/jobs/meetup/fetch_events_job.rb index 868e6609..e020a667 100644 --- a/app/jobs/meetup/fetch_events_job.rb +++ b/app/jobs/meetup/fetch_events_job.rb @@ -16,7 +16,7 @@ def perform(from_time=Time.now.beginning_of_day, to_time=nil) private def fetch_events @events ||= begin - timestamp_range = [(@from_time.to_i * 1000), (@to_time ? @to_time.to_i * 1000 : nil)].join(',') + timestamp_range = [(@from_time.to_i * 1000), (@to_time ? @to_time.to_i * 1000 : nil)].compact.join(',') data = meetup_client.events({ group_urlname: ENV['MEETUP_URLNAME'], status: 'upcoming,past', time: timestamp_range }) data['results'] end