rubeetup 0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6e43ac5b43c6a21aa36bd9be908cafe94977ad7b
4
+ data.tar.gz: 63a251ccdb7a80f09835a247797d9f4f33af57b9
5
+ SHA512:
6
+ metadata.gz: f8528053c9beea9268370c27e5b4add52fff3f3094a0bdc9a06f3d3784532a638559f2341dcc4df2cf05788abfe117e7e9fbb274694244afd2eca12069aaaf16
7
+ data.tar.gz: 5bb0bb297c6a36bf1bd80727f373272b31cf78904c2e84ab841e6f1fab10a5fe18849ade6cef722035ce9d8ee159345ee66b2bea4e9c9717204ce67ce958413b
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /.idea/
11
+ /.coveralls.yml
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.1
4
+ - 2.2
5
+ - 2.1
6
+ - 2.0.0
7
+ - 1.9.3
8
+ - 1.9.2
9
+ before_install: gem install bundler -v 1.10.4
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rubeetup.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,173 @@
1
+ <a href="https://travis-ci.org/mike-vascelli/rubeetup"><img src="https://travis-ci.org/mike-vascelli/rubeetup.svg?branch=master"/></a>
2
+ <a href="https://codeclimate.com/github/mike-vascelli/rubeetup"><img src="https://codeclimate.com/github/mike-vascelli/rubeetup/badges/gpa.svg" /></a>
3
+ <a href='https://gemnasium.com/mike-vascelli/rubeetup'><img src="https://gemnasium.com/mike-vascelli/rubeetup.svg" alt="Dependency Status" /></a>
4
+ <a href='https://coveralls.io/r/mike-vascelli/rubeetup'><img src='https://coveralls.io/repos/mike-vascelli/rubeetup/badge.svg' alt='Coverage Status' /></a>
5
+ <a href="https://inch-ci.org/github/mike-vascelli/rubeetup"><img src="http://inch-ci.org/github/mike-vascelli/rubeetup.svg?branch=master"/></a>
6
+
7
+
8
+ # Rubeetup
9
+
10
+ ## Overview
11
+
12
+ This gem allows you to interact with the `Meetup.com` API in a simple and direct way. It gives you access to all of the available API methods (full `CRUD`), and it also makes sure that all the required parameters are provided with each request.
13
+
14
+ For more information on the Meetup API itself, visit: http://www.meetup.com/meetup_api
15
+
16
+
17
+ ### Requests' Naming Convention
18
+
19
+ They begin with the action to perform on a resource: (i.e. `create`, `get`, `edit`, `delete`) and are followed by underscore, and then the name of the resource requested (as in the Meetup API).
20
+ #### Eg.
21
+ http://www.meetup.com/meetup_api/docs/2/open_events/
22
+
23
+ resource: open_events
24
+
25
+ action: get
26
+
27
+ request name: get_open_events
28
+
29
+ All the requests on Rubeetup follow this scheme.
30
+
31
+
32
+ ## Supported Requests
33
+
34
+ get_open_events, get_concierge, get_events, create_event, get_event, edit_event, delete_event, get_event_comments, create_event_comment, get_event_comment, delete_event_comment, create_event_comment_flag, create_event_comment_subscribe, delete_event_comment_subscribe, create_event_comment_like, delete_event_comment_like, get_event_comment_likes, get_event_ratings, create_event_rating, create_attendance, get_attendance, create_event_payments, create_watchlist, delete_watchlist, get_boards, get_discussions, get_discussion_posts, get_categories, get_cities, get_dashboard, get_activity, get_groups, get_comments, create_group_photo, get_find_groups, get_group, edit_group, create_group_topics, delete_group_topics, get_recommended_groups, create_recommended_groups_ignores, get_similar_groups, get_members, get_member, edit_member, delete_member_photo, create_member_photo, get_status, get_notifications, create_notifications_read, get_oembed, delete_photo, get_photo_comments, create_photo_comment, get_photo_albums, get_photos, create_photo_album, create_photo, get_profiles, create_profile, edit_profile, get_profile, delete_profile, create_member_approvals, delete_member_approvals, get_rsvps, create_rsvp, edit_rsvp, get_rsvp, get_topic_categories, get_topics, get_recommended_group_topics, get_open_venues, get_venues, get_group_venues, get_recommended_venues, create_venue
35
+
36
+ ### Authorization
37
+ Rubeetup will need a Meetup api key to successfully perform the requests. This can be obtained by signing-up at http://www.meetup.com, and by requesting a key for your account.
38
+ Note that in order to `edit`, `create`, or `delete` any resource in the API, you must first obtain the privileges to do those actions. This usually means becoming an organizer for a group. This can be achieved by either starting a meetup group, or by being granted the authorization by a current organizer of a group. If you want to experiment with the API or simply do some live testing, then you can request to become an organizer for the <tt>Meetup API Testing Sandbox Group</tt>.
39
+
40
+ Visit this link http://www.meetup.com/Meetup-API-Testing for more information.
41
+
42
+ ## Usage
43
+
44
+ ```ruby
45
+ auth = key: 'val'
46
+
47
+ # If you wish to provide auth data for each instance,
48
+ # then pass it to the Rubeetup.setup call as an argument:
49
+ #
50
+ requester = Rubeetup.setup(auth)
51
+
52
+ # Otherwise you can pass default auth data once and be done with it:
53
+ #
54
+ Rubeetup.default_auth(auth)
55
+ requester = Rubeetup.setup
56
+
57
+
58
+ # Each request will take a Hash of options.
59
+ # Check in rubeetup/requests_lib/meetup_catalog.rb, or in the
60
+ # MeetupCatalog documentation, or at: http://www.meetup.com/meetup_api
61
+ # for a list of required and available parameters for your specific request.
62
+ #
63
+ # Rubeetup will raise an Error if any request is attempted without
64
+ # providing all the required parameters.
65
+ #
66
+ events = requester.get_events(group_urlname: 'Meetup-API-Testing')
67
+
68
+
69
+ # Every request returns an array of results, and you can safely iterate over them.
70
+ # Furthermore, each result stored in the array is a symbol-keyed Hash.
71
+ #
72
+ events.each do |event|
73
+ # Each element is a Hash, so you can simply do:
74
+ puts event[:name]
75
+
76
+ # But Rubeetup also allows you to access elements' keys in OO-style:
77
+ puts event.name, event.time, event.duration, ...
78
+
79
+ venue = event.venue
80
+ puts venue[:address_1]
81
+ puts venue[:city]
82
+ end
83
+
84
+ pic = requester.create_member_photo(photo: 'spec/test_files/cat.png').first
85
+ puts pic.photo_url, pic.member_photo_id
86
+
87
+ event = requester.create_event(group_id: testing_group_id,
88
+ group_urlname: testing_group_urlname,
89
+ name: 'test').first
90
+ puts event.id
91
+ ```
92
+ Take a look at the file `spec/lib/integration_spec.rb` to see examples for all the requests
93
+
94
+ To check each request's own required parameters:
95
+ Look in `lib/rubeetup/requests_lib/meetup_catalog.json`, or at: http://www.meetup.com/meetup_api
96
+
97
+ To find out about all the available response attributes visit the Meetup API documentation:
98
+ http://www.meetup.com/meetup_api
99
+
100
+ For example, for the `events` API request in the `Usage` example above, check this link:
101
+ http://www.meetup.com/meetup_api/docs/2/events
102
+
103
+
104
+
105
+
106
+
107
+
108
+ ## Installation
109
+
110
+ Add this line to your application's Gemfile:
111
+
112
+ ```ruby
113
+ gem 'rubeetup'
114
+ ```
115
+
116
+ And then execute:
117
+
118
+ $ bundle
119
+
120
+ Or install it yourself as:
121
+
122
+ $ gem install rubeetup
123
+
124
+ ## Ruby Interpreter Compatibility
125
+
126
+ Rubeetup has been tested on the following ruby interpreters:
127
+
128
+ - 2.2.1
129
+ - 2.2
130
+ - 2.1
131
+ - 2.0.0
132
+ - 1.9.3
133
+ - 1.9.2
134
+
135
+ ## Runtime Dependencies
136
+
137
+ - multipart-post
138
+
139
+ ## Development
140
+
141
+ After checking out the repo, run `bin/setup`, or `bundle install` to install dependencies. Then, run `rake` or `rspec` to run the tests. You can also run `bin/console` or `bundle console` for an interactive prompt that will allow you to experiment.
142
+
143
+ To install this gem onto your local machine, run `bundle exec rake install`.
144
+
145
+ ## Contributing
146
+
147
+ Bug reports are more than welcome! Here's the link: https://github.com/mike-vascelli/rubeetup/issues
148
+
149
+
150
+ ## License
151
+
152
+ The MIT License (MIT)
153
+
154
+ Copyright (c) 2015 Mike Vascelli
155
+
156
+ Permission is hereby granted, free of charge, to any person obtaining a copy
157
+ of this software and associated documentation files (the "Software"), to deal
158
+ in the Software without restriction, including without limitation the rights
159
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
160
+ copies of the Software, and to permit persons to whom the Software is
161
+ furnished to do so, subject to the following conditions:
162
+
163
+ The above copyright notice and this permission notice shall be included in
164
+ all copies or substantial portions of the Software.
165
+
166
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
167
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
168
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
169
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
170
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
171
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
172
+ THE SOFTWARE.
173
+
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task default: :spec
7
+ task test: :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "rubeetup"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,32 @@
1
+ module Rubeetup
2
+ ##
3
+ # Raised when the user attempts to create an instance of Requester with
4
+ # invalid auth data
5
+ #
6
+ InvalidAuthenticationError = Class.new(StandardError)
7
+
8
+ ##
9
+ # Raised when the user attempts to create an instance of Request, but the
10
+ # request either does not exist, or it has one or more missing arguments
11
+ #
12
+ RequestError = Class.new(StandardError)
13
+
14
+ ##
15
+ # Custom Error to wrap Rubeetup::RequestResponse objects in case of error
16
+ # while performing a request
17
+ #
18
+ class MeetupResponseError < StandardError
19
+
20
+ ##
21
+ # @return [Rubeetup::RequestResponse] the failed request's response
22
+ #
23
+ attr_reader :response
24
+
25
+ ##
26
+ # @param [Rubeetup::RequestResponse] response the failed request's response
27
+ #
28
+ def initialize(response)
29
+ @response = response
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,133 @@
1
+ require 'set'
2
+
3
+ module Rubeetup
4
+ ##
5
+ # Represents API requests. Provides for their own validation and execution
6
+ #
7
+ class Request
8
+ include Rubeetup::RequestsCatalog
9
+ include Rubeetup::Utilities
10
+
11
+ ##
12
+ # @return [Symbol] name the name of the request as input by the user
13
+ #
14
+ attr_reader :name
15
+
16
+ ##
17
+ # @return [Symbol] http_verb the http_verb for the request
18
+ #
19
+ attr_reader :http_verb
20
+
21
+ ##
22
+ # @return [String] method_path the path of the Meetup API resource
23
+ #
24
+ attr_reader :method_path
25
+
26
+ ##
27
+ # @return [Hash{Symbol=>String}] options holds the request's options
28
+ #
29
+ attr_reader :options
30
+
31
+ ##
32
+ # @return [Rubeetup::Sender] sender the request's chosen sender
33
+ #
34
+ attr_reader :sender
35
+
36
+ ##
37
+ # @return [Lambda] multipart if present it contains the multipart POST logic
38
+ #
39
+ attr_reader :multipart
40
+
41
+ ##
42
+ # @param [Hash{Symbol=>String}] args holds the request's data
43
+ # @option args [Symbol] :name the full request's name
44
+ # @option args [Hash{Symbol=>String}] :options holds the request's options
45
+ # @option args [Symbol] :http_verb the request's http_verb
46
+ #
47
+ def initialize(args = {})
48
+ @name = args[:name]
49
+ @options = args[:options]
50
+ validate_request
51
+ @http_verb = args[:http_verb]
52
+ @method_path = request_path.call(@options)
53
+ @multipart = request_multipart
54
+ @sender = Rubeetup::RequestSender.new
55
+ end
56
+
57
+ ##
58
+ # Completes this request
59
+ # @return [Array<Rubeetup::ResponseWrapper>] the request's response
60
+ #
61
+ def execute
62
+ sender.get_response(self)
63
+ end
64
+
65
+ ##
66
+ # For debugging purposes
67
+ #
68
+ def to_s
69
+ <<-DOC.gsub(/^ {8}/, '')
70
+ \nREQUEST
71
+ name => #{name}
72
+ verb => #{http_verb}
73
+ path => #{method_path}
74
+ options => #{options.inspect}\n
75
+ DOC
76
+ end
77
+
78
+ private
79
+
80
+ def validate_request
81
+ verify_existence
82
+ validate_options
83
+ end
84
+
85
+ def verify_existence
86
+ fail error_class, existence_message unless is_in_catalog?
87
+ end
88
+
89
+ def validate_options
90
+ fail error_class, options_message unless has_all_required_options?
91
+ end
92
+
93
+ ##
94
+ # Uses sets to make sure that there is at least a set of required parameters
95
+ # which is a subset of the arguments passed to the request
96
+ # @return [Boolean] whether all the required arguments have been passed
97
+ #
98
+ def has_all_required_options?
99
+ required_keys_sets = required_options.map do |elem|
100
+ elem.respond_to?(:to_set) ? elem.to_set : Set[elem]
101
+ end
102
+ return true if required_keys_sets.empty?
103
+ option_keys_set = options.keys.to_set
104
+ required_keys_sets.any? { |set| set.subset? option_keys_set }
105
+ end
106
+
107
+ def existence_message
108
+ <<-DOC.gsub(/^ {8}/, '')
109
+ The provided request => '#{name}' is an invalid request.
110
+ This request does not exist in the catalog of supported requests.
111
+ Please consult rubeetup/requests_lib/meetup_catalog.rb, or the provided
112
+ documentation for the complete list of requests.
113
+ DOC
114
+ end
115
+
116
+ def options_message
117
+ <<-DOC.gsub(/^ {8}/, '')
118
+ This request cannot be completed as is.
119
+ The provided arguments => '#{options.inspect}' miss one or more
120
+ required parameters.
121
+ The request '#{name}' requires the following parameters:
122
+ '#{required_options.inspect}'
123
+ Please consult rubeetup/requests_lib/meetup_catalog.rb, or the provided
124
+ documentation for the complete list of requests, and their respective
125
+ required parameters.
126
+ DOC
127
+ end
128
+
129
+ def error_class
130
+ Rubeetup::RequestError
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,68 @@
1
+ module Rubeetup
2
+ ##
3
+ # Allows a Requester object to compose a Request instance with the passed data
4
+ #
5
+ module RequestBuilder
6
+ class << self
7
+ ##
8
+ # Creates an instance of the chosen request type
9
+ # @note only preliminary error checking on the request is performed here.
10
+ # @param [Symbol] name the request's name
11
+ # @param [Hash{Symbol=>String}] args holds the request's options
12
+ # @return [Rubeetup::Request] the newly created Request instance
13
+ #
14
+ def compose_request(name, args)
15
+ verb = get_verb(name)
16
+ validate_verb(verb)
17
+ request_type.new(
18
+ name: name,
19
+ http_verb: infer_http_verb(verb),
20
+ options: args.first
21
+ )
22
+ end
23
+
24
+ private
25
+
26
+ def request_type
27
+ Rubeetup::Request
28
+ end
29
+
30
+ def verbs
31
+ { create: :post, get: :get, edit: :post, delete: :delete }
32
+ end
33
+
34
+ ##
35
+ # Parses the request name, and attempts to split it around the leftmost
36
+ # underscore char. If the name is properly formed, then the left half
37
+ # is the verb of the request
38
+ # @param [Symbol] name the request name
39
+ # @return [Symbol] if name contains the required underscore char
40
+ # @return [nil] if name is invalid
41
+ #
42
+ def get_verb(name)
43
+ pos = (name =~ /_/)
44
+ pos ? name[0...pos].to_sym : nil
45
+ end
46
+
47
+ def validate_verb(verb)
48
+ valid_verbs = verbs.keys
49
+ fail Rubeetup::RequestError, error_message(verb, valid_verbs) unless
50
+ valid_verbs.include? verb
51
+ end
52
+
53
+ def error_message(verb, valid_verbs)
54
+ <<-DOC.gsub(/^ {10}/, '')
55
+ '#{verb}' is an invalid method.
56
+ The only available requests must begin with any of:
57
+ #{valid_verbs.join(', ')}
58
+ Followed by underscore, and a Meetup request name.
59
+ Consult the documentation for a complete list of available requests.
60
+ DOC
61
+ end
62
+
63
+ def infer_http_verb(verb)
64
+ verbs[verb]
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,88 @@
1
+ require 'json'
2
+
3
+ module Rubeetup
4
+ ##
5
+ # Packages a response, and provides for its validation
6
+ #
7
+ class RequestResponse
8
+ include Rubeetup::Utilities
9
+
10
+ ##
11
+ # @return [Net::HTTPResponse] response the raw response from the sender
12
+ #
13
+ attr_reader :response
14
+
15
+ ##
16
+ # @return [Hash{Symbol=>...}] parsed_body the JSON-parsed body of the response.
17
+ # It is a Hash with all the keys as Symbols
18
+ #
19
+ attr_reader :parsed_body
20
+
21
+ ##
22
+ # @return [Rubeetup::Request] the request which caused this response
23
+ #
24
+ attr_reader :request
25
+
26
+ ##
27
+ # @param [Net::HTTPResponse] raw_data the raw response from the sender
28
+ #
29
+ def initialize(sender)
30
+ @response = sender.response_data
31
+ @request = sender.request
32
+ body = @response.body
33
+ @parsed_body = blank?(body) ? [] : parse(body)
34
+ end
35
+
36
+ ##
37
+ # If the request was successful, then creates a collection of
38
+ # Rubeetup::ResponseWrapper instances.
39
+ # @return [Array<Rubeetup::ResponseWrapper>] a collection containing the response's data
40
+ #
41
+ def data
42
+ fail error_class.new(self), error_message unless success?
43
+ collection = collectionize(parsed_body)
44
+ collection.map {|elem| wrapper_class.new(elem)}
45
+ end
46
+
47
+ # Consider implementing pagination
48
+ #def prev; end
49
+ #def next; end
50
+ #def all; end # Calls #next until no more and returns all the results at once
51
+
52
+ private
53
+
54
+ def success?
55
+ response.is_a? Net::HTTPSuccess
56
+ end
57
+
58
+ def error_class
59
+ Rubeetup::MeetupResponseError
60
+ end
61
+
62
+ def wrapper_class
63
+ Rubeetup::ResponseWrapper
64
+ end
65
+
66
+ def collectionize(data)
67
+ return data if data.is_a? Array
68
+ data[:results] || [data]
69
+ end
70
+
71
+ def parse(body)
72
+ begin
73
+ JSON.parse(body, symbolize_names: true)
74
+ rescue
75
+ body
76
+ end
77
+ end
78
+
79
+ def error_message
80
+ <<-DOC.gsub(/^ {8}/, '')
81
+ An error was encountered while processing the following request:
82
+ #{request}
83
+ Here's some information to describe the error, and/or its causes:
84
+ #{parsed_body}
85
+ DOC
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,93 @@
1
+ require 'net/http/post/multipart'
2
+
3
+ module Rubeetup
4
+ ##
5
+ # Responsible for sending responses over an http connection
6
+ #
7
+ class RequestSender
8
+ include Rubeetup::Utilities
9
+
10
+ ##
11
+ # Destination host
12
+ #
13
+ HOST = 'api.meetup.com'
14
+
15
+ ##
16
+ # @return [Net::HTTP] this Sender's http connection
17
+ #
18
+ attr_reader :http
19
+
20
+ ##
21
+ # @return [Rubeetup::Request] this Sender's request job
22
+ #
23
+ attr_reader :request
24
+
25
+ ##
26
+ # @return [Net::HTTPResponse] the response data obtained from the request
27
+ #
28
+ attr_reader :response_data
29
+
30
+ def initialize
31
+ @http = Net::HTTP.new(HOST)
32
+ end
33
+
34
+ ##
35
+ # Performs a request and returns back the response
36
+ # @param [Rubeetup::Request] request the request instance to be sent
37
+ # @return [Array<Rubeetup::ResponseWrapper>] the request response
38
+ #
39
+ def get_response(request)
40
+ @request = request
41
+ @response_data = fetch
42
+ response_class.new(self).data
43
+ end
44
+
45
+ private
46
+
47
+ def fetch
48
+ request.http_verb == :post ? (req = post) : (req = get_or_delete)
49
+ http.request(req)
50
+ end
51
+
52
+ def get_or_delete
53
+ path = "#{request.method_path}?#{stringify(request.options)}"
54
+ http_method_class.new(path)
55
+ end
56
+
57
+ def post
58
+ request.multipart ? multipart_post : url_encoded_post
59
+ end
60
+
61
+ def url_encoded_post
62
+ req = http_method_class.new(request.method_path)
63
+ req.set_form_data(request.options)
64
+ req
65
+ end
66
+
67
+ def multipart_post
68
+ encode_resources
69
+ http_method_class.new(request.method_path, request.options)
70
+ end
71
+
72
+ def encode_resources
73
+ request.multipart.call(request.options)
74
+ end
75
+
76
+ def http_method_class
77
+ class_name = request.http_verb.capitalize.to_s
78
+ if request.multipart
79
+ Net::HTTP::Post::Multipart
80
+ else
81
+ Net::HTTP.const_get(class_name.to_sym)
82
+ end
83
+ end
84
+
85
+ def response_class
86
+ Rubeetup::RequestResponse
87
+ end
88
+
89
+ def handler_class
90
+ Typhoeus::Request
91
+ end
92
+ end
93
+ end