perfect_reach 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.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +37 -0
- data/Rakefile +8 -0
- data/config.yml +8 -0
- data/lib/perfect_reach.rb +18 -0
- data/lib/perfect_reach/api.rb +35 -0
- data/lib/perfect_reach/api_error.rb +19 -0
- data/lib/perfect_reach/api_request.rb +66 -0
- data/lib/perfect_reach/configuration.rb +14 -0
- data/lib/perfect_reach/contact.rb +17 -0
- data/lib/perfect_reach/contact_list.rb +66 -0
- data/lib/perfect_reach/version.rb +3 -0
- data/perfect_reach.gemspec +29 -0
- data/test/minitest_helper.rb +19 -0
- data/test/perfect_reach/api_request_test.rb +43 -0
- data/test/perfect_reach/api_test.rb +12 -0
- data/test/perfect_reach/contact_list_test.rb +73 -0
- data/test/perfect_reach/contact_test.rb +20 -0
- data/test/perfect_reach_test.rb +17 -0
- metadata +207 -0
    
        data/.gitignore
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    
    
        data/LICENSE.txt
    ADDED
    
    | @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            Copyright (c) 2013 Mike Petersen
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            MIT License
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining
         | 
| 6 | 
            +
            a copy of this software and associated documentation files (the
         | 
| 7 | 
            +
            "Software"), to deal in the Software without restriction, including
         | 
| 8 | 
            +
            without limitation the rights to use, copy, modify, merge, publish,
         | 
| 9 | 
            +
            distribute, sublicense, and/or sell copies of the Software, and to
         | 
| 10 | 
            +
            permit persons to whom the Software is furnished to do so, subject to
         | 
| 11 | 
            +
            the following conditions:
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            The above copyright notice and this permission notice shall be
         | 
| 14 | 
            +
            included in all copies or substantial portions of the Software.
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         | 
| 17 | 
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         | 
| 18 | 
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         | 
| 19 | 
            +
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         | 
| 20 | 
            +
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
         | 
| 21 | 
            +
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
         | 
| 22 | 
            +
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            # PerfectReach
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            TODO: Write a gem description
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            ## Installation
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            Add this line to your application's Gemfile:
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                gem 'PerfectReach'
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            And then execute:
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                $ bundle
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            Or install it yourself as:
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                $ gem install PerfectReach
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            ## Usage
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            TODO: Write usage instructions here
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            Configuration:
         | 
| 24 | 
            +
            PerfectReach.configure do |config|
         | 
| 25 | 
            +
            	config.api_key = 'YOUR_API_KEY'
         | 
| 26 | 
            +
            	config.host = api.perfect-reach.com
         | 
| 27 | 
            +
            	config.port = 443
         | 
| 28 | 
            +
            	config.use_https = true
         | 
| 29 | 
            +
            end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            ## Contributing
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            1. Fork it
         | 
| 34 | 
            +
            2. Create your feature branch (`git checkout -b my-new-feature`)
         | 
| 35 | 
            +
            3. Commit your changes (`git commit -am 'Add some feature'`)
         | 
| 36 | 
            +
            4. Push to the branch (`git push origin my-new-feature`)
         | 
| 37 | 
            +
            5. Create new Pull Request
         | 
    
        data/Rakefile
    ADDED
    
    
    
        data/config.yml
    ADDED
    
    
| @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            require 'active_support'
         | 
| 2 | 
            +
            require "perfect_reach/version"
         | 
| 3 | 
            +
            require "perfect_reach/api"
         | 
| 4 | 
            +
            require "perfect_reach/api_error"
         | 
| 5 | 
            +
            require "perfect_reach/api_request"
         | 
| 6 | 
            +
            require "perfect_reach/configuration"
         | 
| 7 | 
            +
            require "perfect_reach/contact"
         | 
| 8 | 
            +
            require "perfect_reach/contact_list"
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            module PerfectReach
         | 
| 11 | 
            +
            	def self.configure
         | 
| 12 | 
            +
            		yield PerfectReach::Configuration
         | 
| 13 | 
            +
            	end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            	def self.config
         | 
| 16 | 
            +
            		PerfectReach::Configuration
         | 
| 17 | 
            +
            	end
         | 
| 18 | 
            +
            end
         | 
| @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            module PerfectReach
         | 
| 2 | 
            +
            	class Api
         | 
| 3 | 
            +
            		include Singleton
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            		def initialize(api_key = PerfectReach.config.api_key)
         | 
| 6 | 
            +
            			@api_key = api_key
         | 
| 7 | 
            +
            		end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            		def method_missing(method_name, *args, &block)
         | 
| 10 | 
            +
            			params, request_type = args
         | 
| 11 | 
            +
            			request = ApiRequest.new(method_name, params, request_type, @api_key)
         | 
| 12 | 
            +
            			request.response
         | 
| 13 | 
            +
            		end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            		def get(method_name, params)
         | 
| 16 | 
            +
            			request = ApiRequest.new(method_name, params, 'get', @api_key)
         | 
| 17 | 
            +
            			request.response
         | 
| 18 | 
            +
            		end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            		def post(method_name, params)
         | 
| 21 | 
            +
            			request = ApiRequest.new(method_name, params, 'post', @api_key)
         | 
| 22 | 
            +
            			request.response
         | 
| 23 | 
            +
            		end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            		def put(method_name, params)
         | 
| 26 | 
            +
            			request = ApiRequest.new(method_name, params, 'put', @api_key)
         | 
| 27 | 
            +
            			request.response
         | 
| 28 | 
            +
            		end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            		def delete(method_name, params)
         | 
| 31 | 
            +
            			request = ApiRequest.new(method_name, params, 'delete', @api_key)
         | 
| 32 | 
            +
            			request.response
         | 
| 33 | 
            +
            		end
         | 
| 34 | 
            +
            	end
         | 
| 35 | 
            +
            end
         | 
| @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
            require 'active_support'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module PerfectReach
         | 
| 5 | 
            +
            	class ApiError < StandardError
         | 
| 6 | 
            +
            		def initialize(code, res, request, request_path, params)
         | 
| 7 | 
            +
            			# code is ugly, output is pretty
         | 
| 8 | 
            +
            			super("error #{code} while sending #{request.inspect} to #{request_path} with #{params.inspect}\n\n" +
         | 
| 9 | 
            +
            						(res['errors'].present? ?
         | 
| 10 | 
            +
            							[(res['errors'] || [])].flatten.map do |param, text|
         | 
| 11 | 
            +
            								[param, text].map(&:to_s).reject(&:blank?).join(': ')
         | 
| 12 | 
            +
            							end.join("\n") :
         | 
| 13 | 
            +
            							res.inspect
         | 
| 14 | 
            +
            						) +
         | 
| 15 | 
            +
            						"\n\nPlease see http://api.perfect-reach.com for more informations on error numbers.\n\n"
         | 
| 16 | 
            +
            			)
         | 
| 17 | 
            +
            		end
         | 
| 18 | 
            +
            	end
         | 
| 19 | 
            +
            end
         | 
| @@ -0,0 +1,66 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
            require 'active_support/core_ext/string'
         | 
| 3 | 
            +
            require 'net/http'
         | 
| 4 | 
            +
            require "net/https"
         | 
| 5 | 
            +
            require 'json'
         | 
| 6 | 
            +
            require 'cgi'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            module PerfectReach
         | 
| 9 | 
            +
            	class ApiRequest
         | 
| 10 | 
            +
            		def initialize(method_name, params = {}, request_type = nil, auth_token = PerfectReach.config.api_key, host = PerfectReach.config.host, port = PerfectReach.config.port)
         | 
| 11 | 
            +
            			@method_name = method_name.to_s
         | 
| 12 | 
            +
            			@params = (params || {}).merge(:format => 'json')
         | 
| 13 | 
            +
            			@request_type = (request_type || guess_request_type).camelize
         | 
| 14 | 
            +
            			@auth_token = auth_token
         | 
| 15 | 
            +
            			@host = host
         | 
| 16 | 
            +
            			@port = port
         | 
| 17 | 
            +
            		end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            		def response
         | 
| 20 | 
            +
            			@response ||= begin
         | 
| 21 | 
            +
            				http = Net::HTTP.new(@host, @port)
         | 
| 22 | 
            +
            				http.use_ssl = PerfectReach.config.use_https
         | 
| 23 | 
            +
            				res = http.request(request)
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            				case res
         | 
| 26 | 
            +
            					when Net::HTTPSuccess
         | 
| 27 | 
            +
            						JSON.parse(res.body || '{}')
         | 
| 28 | 
            +
            					when Net::HTTPNotModified
         | 
| 29 | 
            +
            						{"status" => "not_modified"}
         | 
| 30 | 
            +
            					else
         | 
| 31 | 
            +
            						raise ApiError.new(res.code, JSON.parse(res.body.presence || '{}'), request, request_path, @params)
         | 
| 32 | 
            +
            				end
         | 
| 33 | 
            +
            			end
         | 
| 34 | 
            +
            		end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            		private
         | 
| 37 | 
            +
            		def request
         | 
| 38 | 
            +
            			@request ||= begin
         | 
| 39 | 
            +
            				req = "Net::HTTP::#{@request_type}".constantize.new(request_path)
         | 
| 40 | 
            +
            				Net::HTTP::Get
         | 
| 41 | 
            +
            				req.add_field 'Authorization', "Token token=\"#{@auth_token}\""
         | 
| 42 | 
            +
            				req.add_field 'X-Api-Version', PerfectReach.config.api_version
         | 
| 43 | 
            +
            				req.set_form_data(@params)
         | 
| 44 | 
            +
            				req
         | 
| 45 | 
            +
            			end
         | 
| 46 | 
            +
            		end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            		def request_path
         | 
| 49 | 
            +
            			@request_path ||= begin
         | 
| 50 | 
            +
            				path = "/api/#{@method_name}"
         | 
| 51 | 
            +
            				if @request_type == 'Get'
         | 
| 52 | 
            +
            					path << '?' + @params.collect { |k, v| "#{k}=#{CGI::escape(v.to_s)}" }.join('&')
         | 
| 53 | 
            +
            				end
         | 
| 54 | 
            +
            				path
         | 
| 55 | 
            +
            			end
         | 
| 56 | 
            +
            		end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
            		def guess_request_type
         | 
| 59 | 
            +
            			if @method_name =~ /(?:Create|Add|Remove|Delete|Update)(?:[A-Z]|$)/
         | 
| 60 | 
            +
            				'Post'
         | 
| 61 | 
            +
            			else
         | 
| 62 | 
            +
            				'Get'
         | 
| 63 | 
            +
            			end
         | 
| 64 | 
            +
            		end
         | 
| 65 | 
            +
            	end
         | 
| 66 | 
            +
            end
         | 
| @@ -0,0 +1,14 @@ | |
| 1 | 
            +
            require 'active_support/core_ext/module/attribute_accessors'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module PerfectReach
         | 
| 4 | 
            +
            	module Configuration
         | 
| 5 | 
            +
            		mattr_accessor :api_version
         | 
| 6 | 
            +
            		mattr_accessor :api_key
         | 
| 7 | 
            +
            		mattr_accessor :host
         | 
| 8 | 
            +
            		mattr_accessor :port
         | 
| 9 | 
            +
            		mattr_accessor :use_https
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            		@@api_version = 0.1
         | 
| 12 | 
            +
            		@@use_https = true
         | 
| 13 | 
            +
            	end
         | 
| 14 | 
            +
            end
         | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            require 'perfect_reach/api'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module PerfectReach
         | 
| 4 | 
            +
            	class Contact < OpenStruct
         | 
| 5 | 
            +
            		def infos(options = {})
         | 
| 6 | 
            +
            			PerfectReach::Api.instance.get("contacts/#{(self.id || self.email)}", options)["contact"]
         | 
| 7 | 
            +
            		end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            		class << self
         | 
| 10 | 
            +
            			def all(options = {})
         | 
| 11 | 
            +
            				PerfectReach::Api.instance.get('contacts', options)["contacts"].map do |result_hash|
         | 
| 12 | 
            +
            					self.new(result_hash)
         | 
| 13 | 
            +
            				end
         | 
| 14 | 
            +
            			end
         | 
| 15 | 
            +
            		end
         | 
| 16 | 
            +
            	end
         | 
| 17 | 
            +
            end
         | 
| @@ -0,0 +1,66 @@ | |
| 1 | 
            +
            require 'perfect_reach/api'
         | 
| 2 | 
            +
            require 'perfect_reach/contact'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module PerfectReach
         | 
| 5 | 
            +
            	class ContactList < OpenStruct
         | 
| 6 | 
            +
            		def update(options = {})
         | 
| 7 | 
            +
            			PerfectReach::Api.instance.put("contact_lists/#{self.id}", options)["status"]
         | 
| 8 | 
            +
            		end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            		def add_contacts(contacts, options={})
         | 
| 11 | 
            +
            			throw 'Contacts must be an Array' unless contacts.is_a? Array
         | 
| 12 | 
            +
            			contact_data = []
         | 
| 13 | 
            +
            			contacts.each do |p|
         | 
| 14 | 
            +
            				contact_data << (p.is_a?(PerfectReach::Contact) ? {email: p.email.to_s, name: p.name.to_s} : p)
         | 
| 15 | 
            +
            			end
         | 
| 16 | 
            +
            			PerfectReach::Api.instance.post("contact_lists/#{self.id}/add_contacts", options.reverse_merge(:contacts => contacts.to_json))["status"]
         | 
| 17 | 
            +
            		end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            		def unsubscribe_contact(*params)
         | 
| 20 | 
            +
            			options = params.last.is_a?(Hash) ? params.pop : {}
         | 
| 21 | 
            +
            			contact = params.map { |p| p.is_a?(PerfectReach::Contact) ? p.email.to_s : p.to_s }.reject(&:blank?).first
         | 
| 22 | 
            +
            			PerfectReach::Api.instance.post("contact_lists/#{self.id}/unsubscribe_contact", options.reverse_merge(:contact => contact))["status"]
         | 
| 23 | 
            +
            		end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            		def remove_contacts(contacts, options={})
         | 
| 26 | 
            +
            			throw 'Contacts must be an Array' unless contacts.is_a? Array
         | 
| 27 | 
            +
            			contact_data = []
         | 
| 28 | 
            +
            			contacts.each do |p|
         | 
| 29 | 
            +
            				contact_data << (p.is_a?(PerfectReach::Contact) ? {email: p.email.to_s, name: p.name.to_s} : p)
         | 
| 30 | 
            +
            			end
         | 
| 31 | 
            +
            			PerfectReach::Api.instance.delete("contact_lists/#{self.id}/remove_contacts", options.reverse_merge(:contacts => contacts.to_json))["status"]
         | 
| 32 | 
            +
            		end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            		def contacts(options = {})
         | 
| 35 | 
            +
            			PerfectReach::Api.instance.get("contact_lists/#{self.id}/contacts", options)["contacts"].map do |contact|
         | 
| 36 | 
            +
            				PerfectReach::Contact.new(contact)
         | 
| 37 | 
            +
            			end
         | 
| 38 | 
            +
            		end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            		def email(options = {})
         | 
| 41 | 
            +
            			PerfectReach::Api.instance.get("contact_lists/#{self.id}/email", options)["email"]
         | 
| 42 | 
            +
            		end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            		def statistics(options = {})
         | 
| 45 | 
            +
            			PerfectReach::Api.instance.get("contact_lists/#{self.id}/statistics", options)["statistics"]
         | 
| 46 | 
            +
            		end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            		def delete(options = {})
         | 
| 49 | 
            +
            			PerfectReach::Api.instance.delete("contact_lists/#{self.id}", options)["status"]
         | 
| 50 | 
            +
            		end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            		class << self
         | 
| 53 | 
            +
            			def create(options = {})
         | 
| 54 | 
            +
            				data = PerfectReach::Api.instance.post('contact_lists', options)
         | 
| 55 | 
            +
            				throw data['error_msg'] unless 'ok'.eql? data['status']
         | 
| 56 | 
            +
            				self.new(options.merge(:id => data["id"].to_s))
         | 
| 57 | 
            +
            			end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
            			def all(options = {})
         | 
| 60 | 
            +
            				(PerfectReach::Api.instance.get('contact_lists', options)["contact_lists"] || []).map do |result_hash|
         | 
| 61 | 
            +
            					self.new(result_hash)
         | 
| 62 | 
            +
            				end
         | 
| 63 | 
            +
            			end
         | 
| 64 | 
            +
            		end
         | 
| 65 | 
            +
            	end
         | 
| 66 | 
            +
            end
         | 
| @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            # coding: utf-8
         | 
| 2 | 
            +
            lib = File.expand_path('../lib', __FILE__)
         | 
| 3 | 
            +
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         | 
| 4 | 
            +
            require 'perfect_reach/version'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Gem::Specification.new do |spec|
         | 
| 7 | 
            +
            	spec.name = "perfect_reach"
         | 
| 8 | 
            +
            	spec.version = PerfectReach::VERSION
         | 
| 9 | 
            +
            	spec.authors = ["Mike Petersen"]
         | 
| 10 | 
            +
            	spec.email = ["mike@odania-it.de"]
         | 
| 11 | 
            +
            	spec.description = %q{Manage Ads, ContactLists via an api}
         | 
| 12 | 
            +
            	spec.summary = %q{Manage Ads, ContactLists via an api}
         | 
| 13 | 
            +
            	spec.homepage = "http://www.perfect-reach.com/"
         | 
| 14 | 
            +
            	spec.license = "MIT"
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            	spec.files = `git ls-files`.split($/)
         | 
| 17 | 
            +
            	spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
         | 
| 18 | 
            +
            	spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
         | 
| 19 | 
            +
            	spec.require_paths = ["lib"]
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            	spec.add_dependency "activesupport", ">= 3.1.0"
         | 
| 22 | 
            +
            	spec.add_development_dependency "bundler", "~> 1.3"
         | 
| 23 | 
            +
            	spec.add_development_dependency "rake"
         | 
| 24 | 
            +
            	spec.add_development_dependency "minitest"
         | 
| 25 | 
            +
            	spec.add_development_dependency "minitest-matchers"
         | 
| 26 | 
            +
            	spec.add_development_dependency "turn"
         | 
| 27 | 
            +
            	spec.add_development_dependency "json"
         | 
| 28 | 
            +
            	spec.add_development_dependency "mocha"
         | 
| 29 | 
            +
            end
         | 
| @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            require 'minitest/autorun'
         | 
| 2 | 
            +
            require 'mocha/setup'
         | 
| 3 | 
            +
            require 'perfect_reach'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            test_account = YAML::load(File.new(File.expand_path("../../config.yml", __FILE__)))['perfect_reach']
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            MiniTest::Spec.before do
         | 
| 8 | 
            +
            	PerfectReach.configure do |config|
         | 
| 9 | 
            +
            		config.api_key = test_account['api_key']
         | 
| 10 | 
            +
            		config.host = test_account['host']
         | 
| 11 | 
            +
            		config.port = test_account['port']
         | 
| 12 | 
            +
            		config.use_https = test_account['use_https']
         | 
| 13 | 
            +
            	end
         | 
| 14 | 
            +
            end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            MiniTest::Spec.after do
         | 
| 17 | 
            +
            	Object.send(:remove_const, 'PerfectReach')
         | 
| 18 | 
            +
            	Dir["#{File.dirname(__FILE__)}/../lib/**/*.rb"].each { |f| load f }
         | 
| 19 | 
            +
            end
         | 
| @@ -0,0 +1,43 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'minitest_helper'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            describe PerfectReach::ApiRequest do
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            	describe "#request_path" do
         | 
| 8 | 
            +
            		it "should return the path of the method" do
         | 
| 9 | 
            +
            			PerfectReach::ApiRequest.new('method_name').send(:request_path).must_equal '/api/method_name?format=json'
         | 
| 10 | 
            +
            		end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            		it "should respect api_version" do
         | 
| 13 | 
            +
            			PerfectReach.config.expects(:api_version).returns('0.2')
         | 
| 14 | 
            +
            			PerfectReach::ApiRequest.new('method_name').send(:request).get_fields('X-Api-Version')[0].must_equal '0.2'
         | 
| 15 | 
            +
            		end
         | 
| 16 | 
            +
            	end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            	describe "#request" do
         | 
| 19 | 
            +
            		it "should return an Http request" do
         | 
| 20 | 
            +
            			request = PerfectReach::ApiRequest.new('method_name', {}, 'Post')
         | 
| 21 | 
            +
            			request.send(:request).must_be_kind_of(Net::HTTP::Post)
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            			request = PerfectReach::ApiRequest.new('method_name', {}, 'Get')
         | 
| 24 | 
            +
            			request.send(:request).must_be_kind_of(Net::HTTP::Get)
         | 
| 25 | 
            +
            		end
         | 
| 26 | 
            +
            	end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            	describe "#response" do
         | 
| 29 | 
            +
            		it "should raise an ApiError if authentication fails" do
         | 
| 30 | 
            +
            			request = PerfectReach::ApiRequest.new('method_name', {}, 'Get')
         | 
| 31 | 
            +
            			lambda {
         | 
| 32 | 
            +
            				request.response
         | 
| 33 | 
            +
            			}.must_raise(PerfectReach::ApiError)
         | 
| 34 | 
            +
            		end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            		it "should return a Hash with response values if request is ok" do
         | 
| 37 | 
            +
            			request = PerfectReach::ApiRequest.new('me', {}, 'Get')
         | 
| 38 | 
            +
            			response = request.response
         | 
| 39 | 
            +
            			response.must_be_kind_of(Hash)
         | 
| 40 | 
            +
            			response['user']['email'].must_equal 'test@perfect-reach.com'
         | 
| 41 | 
            +
            		end
         | 
| 42 | 
            +
            	end
         | 
| 43 | 
            +
            end
         | 
| @@ -0,0 +1,12 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
            require 'minitest_helper'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            describe PerfectReach::Api do
         | 
| 5 | 
            +
            	describe "#method_missing" do
         | 
| 6 | 
            +
            		it "should create an ApiRequest" do
         | 
| 7 | 
            +
            			request = mock(:response => true)
         | 
| 8 | 
            +
            			PerfectReach::ApiRequest.expects(:new).with(:user_infos, {:param1 => 1}, 'Post', PerfectReach.config.api_key).returns(request)
         | 
| 9 | 
            +
            			PerfectReach::Api.instance.user_infos({:param1 => 1}, 'Post')
         | 
| 10 | 
            +
            		end
         | 
| 11 | 
            +
            	end
         | 
| 12 | 
            +
            end
         | 
| @@ -0,0 +1,73 @@ | |
| 1 | 
            +
            require 'minitest_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe PerfectReach::ContactList do
         | 
| 4 | 
            +
            	it 'has an integration suite, tested directly against PerfectReach service' do
         | 
| 5 | 
            +
            		# clean-up all existing lists:
         | 
| 6 | 
            +
            		PerfectReach::ContactList.all.each do |list|
         | 
| 7 | 
            +
            			list.delete.must_equal "ok"
         | 
| 8 | 
            +
            		end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            		# PerfectReach::ContactList.all for no lists
         | 
| 11 | 
            +
            		PerfectReach::ContactList.all.must_be_empty
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            		exception = proc { PerfectReach::ContactList.create(name: 'My PerfectReach list') }.must_raise(PerfectReach::ApiError)
         | 
| 14 | 
            +
            		exception.to_s.must_match /invalid_language/
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            		# PerfectReach::ContactList#create
         | 
| 17 | 
            +
            		list = PerfectReach::ContactList.create(name: 'My PerfectReach list', sign_up_allowed: true, show_in_index: false, category_ids: nil,
         | 
| 18 | 
            +
            															 title: 'My PerfectReach list', description: 'My Description', language: 'de', homepage_url: 'http://www.perfect-reach.com')
         | 
| 19 | 
            +
            		list.must_be_instance_of PerfectReach::ContactList
         | 
| 20 | 
            +
            		list.name.must_equal 'My PerfectReach list'
         | 
| 21 | 
            +
            		list.title.must_equal 'My PerfectReach list'
         | 
| 22 | 
            +
            		list.sign_up_allowed.must_equal true
         | 
| 23 | 
            +
            		list.show_in_index.must_equal false
         | 
| 24 | 
            +
            		list.description.must_equal 'My Description'
         | 
| 25 | 
            +
            		list.homepage_url.must_equal 'http://www.perfect-reach.com'
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            		# PerfectReach::ContactList#update.
         | 
| 28 | 
            +
            		list.update(:name => 'My updated PerfectReach list', :title => "myupdatedPerfectReachlist").must_equal "ok"
         | 
| 29 | 
            +
            		updated_list = PerfectReach::ContactList.all.first
         | 
| 30 | 
            +
            		updated_list.name.must_equal 'My updated PerfectReach list'
         | 
| 31 | 
            +
            		updated_list.title.must_equal 'myupdatedPerfectReachlist'
         | 
| 32 | 
            +
            		updated_list.id.must_equal list.id
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            		# PerfectReach::ContactList.all
         | 
| 35 | 
            +
            		lists = PerfectReach::ContactList.all
         | 
| 36 | 
            +
            		lists.count.must_equal 1
         | 
| 37 | 
            +
            		lists.first.must_be_instance_of PerfectReach::ContactList
         | 
| 38 | 
            +
            		lists.first.id.must_equal updated_list.id
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            		# PerfectReach::ContactList#add_contacts
         | 
| 41 | 
            +
            		list.add_contacts([{email: "c1@contacts.com", name: 'c1'}]).must_equal "ok"
         | 
| 42 | 
            +
            		list.add_contacts([{email: "c1@contacts.com", name: 'c1'}]).must_equal "not_modified"
         | 
| 43 | 
            +
            		list.add_contacts([{email: "c2@contacts.com", name: 'c2'}, {email: "c3@contacts.com", name: 'c3'}]).must_equal "ok"
         | 
| 44 | 
            +
            		list.add_contacts([{email: "c4@contacts.com", name: 'c4'}, {email: "c5@contacts.com", name: 'c5'}]).must_equal "ok"
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            		# PerfectReach::ContactList#contacts and validate results of add_contacts
         | 
| 47 | 
            +
            		contacts = list.contacts
         | 
| 48 | 
            +
            		contacts.first.must_be_instance_of PerfectReach::Contact
         | 
| 49 | 
            +
            		contacts.map(&:email).sort.must_equal ["c1@contacts.com", "c2@contacts.com", "c3@contacts.com", "c4@contacts.com", "c5@contacts.com"]
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            		# PerfectReach::ContactList#unsubscribe_contact
         | 
| 52 | 
            +
            		list.unsubscribe_contact("c1@contacts.com").must_equal "ok"
         | 
| 53 | 
            +
            		exception = proc { list.unsubscribe_contact("c1@contacts.com") }.must_raise(PerfectReach::ApiError)
         | 
| 54 | 
            +
            		exception.to_s.must_match /already_unsubscribed/
         | 
| 55 | 
            +
             | 
| 56 | 
            +
            		# PerfectReach::ContactList#remove_contacts
         | 
| 57 | 
            +
            		list.remove_contacts([{email: "does-not-exist@nowhere.com"}]).must_equal "not_modified"
         | 
| 58 | 
            +
            		list.remove_contacts([{email: "c1@contacts.com"}, {email: "c2@contacts.com"}]).must_equal "ok"
         | 
| 59 | 
            +
            		list.contacts.count.must_equal 3
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            		# PerfectReach::ContactList#email
         | 
| 62 | 
            +
            		list.email.must_match /\@lists\.perfect-reach\.com$/
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            		# PerfectReach::ContactList#statistics
         | 
| 65 | 
            +
            		list.statistics["sent"].must_equal 0
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            		# PerfectReach::ContactList#delete
         | 
| 68 | 
            +
            		list.delete.must_equal 'ok'
         | 
| 69 | 
            +
            		PerfectReach::ContactList.all.must_be_empty
         | 
| 70 | 
            +
            		exception = proc { list.delete }.must_raise(PerfectReach::ApiError)
         | 
| 71 | 
            +
            		exception.to_s.must_match /invalid_id/
         | 
| 72 | 
            +
            	end
         | 
| 73 | 
            +
            end
         | 
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            require 'minitest_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe PerfectReach::Contact do
         | 
| 4 | 
            +
            	it 'has an integration suite, tested directly against PerfectReach service' do
         | 
| 5 | 
            +
            		# init
         | 
| 6 | 
            +
            		PerfectReach::ContactList.all.each do |list|
         | 
| 7 | 
            +
            			list.delete
         | 
| 8 | 
            +
            		end
         | 
| 9 | 
            +
            		list = PerfectReach::ContactList.create(:name => 'My PerfectReach list', language: 'en')
         | 
| 10 | 
            +
            		list.add_contacts([{email: "c1@contacts.com"}])
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            		# PerfectReach::Contact.all
         | 
| 13 | 
            +
            		contacts = PerfectReach::Contact.all
         | 
| 14 | 
            +
            		contacts.wont_be_empty # we can't empty the whole contact list...
         | 
| 15 | 
            +
            		contacts.first.must_be_instance_of PerfectReach::Contact
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            		# PerfectReach::Contact#infos
         | 
| 18 | 
            +
            		contacts.first.infos["mails_clicked"].wont_be_nil
         | 
| 19 | 
            +
            	end
         | 
| 20 | 
            +
            end
         | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            require 'minitest_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe PerfectReach do
         | 
| 4 | 
            +
            	describe "#configure" do
         | 
| 5 | 
            +
            		it "should permit to set api keys and remember them" do
         | 
| 6 | 
            +
            			PerfectReach.configure do |config|
         | 
| 7 | 
            +
            				config.api_key = 'sad786523'
         | 
| 8 | 
            +
            				config.host = 'test.perfect-reach.com'
         | 
| 9 | 
            +
            				config.port = '1324'
         | 
| 10 | 
            +
            			end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            			PerfectReach.config.api_key.must_equal 'sad786523'
         | 
| 13 | 
            +
            			PerfectReach.config.host.must_equal 'test.perfect-reach.com'
         | 
| 14 | 
            +
            			PerfectReach.config.port.must_equal '1324'
         | 
| 15 | 
            +
            		end
         | 
| 16 | 
            +
            	end
         | 
| 17 | 
            +
            end
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,207 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: perfect_reach
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.0.1
         | 
| 5 | 
            +
              prerelease: 
         | 
| 6 | 
            +
            platform: ruby
         | 
| 7 | 
            +
            authors:
         | 
| 8 | 
            +
            - Mike Petersen
         | 
| 9 | 
            +
            autorequire: 
         | 
| 10 | 
            +
            bindir: bin
         | 
| 11 | 
            +
            cert_chain: []
         | 
| 12 | 
            +
            date: 2013-07-22 00:00:00.000000000 Z
         | 
| 13 | 
            +
            dependencies:
         | 
| 14 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 15 | 
            +
              name: activesupport
         | 
| 16 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 17 | 
            +
                none: false
         | 
| 18 | 
            +
                requirements:
         | 
| 19 | 
            +
                - - ! '>='
         | 
| 20 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 21 | 
            +
                    version: 3.1.0
         | 
| 22 | 
            +
              type: :runtime
         | 
| 23 | 
            +
              prerelease: false
         | 
| 24 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 25 | 
            +
                none: false
         | 
| 26 | 
            +
                requirements:
         | 
| 27 | 
            +
                - - ! '>='
         | 
| 28 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 29 | 
            +
                    version: 3.1.0
         | 
| 30 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 31 | 
            +
              name: bundler
         | 
| 32 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 33 | 
            +
                none: false
         | 
| 34 | 
            +
                requirements:
         | 
| 35 | 
            +
                - - ~>
         | 
| 36 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 37 | 
            +
                    version: '1.3'
         | 
| 38 | 
            +
              type: :development
         | 
| 39 | 
            +
              prerelease: false
         | 
| 40 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 41 | 
            +
                none: false
         | 
| 42 | 
            +
                requirements:
         | 
| 43 | 
            +
                - - ~>
         | 
| 44 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 45 | 
            +
                    version: '1.3'
         | 
| 46 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 47 | 
            +
              name: rake
         | 
| 48 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 49 | 
            +
                none: false
         | 
| 50 | 
            +
                requirements:
         | 
| 51 | 
            +
                - - ! '>='
         | 
| 52 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 53 | 
            +
                    version: '0'
         | 
| 54 | 
            +
              type: :development
         | 
| 55 | 
            +
              prerelease: false
         | 
| 56 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 57 | 
            +
                none: false
         | 
| 58 | 
            +
                requirements:
         | 
| 59 | 
            +
                - - ! '>='
         | 
| 60 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 61 | 
            +
                    version: '0'
         | 
| 62 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 63 | 
            +
              name: minitest
         | 
| 64 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 65 | 
            +
                none: false
         | 
| 66 | 
            +
                requirements:
         | 
| 67 | 
            +
                - - ! '>='
         | 
| 68 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 69 | 
            +
                    version: '0'
         | 
| 70 | 
            +
              type: :development
         | 
| 71 | 
            +
              prerelease: false
         | 
| 72 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 73 | 
            +
                none: false
         | 
| 74 | 
            +
                requirements:
         | 
| 75 | 
            +
                - - ! '>='
         | 
| 76 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 77 | 
            +
                    version: '0'
         | 
| 78 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 79 | 
            +
              name: minitest-matchers
         | 
| 80 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 81 | 
            +
                none: false
         | 
| 82 | 
            +
                requirements:
         | 
| 83 | 
            +
                - - ! '>='
         | 
| 84 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 85 | 
            +
                    version: '0'
         | 
| 86 | 
            +
              type: :development
         | 
| 87 | 
            +
              prerelease: false
         | 
| 88 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 89 | 
            +
                none: false
         | 
| 90 | 
            +
                requirements:
         | 
| 91 | 
            +
                - - ! '>='
         | 
| 92 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 93 | 
            +
                    version: '0'
         | 
| 94 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 95 | 
            +
              name: turn
         | 
| 96 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 97 | 
            +
                none: false
         | 
| 98 | 
            +
                requirements:
         | 
| 99 | 
            +
                - - ! '>='
         | 
| 100 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 101 | 
            +
                    version: '0'
         | 
| 102 | 
            +
              type: :development
         | 
| 103 | 
            +
              prerelease: false
         | 
| 104 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 105 | 
            +
                none: false
         | 
| 106 | 
            +
                requirements:
         | 
| 107 | 
            +
                - - ! '>='
         | 
| 108 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 109 | 
            +
                    version: '0'
         | 
| 110 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 111 | 
            +
              name: json
         | 
| 112 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 113 | 
            +
                none: false
         | 
| 114 | 
            +
                requirements:
         | 
| 115 | 
            +
                - - ! '>='
         | 
| 116 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 117 | 
            +
                    version: '0'
         | 
| 118 | 
            +
              type: :development
         | 
| 119 | 
            +
              prerelease: false
         | 
| 120 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 121 | 
            +
                none: false
         | 
| 122 | 
            +
                requirements:
         | 
| 123 | 
            +
                - - ! '>='
         | 
| 124 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 125 | 
            +
                    version: '0'
         | 
| 126 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 127 | 
            +
              name: mocha
         | 
| 128 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 129 | 
            +
                none: false
         | 
| 130 | 
            +
                requirements:
         | 
| 131 | 
            +
                - - ! '>='
         | 
| 132 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 133 | 
            +
                    version: '0'
         | 
| 134 | 
            +
              type: :development
         | 
| 135 | 
            +
              prerelease: false
         | 
| 136 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 137 | 
            +
                none: false
         | 
| 138 | 
            +
                requirements:
         | 
| 139 | 
            +
                - - ! '>='
         | 
| 140 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 141 | 
            +
                    version: '0'
         | 
| 142 | 
            +
            description: Manage Ads, ContactLists via an api
         | 
| 143 | 
            +
            email:
         | 
| 144 | 
            +
            - mike@odania-it.de
         | 
| 145 | 
            +
            executables: []
         | 
| 146 | 
            +
            extensions: []
         | 
| 147 | 
            +
            extra_rdoc_files: []
         | 
| 148 | 
            +
            files:
         | 
| 149 | 
            +
            - .gitignore
         | 
| 150 | 
            +
            - Gemfile
         | 
| 151 | 
            +
            - LICENSE.txt
         | 
| 152 | 
            +
            - README.md
         | 
| 153 | 
            +
            - Rakefile
         | 
| 154 | 
            +
            - config.yml
         | 
| 155 | 
            +
            - lib/perfect_reach.rb
         | 
| 156 | 
            +
            - lib/perfect_reach/api.rb
         | 
| 157 | 
            +
            - lib/perfect_reach/api_error.rb
         | 
| 158 | 
            +
            - lib/perfect_reach/api_request.rb
         | 
| 159 | 
            +
            - lib/perfect_reach/configuration.rb
         | 
| 160 | 
            +
            - lib/perfect_reach/contact.rb
         | 
| 161 | 
            +
            - lib/perfect_reach/contact_list.rb
         | 
| 162 | 
            +
            - lib/perfect_reach/version.rb
         | 
| 163 | 
            +
            - perfect_reach.gemspec
         | 
| 164 | 
            +
            - test/minitest_helper.rb
         | 
| 165 | 
            +
            - test/perfect_reach/api_request_test.rb
         | 
| 166 | 
            +
            - test/perfect_reach/api_test.rb
         | 
| 167 | 
            +
            - test/perfect_reach/contact_list_test.rb
         | 
| 168 | 
            +
            - test/perfect_reach/contact_test.rb
         | 
| 169 | 
            +
            - test/perfect_reach_test.rb
         | 
| 170 | 
            +
            homepage: http://www.perfect-reach.com/
         | 
| 171 | 
            +
            licenses:
         | 
| 172 | 
            +
            - MIT
         | 
| 173 | 
            +
            post_install_message: 
         | 
| 174 | 
            +
            rdoc_options: []
         | 
| 175 | 
            +
            require_paths:
         | 
| 176 | 
            +
            - lib
         | 
| 177 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 178 | 
            +
              none: false
         | 
| 179 | 
            +
              requirements:
         | 
| 180 | 
            +
              - - ! '>='
         | 
| 181 | 
            +
                - !ruby/object:Gem::Version
         | 
| 182 | 
            +
                  version: '0'
         | 
| 183 | 
            +
                  segments:
         | 
| 184 | 
            +
                  - 0
         | 
| 185 | 
            +
                  hash: 4411597228737958065
         | 
| 186 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 187 | 
            +
              none: false
         | 
| 188 | 
            +
              requirements:
         | 
| 189 | 
            +
              - - ! '>='
         | 
| 190 | 
            +
                - !ruby/object:Gem::Version
         | 
| 191 | 
            +
                  version: '0'
         | 
| 192 | 
            +
                  segments:
         | 
| 193 | 
            +
                  - 0
         | 
| 194 | 
            +
                  hash: 4411597228737958065
         | 
| 195 | 
            +
            requirements: []
         | 
| 196 | 
            +
            rubyforge_project: 
         | 
| 197 | 
            +
            rubygems_version: 1.8.25
         | 
| 198 | 
            +
            signing_key: 
         | 
| 199 | 
            +
            specification_version: 3
         | 
| 200 | 
            +
            summary: Manage Ads, ContactLists via an api
         | 
| 201 | 
            +
            test_files:
         | 
| 202 | 
            +
            - test/minitest_helper.rb
         | 
| 203 | 
            +
            - test/perfect_reach/api_request_test.rb
         | 
| 204 | 
            +
            - test/perfect_reach/api_test.rb
         | 
| 205 | 
            +
            - test/perfect_reach/contact_list_test.rb
         | 
| 206 | 
            +
            - test/perfect_reach/contact_test.rb
         | 
| 207 | 
            +
            - test/perfect_reach_test.rb
         |