From b0fd6584272a18d268af67a694faa73006d6d12d Mon Sep 17 00:00:00 2001 From: Zee Date: Thu, 30 Nov 2017 15:12:04 -0800 Subject: [PATCH] Split out a `nylas-streaming` gem (#151) This lays the foundation for a future `nylas-rails` gem while keeping our build process and core dependencies lightweight. We can now have `nylas` be the rest/http client while `nylas-streaming` includes the event machine and yajl functionality so that users who aren't using those don't pull in unnecessary dependencies or run into dependency resolution conflicts. * Inform users that delta streams are now part of nylas-streaming in the README * Add post install message to ask people to read the upgrade guide --- Gemfile | 2 +- README.md | 2 +- gemfiles/Gemfile.rails4 | 2 +- gemfiles/Gemfile.rails4.lock | 8 +-- gemfiles/Gemfile.rails5 | 2 +- gemfiles/Gemfile.rails5.lock | 8 +-- gemfiles/Gemfile.rest-client.1 | 2 +- gemfiles/Gemfile.rest-client.1.lock | 8 +-- gemfiles/Gemfile.rest-client.2 | 2 +- gemfiles/Gemfile.rest-client.2.lock | 8 +-- lib/nylas-streaming.rb | 81 +++++++++++++++++++++++++++++ lib/nylas.rb | 64 ++++++++++------------- lib/nylas/api/delta_stream.rb | 74 -------------------------- lib/nylas/types_filter.rb | 24 +++++++++ nylas-streaming.gemspec | 42 +++++++++++++++ nylas.gemspec | 14 +++-- spec/spec_helper.rb | 2 +- 17 files changed, 211 insertions(+), 134 deletions(-) create mode 100644 lib/nylas-streaming.rb delete mode 100644 lib/nylas/api/delta_stream.rb create mode 100644 lib/nylas/types_filter.rb create mode 100644 nylas-streaming.gemspec diff --git a/Gemfile b/Gemfile index fa75df15..9f2b544c 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,3 @@ source 'https://rubygems.org' -gemspec +gemspec name: 'nylas-streaming' diff --git a/README.md b/README.md index 9341c54b..804b28a2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Nylas REST API Ruby bindings ![Travis build status](https://travis-ci.org/nylas/nylas-ruby.svg?branch=master) -This README is for the Nylas Ruby SDK version 4. For those who are still using Nylas Ruby SDK Version 3, the documentation and source code is located in the [3.X-master branch](https://github.com/nylas/nylas-ruby/tree/3.X-master) +This README is for the Nylas Ruby SDK version 4. For those who are still using Nylas Ruby SDK Version 3, the documentation and source code is located in the [3.X-master branch](https://github.com/nylas/nylas-ruby/tree/3.X-master). For those upgrading from 3.X to 4.0, review the [upgrade guide](https://github.com/nylas/nylas-ruby/wiki/Upgrading-from-3.X-to-4.0) ## Installation diff --git a/gemfiles/Gemfile.rails4 b/gemfiles/Gemfile.rails4 index 10bdef60..09cac3b3 100644 --- a/gemfiles/Gemfile.rails4 +++ b/gemfiles/Gemfile.rails4 @@ -2,4 +2,4 @@ source 'https://rubygems.org' gem 'rails', '~> 4.2' -gemspec path: ".." +gemspec path: '..', name: 'nylas-streaming' diff --git a/gemfiles/Gemfile.rails4.lock b/gemfiles/Gemfile.rails4.lock index ed3d4154..70180151 100644 --- a/gemfiles/Gemfile.rails4.lock +++ b/gemfiles/Gemfile.rails4.lock @@ -3,6 +3,10 @@ PATH specs: nylas (4.0.0.rc1) rest-client (>= 1.6) + nylas-streaming (4.0.0.rc1) + em-http-request (~> 1.1, >= 1.1.3) + nylas (>= 4.0) + yajl-ruby (~> 1.2, >= 1.2.1) GEM remote: https://rubygems.org/ @@ -220,9 +224,8 @@ PLATFORMS DEPENDENCIES bundler (>= 1.3.5) - em-http-request (~> 1.1, >= 1.1.3) jeweler (>= 2.1.2) - nylas! + nylas-streaming! pry (>= 0.10.4) pry-nav (>= 0.2.4) pry-stack_explorer (>= 0.4.9.2) @@ -231,7 +234,6 @@ DEPENDENCIES shoulda (>= 3.4.0) sinatra (>= 1.4.7) webmock (>= 2.1.0) - yajl-ruby (~> 1.2, >= 1.2.1) yard (>= 0.9.12) BUNDLED WITH diff --git a/gemfiles/Gemfile.rails5 b/gemfiles/Gemfile.rails5 index 585c8ff6..2beb974d 100644 --- a/gemfiles/Gemfile.rails5 +++ b/gemfiles/Gemfile.rails5 @@ -2,4 +2,4 @@ source 'https://rubygems.org' gem 'rails', '~> 5' -gemspec path: ".." +gemspec path: "..", name: 'nylas-streaming' diff --git a/gemfiles/Gemfile.rails5.lock b/gemfiles/Gemfile.rails5.lock index 8beb5a4f..606d3f5a 100644 --- a/gemfiles/Gemfile.rails5.lock +++ b/gemfiles/Gemfile.rails5.lock @@ -3,6 +3,10 @@ PATH specs: nylas (4.0.0.rc1) rest-client (>= 1.6) + nylas-streaming (4.0.0.rc1) + em-http-request (~> 1.1, >= 1.1.3) + nylas (>= 4.0) + yajl-ruby (~> 1.2, >= 1.2.1) GEM remote: https://rubygems.org/ @@ -228,9 +232,8 @@ PLATFORMS DEPENDENCIES bundler (>= 1.3.5) - em-http-request (~> 1.1, >= 1.1.3) jeweler (>= 2.1.2) - nylas! + nylas-streaming! pry (>= 0.10.4) pry-nav (>= 0.2.4) pry-stack_explorer (>= 0.4.9.2) @@ -239,7 +242,6 @@ DEPENDENCIES shoulda (>= 3.4.0) sinatra (>= 1.4.7) webmock (>= 2.1.0) - yajl-ruby (~> 1.2, >= 1.2.1) yard (>= 0.9.12) BUNDLED WITH diff --git a/gemfiles/Gemfile.rest-client.1 b/gemfiles/Gemfile.rest-client.1 index 419c820b..057853ce 100644 --- a/gemfiles/Gemfile.rest-client.1 +++ b/gemfiles/Gemfile.rest-client.1 @@ -2,4 +2,4 @@ source 'https://rubygems.org' gem 'rest-client', '~> 1.6' -gemspec path: ".." +gemspec path: "..", name: 'nylas-streaming' diff --git a/gemfiles/Gemfile.rest-client.1.lock b/gemfiles/Gemfile.rest-client.1.lock index 9c2a1f6a..86071a3c 100644 --- a/gemfiles/Gemfile.rest-client.1.lock +++ b/gemfiles/Gemfile.rest-client.1.lock @@ -3,6 +3,10 @@ PATH specs: nylas (4.0.0.rc1) rest-client (>= 1.6) + nylas-streaming (4.0.0.rc1) + em-http-request (~> 1.1, >= 1.1.3) + nylas (>= 4.0) + yajl-ruby (~> 1.2, >= 1.2.1) GEM remote: https://rubygems.org/ @@ -148,9 +152,8 @@ PLATFORMS DEPENDENCIES bundler (>= 1.3.5) - em-http-request (~> 1.1, >= 1.1.3) jeweler (>= 2.1.2) - nylas! + nylas-streaming! pry (>= 0.10.4) pry-nav (>= 0.2.4) pry-stack_explorer (>= 0.4.9.2) @@ -159,7 +162,6 @@ DEPENDENCIES shoulda (>= 3.4.0) sinatra (>= 1.4.7) webmock (>= 2.1.0) - yajl-ruby (~> 1.2, >= 1.2.1) yard (>= 0.9.12) BUNDLED WITH diff --git a/gemfiles/Gemfile.rest-client.2 b/gemfiles/Gemfile.rest-client.2 index 5d30ef91..0bbf7185 100644 --- a/gemfiles/Gemfile.rest-client.2 +++ b/gemfiles/Gemfile.rest-client.2 @@ -2,4 +2,4 @@ source 'https://rubygems.org' gem 'rest-client', '~> 2.0' -gemspec path: ".." +gemspec path: "..", name: 'nylas-streaming' diff --git a/gemfiles/Gemfile.rest-client.2.lock b/gemfiles/Gemfile.rest-client.2.lock index 22590009..fb336a89 100644 --- a/gemfiles/Gemfile.rest-client.2.lock +++ b/gemfiles/Gemfile.rest-client.2.lock @@ -3,6 +3,10 @@ PATH specs: nylas (4.0.0.rc1) rest-client (>= 1.6) + nylas-streaming (4.0.0.rc1) + em-http-request (~> 1.1, >= 1.1.3) + nylas (>= 4.0) + yajl-ruby (~> 1.2, >= 1.2.1) GEM remote: https://rubygems.org/ @@ -148,9 +152,8 @@ PLATFORMS DEPENDENCIES bundler (>= 1.3.5) - em-http-request (~> 1.1, >= 1.1.3) jeweler (>= 2.1.2) - nylas! + nylas-streaming! pry (>= 0.10.4) pry-nav (>= 0.2.4) pry-stack_explorer (>= 0.4.9.2) @@ -159,7 +162,6 @@ DEPENDENCIES shoulda (>= 3.4.0) sinatra (>= 1.4.7) webmock (>= 2.1.0) - yajl-ruby (~> 1.2, >= 1.2.1) yard (>= 0.9.12) BUNDLED WITH diff --git a/lib/nylas-streaming.rb b/lib/nylas-streaming.rb new file mode 100644 index 00000000..78516e77 --- /dev/null +++ b/lib/nylas-streaming.rb @@ -0,0 +1,81 @@ +require 'yajl' +require 'em-http' +require 'nylas' + +module Nylas + class API + def delta_stream(cursor, exclude_types=[], timeout=0, expanded_view=false, include_types=[], &block) + Streaming::Stream.new(api: self, cursor: cursor, timeout: timeout, expanded_view: expanded_view, + exclude_types: exclude_types, include_types: include_types).listen(&block) + end + end + + module Streaming + class Stream + extend Forwardable + def_delegators :api, :url_for_path + attr_accessor :api, :timeout, :expanded_view, :include_types, :exclude_types, :cursor + + def initialize(api: , cursor: , timeout: 0, expanded_view: false, include_types: [], exclude_types: []) + self.api = api + self.cursor = cursor + self.timeout = timeout + self.expanded_view = expanded_view + self.include_types = TypesFilter.new(:include, types: include_types) + self.exclude_types = TypesFilter.new(:exclude, types: exclude_types) + end + + def listen + raise 'Please provide a block for receiving the delta objects' if !block_given? + + exclude_string = exclude_types.to_query_string + include_string = include_types.to_query_string + + # loop and yield deltas indefinitely. + path = self.url_for_path("/delta/streaming?exclude_folders=false&cursor=#{cursor}#{exclude_string}#{include_string}") + if expanded_view + path += '&view=expanded' + end + + parser = Yajl::Parser.new(:symbolize_keys => false) + parser.on_parse_complete = proc do |data| + delta = Nylas.interpret_response(OpenStruct.new(:code => '200'), data, {:expected_class => Object, :result_parsed => true}) + + if not OBJECTS_TABLE.has_key?(delta['object']) + next + end + + cls = OBJECTS_TABLE[delta['object']] + if EXPANDED_OBJECTS_TABLE.has_key?(delta['object']) and expanded_view + cls = EXPANDED_OBJECTS_TABLE[delta['object']] + end + + obj = cls.new(api) + + case delta["event"] + when 'create', 'modify' + obj.inflate(delta['attributes']) + obj.cursor = delta["cursor"] + yield delta["event"], obj + when 'delete' + obj.id = delta["id"] + obj.cursor = delta["cursor"] + yield delta["event"], obj + end + end + + http = EventMachine::HttpRequest.new(path, :connect_timeout => 0, :inactivity_timeout => timeout).get(:keepalive => true) + + # set a callback on the HTTP stream that parses incoming chunks as they come in + http.stream do |chunk| + parser << chunk + end + + http.errback do + raise UnexpectedResponse.new http.error + end + end + end + end +end + diff --git a/lib/nylas.rb b/lib/nylas.rb index bc73aab6..7bbb698c 100644 --- a/lib/nylas.rb +++ b/lib/nylas.rb @@ -4,6 +4,8 @@ require 'ostruct' require_relative 'nylas/to_query' +require_relative 'nylas/types_filter' + require 'nylas/account' require 'nylas/api_account' require 'nylas/thread' @@ -23,6 +25,25 @@ require 'nylas/version' module Nylas + OBJECTS_TABLE = { + "account" => Nylas::Account, + "calendar" => Nylas::Calendar, + "draft" => Nylas::Draft, + "thread" => Nylas::Thread, + "contact" => Nylas::Contact, + "event" => Nylas::Event, + "file" => Nylas::File, + "message" => Nylas::Message, + "folder" => Nylas::Folder, + "label" => Nylas::Label, + } + + # It's possible to ask the API to expand objects. + # In this case, we do the right thing and return + # an expanded object. + EXPANDED_OBJECTS_TABLE = { + "message" => Nylas::ExpandedMessage, + } Error = Class.new(::StandardError) NoAuthToken = Class.new(Error) UnexpectedAccountAction = Class.new(Error) @@ -265,46 +286,13 @@ def latest_cursor cursor end - OBJECTS_TABLE = { - "account" => Nylas::Account, - "calendar" => Nylas::Calendar, - "draft" => Nylas::Draft, - "thread" => Nylas::Thread, - "contact" => Nylas::Contact, - "event" => Nylas::Event, - "file" => Nylas::File, - "message" => Nylas::Message, - "folder" => Nylas::Folder, - "label" => Nylas::Label, - } - - # It's possible to ask the API to expand objects. - # In this case, we do the right thing and return - # an expanded object. - EXPANDED_OBJECTS_TABLE = { - "message" => Nylas::ExpandedMessage, - } - - def _build_types_filter_string(filter, types) - return "" if types.empty? - query_string = "&#{filter}_types=" - - types.each do |value| - count = 0 - if OBJECTS_TABLE.has_value?(value) - param_name = OBJECTS_TABLE.key(value) - query_string += "#{param_name}," - end - end - query_string = query_string[0..-2] - end def deltas(cursor, exclude_types=[], expanded_view=false, include_types=[]) return enum_for(:deltas, cursor, exclude_types, expanded_view, include_types) unless block_given? - exclude_string = _build_types_filter_string(:exclude, exclude_types) - include_string = _build_types_filter_string(:include, include_types) + exclude_string = TypesFilter.new(:exclude, types: exclude_types).to_query_string + include_string = TypesFilter.new(:include, types: include_types).to_query_string # loop and yield deltas until we've come to the end. loop do @@ -351,6 +339,10 @@ def deltas(cursor, exclude_types=[], expanded_view=false, include_types=[]) end end - require 'nylas/api/delta_stream' + def delta_stream(cursor, exclude_types=[], timeout=0, expanded_view=false, include_types=[], &block) + raise NotImplementedError, "the `#delta_stream` method was removed in 4.0 in favor of using the " \ + "`nylas-streming` gem. This reduces the dependency footprint of the core " \ + "nylas gem for those not using the streaming API." + end end end diff --git a/lib/nylas/api/delta_stream.rb b/lib/nylas/api/delta_stream.rb deleted file mode 100644 index b830fbf6..00000000 --- a/lib/nylas/api/delta_stream.rb +++ /dev/null @@ -1,74 +0,0 @@ -not_installed = false -{ - 'yajl' => 'yajl-ruby', - 'em-http' => 'em-http-request' -}.all? do |dep, gem| - begin - require dep - true - rescue LoadError - not_installed = gem - false - end -end - -if not_installed - Nylas::API.class_eval do - define_method(:delta_stream) do |*| - raise LoadError, "In order to use Nylas::API#delta_stream you should install #{not_installed} gem. Just add \"gem '#{not_installed}'\" to your Gemfile." - end - end -else - class Nylas::API - def delta_stream(cursor, exclude_types=[], timeout=0, expanded_view=false, include_types=[]) - raise 'Please provide a block for receiving the delta objects' if !block_given? - - exclude_string = _build_types_filter_string(:exclude, exclude_types) - include_string = _build_types_filter_string(:include, include_types) - - # loop and yield deltas indefinitely. - path = self.url_for_path("/delta/streaming?exclude_folders=false&cursor=#{cursor}#{exclude_string}#{include_string}") - if expanded_view - path += '&view=expanded' - end - - parser = Yajl::Parser.new(:symbolize_keys => false) - parser.on_parse_complete = proc do |data| - delta = Nylas.interpret_response(OpenStruct.new(:code => '200'), data, {:expected_class => Object, :result_parsed => true}) - - if not OBJECTS_TABLE.has_key?(delta['object']) - next - end - - cls = OBJECTS_TABLE[delta['object']] - if EXPANDED_OBJECTS_TABLE.has_key?(delta['object']) and expanded_view - cls = EXPANDED_OBJECTS_TABLE[delta['object']] - end - - obj = cls.new(self) - - case delta["event"] - when 'create', 'modify' - obj.inflate(delta['attributes']) - obj.cursor = delta["cursor"] - yield delta["event"], obj - when 'delete' - obj.id = delta["id"] - obj.cursor = delta["cursor"] - yield delta["event"], obj - end - end - - http = EventMachine::HttpRequest.new(path, :connect_timeout => 0, :inactivity_timeout => timeout).get(:keepalive => true) - - # set a callback on the HTTP stream that parses incoming chunks as they come in - http.stream do |chunk| - parser << chunk - end - - http.errback do - raise UnexpectedResponse.new http.error - end - end - end -end diff --git a/lib/nylas/types_filter.rb b/lib/nylas/types_filter.rb new file mode 100644 index 00000000..3110163e --- /dev/null +++ b/lib/nylas/types_filter.rb @@ -0,0 +1,24 @@ +module Nylas + class TypesFilter + attr_accessor :filter, :types + def initialize(filter, types: []) + self.filter = filter + self.types = types + end + + def to_query_string + return "" if types.empty? + query_string = "&#{filter}_types=" + + types.each do |value| + count = 0 + if OBJECTS_TABLE.has_value?(value) + param_name = OBJECTS_TABLE.key(value) + query_string += "#{param_name}," + end + end + + query_string = query_string[0..-2] + end + end +end diff --git a/nylas-streaming.gemspec b/nylas-streaming.gemspec new file mode 100644 index 00000000..88db38bd --- /dev/null +++ b/nylas-streaming.gemspec @@ -0,0 +1,42 @@ +# encoding: utf-8 +require "./lib/nylas/version.rb" + +Gem::Specification.new do |gem| + gem.name = "nylas-streaming" + gem.homepage = "http://github.com/nylas/nylas-ruby" + gem.license = "MIT" + gem.summary = %Q{Gem for interacting with the Nylas API} + gem.description = %Q{Gem for interacting with the Nylas API.} + gem.version = Nylas::VERSION + gem.email = "support@nylas.com" + gem.authors = ["Nylas, Inc."] + gem.files = Dir.glob("lib/{nylas-streaming.rb,nylas-streaming/**/*.rb}") + gem.platform = "ruby" + + gem.metadata = { + "bug_tracker_uri" => "https://github.com/nylas/nylas-ruby/issues", + "changelog_uri" => "https://github.com/nylas/nylas-ruby/blob/master/CHANGELOG.md", + "documentation_uri" => "http://www.rubydoc.info/gems/nylas", + "homepage_uri" => "https://www.nylas.com", + "source_code_uri" => "https://github.com/nylas/nylas-ruby", + "wiki_uri" => "https://github.com/nylas/nylas-ruby/wiki" + } + + gem.add_runtime_dependency "nylas", ">= 4.0" + gem.add_runtime_dependency "yajl-ruby", "~> 1.2", ">= 1.2.1" + gem.add_runtime_dependency "em-http-request", "~> 1.1", ">= 1.1.3" + + gem.add_development_dependency "yard", ">= 0.9.12" + gem.add_development_dependency "bundler", ">= 1.3.5" + gem.add_development_dependency "jeweler", ">= 2.1.2" + + gem.add_development_dependency "pry", ">= 0.10.4" + gem.add_development_dependency "pry-nav", ">= 0.2.4" + gem.add_development_dependency "pry-stack_explorer", ">= 0.4.9.2" + + gem.add_development_dependency "rspec", ">= 3.5.0" + gem.add_development_dependency "shoulda", ">= 3.4.0" + gem.add_development_dependency "webmock", ">= 2.1.0", ">= 2.1.0" + + gem.add_development_dependency "sinatra", ">= 1.4.7" +end diff --git a/nylas.gemspec b/nylas.gemspec index 805d7f2e..41192792 100644 --- a/nylas.gemspec +++ b/nylas.gemspec @@ -10,7 +10,8 @@ Gem::Specification.new do |gem| gem.version = Nylas::VERSION gem.email = "support@nylas.com" gem.authors = ["Nylas, Inc."] - gem.files = Dir.glob("lib/**/*.rb") + ['CHANGELOG.md', 'LICENSE.txt', 'README.md', __FILE__] + gem.files = Dir.glob("lib/{nylas.rb,nylas/**/*.rb}") + ['CHANGELOG.md', 'LICENSE.txt', 'README.md', + __FILE__] gem.platform = "ruby" gem.metadata = { @@ -23,18 +24,21 @@ Gem::Specification.new do |gem| "yard.run" => "yri" } + gem.post_install_message = "Nylas 4.0 includes breaking changes. Review the upgrade guide! " \ + "https://github.com/nylas/nylas-ruby/wiki/Upgrading-from-3.X-to-4.0" gem.add_runtime_dependency "rest-client", ">= 1.6" - gem.add_development_dependency "rspec", ">= 3.5.0" - gem.add_development_dependency "shoulda", ">= 3.4.0" gem.add_development_dependency "yard", ">= 0.9.12" gem.add_development_dependency "bundler", ">= 1.3.5" gem.add_development_dependency "jeweler", ">= 2.1.2" + gem.add_development_dependency "pry", ">= 0.10.4" gem.add_development_dependency "pry-nav", ">= 0.2.4" gem.add_development_dependency "pry-stack_explorer", ">= 0.4.9.2" + + gem.add_development_dependency "rspec", ">= 3.5.0" + gem.add_development_dependency "shoulda", ">= 3.4.0" gem.add_development_dependency "webmock", ">= 2.1.0", ">= 2.1.0" + gem.add_development_dependency "sinatra", ">= 1.4.7" - gem.add_development_dependency "yajl-ruby", "~> 1.2", ">= 1.2.1" - gem.add_development_dependency "em-http-request", "~> 1.1", ">= 1.1.3" end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2b2edfac..dc983c8b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,4 +1,4 @@ ::ENV['RACK_ENV'] ||= 'test' -require 'nylas' +require 'nylas-streaming' require 'webmock/rspec'