rMeetup 1.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +21 -0
  3. data/README.md +65 -0
  4. data/Rakefile +6 -12
  5. data/lib/core_ext/string.rb +11 -0
  6. data/lib/rmeetup.rb +2 -63
  7. data/lib/rmeetup/client.rb +77 -0
  8. data/lib/rmeetup/errors.rb +15 -0
  9. data/lib/rmeetup/fetcher.rb +20 -33
  10. data/lib/rmeetup/fetcher/base.rb +66 -55
  11. data/lib/rmeetup/fetcher/open_events.rb +14 -0
  12. data/lib/rmeetup/fetcher/venues.rb +14 -0
  13. data/lib/rmeetup/poster.rb +17 -0
  14. data/lib/rmeetup/poster/base.rb +81 -0
  15. data/lib/rmeetup/{fetcher/comments.rb → poster/event_comment.rb} +5 -5
  16. data/lib/rmeetup/type.rb +2 -1
  17. data/lib/rmeetup/type/event.rb +21 -18
  18. data/lib/rmeetup/type/event_comment.rb +42 -0
  19. data/lib/rmeetup/type/rsvp.rb +9 -5
  20. data/lib/rmeetup/type/venue.rb +63 -0
  21. data/lib/rmeetup/version.rb +14 -0
  22. data/rmeetup.gemspec +34 -0
  23. data/spec/client_spec.rb +77 -29
  24. data/spec/fetcher_spec.rb +4 -4
  25. data/spec/fixtures/vcr_cassettes/fetching_cities.yml +358 -0
  26. data/spec/fixtures/vcr_cassettes/fetching_events.yml +54 -0
  27. data/spec/fixtures/vcr_cassettes/fetching_groups.yml +8627 -0
  28. data/spec/fixtures/vcr_cassettes/fetching_members.yml +3984 -0
  29. data/spec/fixtures/vcr_cassettes/fetching_photos.yml +54 -0
  30. data/spec/fixtures/vcr_cassettes/fetching_rsvps.yml +54 -0
  31. data/spec/spec_helper.rb +11 -69
  32. metadata +140 -101
  33. data/Manifest +0 -45
  34. data/README.rdoc +0 -34
  35. data/lib/rmeetup/fetcher/topics.rb +0 -14
  36. data/rMeetup.gemspec +0 -29
  37. data/spec/fetchers/base_spec.rb +0 -58
  38. data/spec/fetchers/cities_spec.rb +0 -18
  39. data/spec/fetchers/comments_spec.rb +0 -14
  40. data/spec/fetchers/events_spec.rb +0 -14
  41. data/spec/fetchers/groups_spec.rb +0 -14
  42. data/spec/fetchers/members_spec.rb +0 -14
  43. data/spec/fetchers/photos_spec.rb +0 -14
  44. data/spec/fetchers/rsvps_spec.rb +0 -14
  45. data/spec/fetchers/topics_spec.rb +0 -14
  46. data/spec/responses/cities.json +0 -1
  47. data/spec/responses/comments.json +0 -1
  48. data/spec/responses/error.json +0 -1
  49. data/spec/responses/events.json +0 -1
  50. data/spec/responses/groups.json +0 -1
  51. data/spec/responses/members.json +0 -1
  52. data/spec/responses/photos.json +0 -1
  53. data/spec/responses/rsvps.json +0 -1
  54. data/spec/responses/topics.json +0 -1
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d979168eaba97d900401cadf7ef5a2fc603f7e58
4
+ data.tar.gz: 18f87dca4c0d55f225c1f441daca6926b42996a0
5
+ SHA512:
6
+ metadata.gz: 22695b99138b576c50788826ea61fd6d34ed747b47e1e749a8707812e7b04d9e1bfb7d39d6185058c1392be94b385ef4f693d9584e7d7eeb43ed48c278fa7555
7
+ data.tar.gz: a4a6f66269c88ab9433be369612de761cb9ed96e2585c9b46d7b1c8fb93cc2199fec9318a431e6b5c090823da2fe8b20255684aced0601671ad84a56b17fbc4e
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2008-2013 Jared Pace, Jason Berlinsky, Tommy Chan, Tanner Mares, Zishan Ahmad, Nikica Jokić
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
@@ -0,0 +1,65 @@
1
+ rMeetup
2
+ =======
3
+ [![Gem Version](https://badge.fury.io/rb/rMeetup.svg)](http://badge.fury.io/rb/rMeetup)
4
+ [![Build Status](https://travis-ci.org/neektza/rmeetup.svg?branch=master)](https://travis-ci.org/neektza/rmeetup)
5
+ [![Code Climate](https://codeclimate.com/github/neektza/rmeetup.png)](https://codeclimate.com/github/neektza/rmeetup)
6
+ [![Coverage Status](https://coveralls.io/repos/neektza/rmeetup/badge.png?branch=master)](https://coveralls.io/r/neektza/rmeetup?branch=master)
7
+ [![Inline docs](http://inch-ci.org/github/neektza/rmeetup.svg?branch=master)](http://inch-ci.org/github/neektza/rmeetup)
8
+
9
+ A Ruby wrapper for the Meetup REST API v2.
10
+
11
+ Code Sample
12
+ -----------
13
+
14
+ Sample code is worth a thousand words:
15
+
16
+ ```ruby
17
+ client = RMeetup::Client.new do |config|
18
+ config.api_key = "API_KEY"
19
+ end
20
+
21
+ results = client.fetch(:events, { event_id: 'some_id' })
22
+ results.each do |result|
23
+ # Do something with the result
24
+ end
25
+ ```
26
+
27
+ Fetch
28
+ -----
29
+
30
+ RMeetup::Client#fetch takes a data model type and set of options as arguments. Possible data models are:
31
+
32
+ * :events
33
+ * :open_events
34
+ * :groups
35
+ * :rsvps
36
+ * :cities
37
+ * :members
38
+ * :photos
39
+ * :venues
40
+
41
+ The options that may be passed can be found on the Meetup API documentation. Please see http://www.meetup.com/meetup_api/docs/ and look up the model that you are calling (i.e. for :events, look at the API call "GET /2/events" at http://www.meetup.com/meetup_api/docs/2/events/).
42
+
43
+ Post
44
+ ----
45
+
46
+ RMeetup::Client#post takes a data model type and set of options as arguments. Possible data models are:
47
+
48
+ * :event_comment
49
+
50
+ The options that may be passed can be found on the Meetup API documentation. Please see http://www.meetup.com/meetup_api/docs/ and look up the model that you are calling (i.e. for :event_comment, look at the API call ```POST /2/event_comment``` at http://www.meetup.com/meetup_api/docs/2/event_comment).
51
+
52
+ Installation
53
+ ------------
54
+
55
+ ... via Rubygems/Bundler.
56
+
57
+ Credits
58
+ -------
59
+ * [Jared Pace](https://github.com/jdpace/rmeetup) - built initial iteration
60
+ * [Jason Berlinsky](https://github.com/Jberlinsky/rmeetup) - forked, expanded, documented for api v1.0
61
+ * [Tommy Chan](https://github.com/tommytcchan/rmeetup) - added venues, updated to api v2.0
62
+ * [Tanner Mares](https://github.com/tannermares/rmeetup) - check for type in base_url, fix event time and venue, also updated to api v2.0
63
+ * [Joshua Calloway](https://github.com/joshuacalloway/rmeetup) - added post functionality and event comment creation
64
+ * [Zishan Ahmad](https://github.com/zishan/rmeetup) - consolidated changes, updated docs
65
+ * [Nikica Jokić](https://github.com/neektza/rmeetup) - thread-safe client refactoring, setup for TravisCI, CodeClimate, Coveralls...
data/Rakefile CHANGED
@@ -1,14 +1,8 @@
1
- require 'rubygems'
2
- require 'rake'
3
- require 'echoe'
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
4
3
 
5
- Echoe.new('rMeetup','1.0') do |p|
6
- p.description = "A simple Ruby gem, providing access to the Meetup API"
7
- p.url = "https://github.com/Jberlinsky/rmeetup"
8
- p.author = "Jason Berlinsky"
9
- p.email = "jason@jasonberlinsky.com"
10
- p.ignore_pattern = ["tmp/*", "script/*"]
11
- p.development_dependencies = []
12
- end
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ task :test => :spec
13
7
 
14
- Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
8
+ task :default => [:spec]
@@ -0,0 +1,11 @@
1
+ class String
2
+ def snake_case
3
+ self.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").tr(".", "_").downcase
4
+ end
5
+
6
+ def camel_case
7
+ return self if self !~ /_/ && self =~ /[A-Z]+.*/
8
+ split('_').map{|e| e.capitalize}.join
9
+ end
10
+ end
11
+
@@ -2,68 +2,7 @@ require 'net/http'
2
2
  require 'date'
3
3
  require 'rubygems'
4
4
  require 'json'
5
+ require 'rmeetup/client'
5
6
  require 'rmeetup/type'
6
7
  require 'rmeetup/collection'
7
- require 'rmeetup/fetcher'
8
-
9
- module RMeetup
10
-
11
- # RMeetup Errors
12
- class NotConfiguredError < StandardError
13
- def initialize
14
- super "Please provide your Meetup API key before fetching data."
15
- end
16
- end
17
-
18
- class InvalidRequestTypeError < StandardError
19
- def initialize(type)
20
- super "Fetch type '#{type}' not a valid."
21
- end
22
- end
23
-
24
- # == RMeetup::Client
25
- #
26
- # Essentially a simple wrapper to delegate requests to
27
- # different fetcher classes who are responsible for fetching
28
- # and parsing their own responses.
29
- class Client
30
- FETCH_TYPES = [:topics, :cities, :members, :rsvps, :events, :groups, :comments, :photos]
31
-
32
- # Meetup API Key
33
- # Get one at http://www.meetup.com/meetup_api/key/
34
- # Needs to be the group organizers API Key
35
- # to be able to RSVP for other people
36
- @@api_key = nil
37
- def self.api_key; @@api_key; end;
38
- def self.api_key=(key); @@api_key = key; end;
39
-
40
- def self.fetch(type, options = {})
41
- check_configuration!
42
-
43
- # Merge in all the standard options
44
- # Keeping whatever was passed in
45
- options = default_options.merge(options)
46
-
47
- if FETCH_TYPES.include?(type.to_sym)
48
- # Get the custom fetcher used to manage options, api call to get a type of response
49
- fetcher = RMeetup::Fetcher.for(type)
50
- return fetcher.fetch(options)
51
- else
52
- raise InvalidRequestTypeError.new(type)
53
- end
54
- end
55
-
56
- protected
57
- def self.default_options
58
- {
59
- :key => api_key
60
- }
61
- end
62
-
63
- # Raise an error if RMeetup has not been
64
- # provided with an api key
65
- def self.check_configuration!
66
- raise NotConfiguredError.new unless api_key
67
- end
68
- end
69
- end
8
+ require 'rmeetup/version'
@@ -0,0 +1,77 @@
1
+ require 'rmeetup/errors'
2
+ require 'rmeetup/fetcher'
3
+ require 'rmeetup/poster'
4
+
5
+ module RMeetup
6
+
7
+ # == RMeetup::Configuration
8
+ #
9
+ # Holds various configuration data. For now it's
10
+ # only used to hold the API key. You can get one
11
+ # at http://www.meetup.com/meetup_api/key/
12
+ #
13
+ # Needs to be the group organizers API Key
14
+ # to be able to RSVP for other people
15
+ class Configuration
16
+ attr_accessor :api_key, :access_token
17
+ end
18
+
19
+ # == RMeetup::Client
20
+ #
21
+ # Essentially a simple wrapper to delegate requests to
22
+ # different fetcher classes who are responsible for fetching
23
+ # and parsing their own responses.
24
+ class Client
25
+ # Initializes a new Client object
26
+ #
27
+ # @param opts [Hash] Options, currenlty only for providing clinet with the auth data
28
+ # @yield [Configuration], object with accessors :api_key and :access_token, used for providing auth data in a backward compatible way
29
+ # @return [RMeetup::Client]
30
+ def initialize(opts = {})
31
+ if not(opts.empty?)
32
+ configuration.api_key = opts[:api_key] if opts.has_key?(:api_key)
33
+ configuration.access_token = opts[:access_token] if opts.has_key?(:access_token)
34
+ else
35
+ yield(configuration) if block_given?
36
+ end
37
+ check_configuration!
38
+ end
39
+
40
+ # Delegates to appropriate RMeetup::Fetcher
41
+ def fetch(type, options = {})
42
+ RMeetup::Fetcher.for(type).fetch options.merge(auth)
43
+ end
44
+
45
+ # Delegates to appropriate RMeetup::Poster
46
+ def post(type, options = {})
47
+ RMeetup::Poster.for(type).post options.merge(auth)
48
+ end
49
+
50
+ private
51
+ # Creates or returns Client configuration
52
+ #
53
+ # @return [RMeetup::Configuration] Configuration is created and returned during Client initializtion and just returned otherwise
54
+ def configuration
55
+ @configuration ||= Configuration.new
56
+ end
57
+
58
+ # Construct authorization part of the query. Preferes :api_key over :access_token
59
+ #
60
+ # @return [Hash] Authorization part of the query.
61
+ def auth
62
+ if configuration.api_key
63
+ { :key => configuration.api_key, :sign => 'true' }
64
+ elsif configuration.access_token
65
+ { :access_token => configuration.access_token }
66
+ end
67
+ end
68
+
69
+ # Ensures that API key is set during configuration.
70
+ #
71
+ # @raise [RMeetup::Error::ConfigurationError] Error is raised when API key isn't provided
72
+ def check_configuration!
73
+ fail Error::NotConfiguredError.new unless configuration.api_key || configuration.access_token
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,15 @@
1
+ module RMeetup
2
+ module Error
3
+ class NotConfiguredError < StandardError
4
+ def initialize
5
+ super "Please provide your Meetup API key before fetching data."
6
+ end
7
+ end
8
+
9
+ class InvalidRequestTypeError < StandardError
10
+ def initialize(type)
11
+ super "Fetch type '#{type}' not a valid."
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,38 +1,25 @@
1
- require "rmeetup/fetcher/base"
2
- require "rmeetup/fetcher/topics"
3
- require "rmeetup/fetcher/cities"
4
- require "rmeetup/fetcher/members"
5
- require "rmeetup/fetcher/rsvps"
6
- require "rmeetup/fetcher/events"
7
- require "rmeetup/fetcher/groups"
8
- require "rmeetup/fetcher/comments"
9
- require "rmeetup/fetcher/photos"
1
+ require 'core_ext/string'
2
+ require 'rmeetup/fetcher/base'
3
+ require 'rmeetup/fetcher/cities'
4
+ require 'rmeetup/fetcher/members'
5
+ require 'rmeetup/fetcher/rsvps'
6
+ require 'rmeetup/fetcher/events'
7
+ require 'rmeetup/fetcher/open_events'
8
+ require 'rmeetup/fetcher/groups'
9
+ require 'rmeetup/fetcher/photos'
10
+ require 'rmeetup/fetcher/venues'
10
11
 
11
12
  module RMeetup
12
13
  module Fetcher
13
-
14
- class << self
15
- # Return a fetcher for given type
16
- def for(type)
17
- return case type.to_sym
18
- when :topics
19
- Topics.new
20
- when :cities
21
- Cities.new
22
- when :members
23
- Members.new
24
- when :rsvps
25
- Rsvps.new
26
- when :events
27
- Events.new
28
- when :groups
29
- Groups.new
30
- when :comments
31
- Comments.new
32
- when :photos
33
- Photos.new
34
- end
35
- end
14
+
15
+ def self.for(type)
16
+ name = type.to_s.camel_case.to_sym
17
+ if (name && constants.include?(name))
18
+ const_get(name).new
19
+ else
20
+ raise RMeetup::Error::InvalidRequestTypeError.new(type)
21
+ end
36
22
  end
23
+
37
24
  end
38
- end
25
+ end
@@ -1,5 +1,7 @@
1
1
  module RMeetup
2
2
  module Fetcher
3
+ Domain = "api.meetup.com"
4
+
3
5
  class ApiError < StandardError
4
6
  def initialize(error_message, request_url)
5
7
  super "Meetup API Error: #{error_message} - API URL: #{request_url}"
@@ -11,74 +13,83 @@ module RMeetup
11
13
  super "No Response was returned from the Meetup API."
12
14
  end
13
15
  end
14
-
16
+
15
17
  # == RMeetup::Fetcher::Base
16
- #
17
- # Base fetcher class that other fetchers
18
- # will inherit from.
18
+ #
19
+ # Base fetcher class that other fetchers will inherit from.
19
20
  class Base
20
21
  def initialize
21
22
  @type = nil
22
23
  end
23
-
24
- # Fetch and parse a response
25
- # based on a set of options.
26
- # Override this method to ensure
27
- # neccessary options are passed
28
- # for the request.
24
+
25
+ # Fetch and parse a response based on a set of options. Override this method to ensure neccessary options are passed for the request.
26
+ #
27
+ # @param options [Hash] Options for deciding what and how to fetch
28
+ # @return [RMeetup::Collection] Collection of appropriate entities
29
29
  def fetch(options = {})
30
- url = build_url(options)
31
-
32
- json = get_response(url)
33
- data = JSON.parse(json)
34
-
30
+ raise NotConfiguredError, /fetches only possible with a concrete fetcher/ if @type.nil?
31
+ path = path_and_query(options)
32
+
33
+ # Fetch and parse data from Meetup
34
+ response_body = requester(options).get(path).body || raise(NoResponseError.new)
35
+ data = JSON.parse(response_body)
36
+
35
37
  # Check to see if the api returned an error
36
- raise ApiError.new(data['details'],url) if data.has_key?('problem')
37
-
38
+ raise ApiError.new(data['details'],path) if data.has_key?('problem')
39
+
38
40
  collection = RMeetup::Collection.build(data)
39
-
41
+
40
42
  # Format each result in the collection and return it
41
43
  collection.map!{|result| format_result(result)}
42
44
  end
43
45
 
44
- protected
45
- # OVERRIDE this method to format a result section
46
- # as per Result type.
47
- # Takes a result in a collection and
48
- # formats it to be put back into the collection.
49
- def format_result(result)
50
- result
51
- end
52
-
53
- def build_url(options)
54
- options = encode_options(options)
55
-
56
- base_url + params_for(options)
57
- end
46
+ def path_and_query(options)
47
+ base_path + query(options)
48
+ end
49
+
50
+ def base_path
51
+ "/2/#{@type}"
52
+ end
53
+
54
+ # Create a query string from an options hash
55
+ def query(options)
56
+ '?' + URI.encode_www_form(options)
57
+ end
58
58
 
59
- def base_url
60
- "http://api.meetup.com/#{@type}.json/"
61
- end
62
-
63
- # Create a query string from an options hash
64
- def params_for(options)
65
- params = []
66
- options.each do |key, value|
67
- params << "#{key}=#{value}"
68
- end
69
- "?#{params.join("&")}"
70
- end
71
-
72
- # Encode a hash of options to be used as request parameters
73
- def encode_options(options)
74
- options.each do |key,value|
75
- options[key] = URI.encode(value.to_s)
76
- end
77
- end
78
-
79
- def get_response(url)
80
- Net::HTTP.get_response(URI.parse(url)).body || raise(NoResponseError.new)
59
+ # Decides whether to use HTTP or HTTPS. HTTPS is needed if we're authoizing via the :access_token
60
+ #
61
+ # @param q [Hash] Constructed HTTP(S) query
62
+ # @return [Net:HTTP] Constructs an HTTP request if client is given an API key
63
+ # @return [Net:HTTPS] Constructs an HTTPS request if client is given an access token
64
+ def requester(q)
65
+ if q.has_key?(:api_key)
66
+ http
67
+ elsif q.has_key?(:access_token)
68
+ https
69
+ else
70
+ http
81
71
  end
72
+ end
73
+
74
+ protected
75
+ # OVERRIDE this method to format a result section
76
+ # as per Result type.
77
+ # Takes a result in a collection and
78
+ # formats it to be put back into the collection.
79
+ def format_result(result)
80
+ result
81
+ end
82
+
83
+ def http
84
+ Net::HTTP.new Domain, 80
85
+ end
86
+
87
+ def https
88
+ c = Net::HTTP.new Domain, 443
89
+ c.use_ssl = true
90
+ c
91
+ end
92
+
82
93
  end
83
94
  end
84
- end
95
+ end