rMeetup 1.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
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