rescue_groups 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 +7 -0
- data/.gitignore +4 -0
- data/.travis.yml +3 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +90 -0
- data/LICENSE.txt +22 -0
- data/README.md +226 -0
- data/Rakefile +30 -0
- data/config/config.rb +26 -0
- data/config/initializer.rb +15 -0
- data/docs/animal_field.md +138 -0
- data/docs/event_field.md +20 -0
- data/docs/organization_field.md +25 -0
- data/fields/animal_field.rb +152 -0
- data/fields/event_field.rb +35 -0
- data/fields/organization_field.rb +40 -0
- data/fields/picture_field.rb +30 -0
- data/lib/api_client.rb +29 -0
- data/lib/queryable.rb +79 -0
- data/lib/relationable.rb +76 -0
- data/lib/remote_client.rb +29 -0
- data/lib/remote_model.rb +47 -0
- data/lib/requests/find.rb +29 -0
- data/lib/requests/invalid_client.rb +1 -0
- data/lib/requests/where.rb +94 -0
- data/lib/response.rb +48 -0
- data/models/animal.rb +57 -0
- data/models/event.rb +41 -0
- data/models/organization.rb +41 -0
- data/models/picture.rb +26 -0
- data/rescue_groups.gemspec +28 -0
- data/rescue_groups.rb +27 -0
- data/search/animal_search.rb +15 -0
- data/search/base_search.rb +72 -0
- data/search/event_search.rb +15 -0
- data/search/filter.rb +49 -0
- data/search/organization_search.rb +15 -0
- data/spec/fixtures/animal/find.json +1 -0
- data/spec/fixtures/animal/where.json +1 -0
- data/spec/fixtures/error.json +20 -0
- data/spec/fixtures/event/find.json +1 -0
- data/spec/fixtures/event/where.json +1 -0
- data/spec/fixtures/organization/find.json +1 -0
- data/spec/fixtures/organization/where.json +1 -0
- data/spec/fixtures/test_constants.rb +12 -0
- data/spec/integration/animal_spec.rb +55 -0
- data/spec/integration/event_spec.rb +33 -0
- data/spec/integration/organization_spec.rb +35 -0
- data/spec/lib/queryable_spec.rb +257 -0
- data/spec/lib/relationable_spec.rb +113 -0
- data/spec/lib/remote_client_spec.rb +27 -0
- data/spec/lib/requests/find_spec.rb +97 -0
- data/spec/lib/requests/where_spec.rb +267 -0
- data/spec/lib/response_spec.rb +99 -0
- data/spec/models/animal_spec.rb +131 -0
- data/spec/models/event_spec.rb +105 -0
- data/spec/models/organization_spec.rb +112 -0
- data/spec/models/picture_spec.rb +87 -0
- data/spec/search/animal_search_spec.rb +8 -0
- data/spec/search/event_search_spec.rb +8 -0
- data/spec/search/filter_spec.rb +39 -0
- data/spec/search/organization_search_spec.rb +8 -0
- data/spec/spec_helper.rb +340 -0
- data/spec/support/model_spec.rb +47 -0
- data/spec/support/searchable_spec.rb +15 -0
- data/support/animal_mock.rb +215 -0
- data/support/base_mock.rb +44 -0
- data/support/event_mock.rb +48 -0
- data/support/organization_mock.rb +53 -0
- data/version.rb +3 -0
- metadata +242 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require_relative './response'
|
3
|
+
|
4
|
+
module RescueGroups
|
5
|
+
# HTTParty wrapper with defaulted values
|
6
|
+
# for Content-Type, URL and apikey authentication
|
7
|
+
class RemoteClient
|
8
|
+
include HTTParty
|
9
|
+
|
10
|
+
base_uri 'https://api.rescuegroups.org/http/json'
|
11
|
+
headers 'Content-Type' => 'application/json'
|
12
|
+
|
13
|
+
# method: post_and_respond
|
14
|
+
# purpose: make a POST request to the RescueGroups API and respond
|
15
|
+
# param: post_body - <Hash> - attributes to be included in the post body
|
16
|
+
# return: response <Response> object with details of HTTP response
|
17
|
+
def post_and_respond(post_body)
|
18
|
+
if RescueGroups.config.apikey.nil? || RescueGroups.config.apikey.length == 0
|
19
|
+
raise 'No RescueGroups API Key set'
|
20
|
+
end
|
21
|
+
|
22
|
+
response = self.class.post(
|
23
|
+
self.class.base_uri,
|
24
|
+
{ body: JSON(post_body.merge(apikey: RescueGroups.config.apikey)) })
|
25
|
+
|
26
|
+
Response.new(response)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/remote_model.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative './api_client'
|
2
|
+
require_relative '../lib/queryable'
|
3
|
+
require_relative '../lib/relationable'
|
4
|
+
require_relative '../models/picture'
|
5
|
+
|
6
|
+
module RescueGroups
|
7
|
+
class RemoteModel
|
8
|
+
include ApiClient
|
9
|
+
include Queryable
|
10
|
+
include Relationable
|
11
|
+
|
12
|
+
# method: initialize
|
13
|
+
# purpose: given a hash of attributes, assign the attributes that the
|
14
|
+
# included model has defined and discard the rest
|
15
|
+
# param: attribute_hash - <Hash> - hash of attributes to instantiate
|
16
|
+
# this model with
|
17
|
+
# return: nil
|
18
|
+
def initialize(attribute_hash = {})
|
19
|
+
(attribute_hash || {}).each do |key, value|
|
20
|
+
mapped_key = self.class.object_fields::FIELDS.key(key.to_sym)
|
21
|
+
mapped_method = "#{ mapped_key }="
|
22
|
+
next unless self.respond_to?(mapped_method)
|
23
|
+
self.send(mapped_method, cast_attribute_to_type(mapped_key, value))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def cast_attribute_to_type(name, value)
|
28
|
+
return value.to_i if name.to_s == 'id' || name =~ /_id/
|
29
|
+
|
30
|
+
value
|
31
|
+
end
|
32
|
+
|
33
|
+
# method: attributes
|
34
|
+
# purpose: Distill the included class's attributes into a hash of keys and values
|
35
|
+
# If an attribute is nil, the key for that attribute is still included
|
36
|
+
# in the resulting hash and the value is nil
|
37
|
+
# param: none
|
38
|
+
# return: <Hash> of attributes from the included class
|
39
|
+
def attributes
|
40
|
+
{}.tap do |hash|
|
41
|
+
self.class.object_fields::FIELDS.keys.each do |attribute|
|
42
|
+
hash[attribute] = instance_variable_get(:"@#{ attribute }")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative './invalid_client'
|
2
|
+
|
3
|
+
module RescueGroups
|
4
|
+
module Requests
|
5
|
+
class Find
|
6
|
+
def initialize(ids, model, client)
|
7
|
+
@ids = [*ids].flatten.uniq.map(&:to_i)
|
8
|
+
@model = model
|
9
|
+
@client = client
|
10
|
+
rescue NoMethodError
|
11
|
+
raise 'Only initialize a Requests::Find with integers or models responding to .to_i'
|
12
|
+
end
|
13
|
+
|
14
|
+
def request
|
15
|
+
raise InvalidClient, 'Invalid client given to Requests::Find' unless @client.respond_to?(:post_and_respond)
|
16
|
+
@client.post_and_respond(as_json)
|
17
|
+
end
|
18
|
+
|
19
|
+
def as_json(*)
|
20
|
+
{
|
21
|
+
objectAction: :publicView,
|
22
|
+
objectType: @model.object_type,
|
23
|
+
fields: @model.object_fields.all,
|
24
|
+
values: @ids.map { |i| { @model.object_fields::FIELDS[:id] => i } }
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
class InvalidClient < StandardError; end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require_relative './invalid_client'
|
2
|
+
|
3
|
+
module RescueGroups
|
4
|
+
module Requests
|
5
|
+
class Where
|
6
|
+
attr_reader :search_engine, :results
|
7
|
+
|
8
|
+
def initialize(conditions, model, client, search_engine_class)
|
9
|
+
modifiers = %i[limit start sort order]
|
10
|
+
|
11
|
+
conditions.each do |key, value|
|
12
|
+
if modifiers.include?(key.to_sym)
|
13
|
+
instance_variable_set(:"@#{ key }", conditions.delete(key))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
@conditions = conditions
|
18
|
+
@model = model
|
19
|
+
@client = client
|
20
|
+
@search_engine = build_search_engine(search_engine_class)
|
21
|
+
@results = { 'data' => {} }
|
22
|
+
end
|
23
|
+
|
24
|
+
def request
|
25
|
+
raise InvalidClient, 'Invalid client given to Requests::Where' unless @client.respond_to?(:post_and_respond)
|
26
|
+
response = @client.post_and_respond(as_json)
|
27
|
+
|
28
|
+
if response.success? && !response['data'].empty?
|
29
|
+
@results['found_rows'] = response['found_rows']
|
30
|
+
@results['data'].merge!(response['data'])
|
31
|
+
end
|
32
|
+
|
33
|
+
response
|
34
|
+
end
|
35
|
+
|
36
|
+
def as_json(*)
|
37
|
+
{
|
38
|
+
objectAction: :publicSearch,
|
39
|
+
objectType: @model.object_type,
|
40
|
+
search: @search_engine.as_json,
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def update_conditions!(conditions)
|
45
|
+
@conditions.merge!(conditions)
|
46
|
+
end
|
47
|
+
|
48
|
+
def can_request_more?
|
49
|
+
return false if results.nil? || results['data'].nil? || results['data'].empty?
|
50
|
+
!results['found_rows'].nil? && results['data'].keys.length < results['found_rows']
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def build_search_engine(search_engine_class)
|
56
|
+
args = {
|
57
|
+
limit: @limit,
|
58
|
+
start: @start,
|
59
|
+
sort: @sort,
|
60
|
+
}.reject { |_, v| v.nil? }
|
61
|
+
|
62
|
+
search = search_engine_class.new(**args)
|
63
|
+
|
64
|
+
add_filters_to_search_engine(search)
|
65
|
+
|
66
|
+
search
|
67
|
+
end
|
68
|
+
|
69
|
+
def add_filters_to_search_engine(search)
|
70
|
+
conditions_to_rescue_groups_key_value do |mapped_key, val|
|
71
|
+
equality_operator = :equal
|
72
|
+
|
73
|
+
if val.is_a?(Hash)
|
74
|
+
equality_operator = val.keys[0]
|
75
|
+
val = val.values[0]
|
76
|
+
end
|
77
|
+
|
78
|
+
search.add_filter(mapped_key, equality_operator, val)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def conditions_to_rescue_groups_key_value(&block)
|
83
|
+
fail('Block not given') unless block_given?
|
84
|
+
@conditions.each do |key, value|
|
85
|
+
mapped_key = @model.object_fields::FIELDS[key.to_sym]
|
86
|
+
|
87
|
+
next if mapped_key.nil?
|
88
|
+
|
89
|
+
yield mapped_key, value
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/response.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module RescueGroups
|
2
|
+
class Response
|
3
|
+
attr_accessor :http_status_code, :parsed_body
|
4
|
+
|
5
|
+
# method: initialize
|
6
|
+
# purpose: When a Response is created, this method
|
7
|
+
# extracts the code and parsed_response from
|
8
|
+
# the raw HTTParty response
|
9
|
+
# param: raw_response - <HTTParty Response> - raw HTTParty response
|
10
|
+
# return: nil
|
11
|
+
def initialize(raw_response)
|
12
|
+
@http_status_code = raw_response.code
|
13
|
+
@parsed_body = raw_response.parsed_response
|
14
|
+
end
|
15
|
+
|
16
|
+
# method: []
|
17
|
+
# purpose: Convenience method to access an attribute from
|
18
|
+
# the parsed body
|
19
|
+
# param: attribute - <String> - key of desired value from response
|
20
|
+
# return: value at the specified key of the response
|
21
|
+
def [](attribute)
|
22
|
+
@parsed_body[attribute]
|
23
|
+
end
|
24
|
+
|
25
|
+
# method: success
|
26
|
+
# purpose: return true/false for if the response had an error (Non-HTTP error)
|
27
|
+
# param: none
|
28
|
+
# return: <Boolean> state of the response['status']
|
29
|
+
def success?
|
30
|
+
self['status'] != 'error'
|
31
|
+
end
|
32
|
+
|
33
|
+
# method: error
|
34
|
+
# purpose: traverse down response tree and extract the error
|
35
|
+
# text on errorful responses
|
36
|
+
# param: none
|
37
|
+
# return: <Nil> if there is no error
|
38
|
+
# <String> if an error exists and messageCriticality is expected
|
39
|
+
# value
|
40
|
+
def error
|
41
|
+
unless success?
|
42
|
+
self['messages']['generalMessages'].map do |m|
|
43
|
+
m['messageText'] if m['messageCriticality'] == 'error'
|
44
|
+
end.compact.join("\n")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/models/animal.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require_relative '../lib/remote_model'
|
2
|
+
require_relative '../search/animal_search'
|
3
|
+
|
4
|
+
module RescueGroups
|
5
|
+
class Animal < RemoteModel
|
6
|
+
belongs_to :organization
|
7
|
+
|
8
|
+
class << self
|
9
|
+
# method :object_type
|
10
|
+
# purpose: Define this class's object_type used by the
|
11
|
+
# the Queryable module when composing remote queries
|
12
|
+
# param: none
|
13
|
+
# return: <Symbol> - the value of the object_type
|
14
|
+
def object_type
|
15
|
+
:animals
|
16
|
+
end
|
17
|
+
|
18
|
+
# method :object_fields
|
19
|
+
# purpose: Define this class's object fields class used by the
|
20
|
+
# the Queryable module when composing remote queries
|
21
|
+
# param: none
|
22
|
+
# return: <Constant> - the class containing the list of fields
|
23
|
+
# pertinent to this class
|
24
|
+
def object_fields
|
25
|
+
AnimalField
|
26
|
+
end
|
27
|
+
|
28
|
+
# method :search_engine_class
|
29
|
+
# purpose: Define which search engine is used by the class. The Queryable
|
30
|
+
# module uses the search engine when constructing remote queries to make
|
31
|
+
#
|
32
|
+
# param: none
|
33
|
+
# return: <Constant> - the class that contains search data pertinent to this class
|
34
|
+
def search_engine_class
|
35
|
+
AnimalSearch
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(*args)
|
40
|
+
super
|
41
|
+
|
42
|
+
extract_pictures if !@pictures.nil? && !@pictures.empty?
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_accessor *object_fields::FIELDS.keys
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def extract_pictures
|
50
|
+
@pictures.map! do |picture_data|
|
51
|
+
Picture.new(picture_data).tap do |picture|
|
52
|
+
picture.animal_id = self.id
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/models/event.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require_relative '../lib/remote_model'
|
2
|
+
require_relative '../search/event_search'
|
3
|
+
|
4
|
+
module RescueGroups
|
5
|
+
class Event < RemoteModel
|
6
|
+
belongs_to :organization
|
7
|
+
|
8
|
+
class << self
|
9
|
+
# method :object_type
|
10
|
+
# purpose: Define this class's object_type used by the
|
11
|
+
# the Queryable module when composing remote queries
|
12
|
+
# param: none
|
13
|
+
# return: <Symbol> - the value of the object_type
|
14
|
+
def object_type
|
15
|
+
:events
|
16
|
+
end
|
17
|
+
|
18
|
+
# method :object_fields
|
19
|
+
# purpose: Define this class's object fields class used by the
|
20
|
+
# the Queryable module when composing remote queries
|
21
|
+
# param: none
|
22
|
+
# return: <Constant> - the class containing the list of fields
|
23
|
+
# pertinent to this class
|
24
|
+
def object_fields
|
25
|
+
EventField
|
26
|
+
end
|
27
|
+
|
28
|
+
# method :search_engine_class
|
29
|
+
# purpose: Define which search engine is used by the class. The Queryable
|
30
|
+
# module uses the search engine when constructing remote queries to make
|
31
|
+
#
|
32
|
+
# param: none
|
33
|
+
# return: <Constant> - the class that contains search data pertinent to this class
|
34
|
+
def search_engine_class
|
35
|
+
EventSearch
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_accessor *object_fields::FIELDS.keys
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require_relative '../lib/remote_model'
|
2
|
+
require_relative '../search/organization_search'
|
3
|
+
|
4
|
+
module RescueGroups
|
5
|
+
class Organization < RemoteModel
|
6
|
+
has_many :animals
|
7
|
+
|
8
|
+
class << self
|
9
|
+
# method :object_type
|
10
|
+
# purpose: Define this class's object_type used by the
|
11
|
+
# the Queryable module when composing remote queries
|
12
|
+
# param: none
|
13
|
+
# return: <Symbol> - the value of the object_type
|
14
|
+
def object_type
|
15
|
+
:orgs
|
16
|
+
end
|
17
|
+
|
18
|
+
# method :object_fields
|
19
|
+
# purpose: Define this class's object fields class used by the
|
20
|
+
# the Queryable module when composing remote queries
|
21
|
+
# param: none
|
22
|
+
# return: <Constant> - the class containing the list of fields
|
23
|
+
# pertinent to this class
|
24
|
+
def object_fields
|
25
|
+
OrganizationField
|
26
|
+
end
|
27
|
+
|
28
|
+
# method :search_engine_class
|
29
|
+
# purpose: Define which search engine is used by the class. The Queryable
|
30
|
+
# module uses the search engine when constructing remote queries to make
|
31
|
+
#
|
32
|
+
# param: none
|
33
|
+
# return: <Constant> - the class that contains search data pertinent to this class
|
34
|
+
def search_engine_class
|
35
|
+
OrganizationSearch
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_accessor *object_fields::FIELDS.keys
|
40
|
+
end
|
41
|
+
end
|
data/models/picture.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative '../fields/picture_field'
|
2
|
+
|
3
|
+
module RescueGroups
|
4
|
+
class Picture
|
5
|
+
include Relationable
|
6
|
+
|
7
|
+
belongs_to :animal
|
8
|
+
|
9
|
+
attr_accessor *PictureField::FIELDS.values
|
10
|
+
|
11
|
+
def initialize(attribute_hash = {})
|
12
|
+
attribute_hash.each do |key, value|
|
13
|
+
mapped_key = PictureField::FIELDS[key.to_sym]
|
14
|
+
self.send(:"#{ mapped_key }=", value) unless mapped_key.nil?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def url(secure: false)
|
19
|
+
secure ? url_full : insecure_url_full
|
20
|
+
end
|
21
|
+
|
22
|
+
def url_thumb(secure: false)
|
23
|
+
secure ? url_thumbnail : insecure_url_thumb
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
$LOAD_PATH << File.dirname(__FILE__)
|
2
|
+
require 'version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'rescue_groups'
|
6
|
+
s.version = RescueGroups::VERSION
|
7
|
+
s.summary = %q{RescueGroups.org API wrapper}
|
8
|
+
s.description = %q{Ruby ORM for the RescueGroups API. On demand pet data provided for free by RescueGroups.org.}
|
9
|
+
s.authors = ['Jake Yesbeck']
|
10
|
+
s.email = 'yesbeckjs@gmail.com'
|
11
|
+
s.homepage = 'http://rubygems.org/gems/rescue_groups'
|
12
|
+
s.license = 'MIT'
|
13
|
+
|
14
|
+
s.require_paths = %w(lib models search config)
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = s.files.grep(/^spec\//)
|
17
|
+
|
18
|
+
s.required_ruby_version = '>= 2.0.0'
|
19
|
+
|
20
|
+
s.add_dependency 'httparty', '>= 0.13.0'
|
21
|
+
|
22
|
+
s.add_development_dependency 'bundler', '~> 1.3'
|
23
|
+
s.add_development_dependency 'rake'
|
24
|
+
s.add_development_dependency 'rspec'
|
25
|
+
s.add_development_dependency 'pry'
|
26
|
+
s.add_development_dependency 'webmock'
|
27
|
+
s.add_development_dependency 'guard-rspec'
|
28
|
+
end
|