easy311 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +39 -0
- data/Rakefile +1 -0
- data/easy311.gemspec +25 -0
- data/easy311.yml +4 -0
- data/lib/easy311.rb +11 -0
- data/lib/easy311/api_wrapper.rb +134 -0
- data/lib/easy311/attribute.rb +17 -0
- data/lib/easy311/factories.rb +65 -0
- data/lib/easy311/rails.rb +35 -0
- data/lib/easy311/request.rb +91 -0
- data/lib/easy311/response.rb +22 -0
- data/lib/easy311/service.rb +41 -0
- data/lib/easy311/version.rb +3 -0
- data/spec/easy311/api_wrapper_spec.rb +298 -0
- data/spec/easy311/json_helper.rb +51 -0
- data/spec/easy311/request_spec.rb +114 -0
- data/spec/spec_helper.rb +15 -0
- metadata +107 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Jimmy Bourassa
|
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,39 @@
|
|
1
|
+
# Easy311
|
2
|
+
|
3
|
+
Easy311 is a set of tools to ease the development of Open311 applications:
|
4
|
+
it provides a wrapper around the REST API and a close-to-compliant ActiveModel
|
5
|
+
`Request` object.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'easy311'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install easy311
|
20
|
+
|
21
|
+
If you want to use `Easy311::Rails`, copy and edit easy311.yml
|
22
|
+
to `#{Rails.root}/config/easy311.yml`
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
The library can be used by itself without Rails, but we built a wrapper
|
27
|
+
for Rails that makes it easy to configure and load Open311's services.
|
28
|
+
|
29
|
+
No documentation official documentation has been written (yet) since
|
30
|
+
this library has originally been built during a Hackaton (see credits).
|
31
|
+
|
32
|
+
## Contributing
|
33
|
+
|
34
|
+
No contribution is too small: fork away and send pull requests!
|
35
|
+
|
36
|
+
## Credits
|
37
|
+
|
38
|
+
This gem has been extracted from 2013 [Iron Web](http://ironweb.org)
|
39
|
+
_Rouges_' team. Original code by @gelendir and @jbourassa.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/easy311.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'easy311/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "easy311"
|
8
|
+
gem.version = Easy311::VERSION
|
9
|
+
gem.authors = ["Jimmy Bourassa", "Gregory Eric Sanderson"]
|
10
|
+
gem.email = ["jimmy.bourassa@hooktstudios.com", "gzou2000@gmail.com"]
|
11
|
+
gem.description = %q{Set of tools to ease the development of Open311 applications}
|
12
|
+
gem.summary = %q{Easy311 is a set of tools to ease the development of
|
13
|
+
Open311 applications: it provides a wrapper around the
|
14
|
+
REST API and a close-to-compliant ActiveModel
|
15
|
+
`Request` object.}
|
16
|
+
gem.homepage = "https://github.com/jbourassa/easy311"
|
17
|
+
|
18
|
+
gem.files = `git ls-files`.split($/)
|
19
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
20
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
21
|
+
gem.require_paths = ["lib"]
|
22
|
+
|
23
|
+
gem.add_dependency('active_attr')
|
24
|
+
gem.add_dependency('rest-client')
|
25
|
+
end
|
data/easy311.yml
ADDED
data/lib/easy311.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require "active_attr"
|
2
|
+
require "easy311/version"
|
3
|
+
require "easy311/request"
|
4
|
+
require "easy311/api_wrapper"
|
5
|
+
require "easy311/service"
|
6
|
+
require "easy311/attribute"
|
7
|
+
require "easy311/response"
|
8
|
+
require "easy311/rails" if defined?(Rails)
|
9
|
+
|
10
|
+
module Easy311
|
11
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
require 'json'
|
3
|
+
require 'easy311/service'
|
4
|
+
require 'easy311/attribute'
|
5
|
+
require 'logger'
|
6
|
+
|
7
|
+
module Easy311
|
8
|
+
|
9
|
+
class ApiWrapper
|
10
|
+
|
11
|
+
attr_accessor :logger
|
12
|
+
|
13
|
+
def self.from_url(url, api_key, jurisdiction_id=nil)
|
14
|
+
resource = RestClient::Resource.new(url)
|
15
|
+
return self.new(resource, api_key, jurisdiction_id)
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(resource, api_key, jurisdiction_id=nil)
|
19
|
+
@resource = resource
|
20
|
+
@api_key = api_key
|
21
|
+
@jurisdiction_id = jurisdiction_id
|
22
|
+
end
|
23
|
+
|
24
|
+
def log
|
25
|
+
@logger ||= Easy311.logger
|
26
|
+
end
|
27
|
+
|
28
|
+
def all_services
|
29
|
+
log.info "querying list of services"
|
30
|
+
response = query_service('/services.json')
|
31
|
+
return [] if response.empty?
|
32
|
+
|
33
|
+
raw_services = JSON.parse(response)
|
34
|
+
services = raw_services.map { |r| Service.new(r) }
|
35
|
+
|
36
|
+
return services
|
37
|
+
end
|
38
|
+
|
39
|
+
def group_names
|
40
|
+
all_services.map { |s| s.group }.uniq
|
41
|
+
end
|
42
|
+
|
43
|
+
def groups
|
44
|
+
all_services.group_by { |s| s.group }
|
45
|
+
end
|
46
|
+
|
47
|
+
def attrs_from_code(code)
|
48
|
+
log.info "querying attributes for service #{code}"
|
49
|
+
url = "/services/#{code}.json"
|
50
|
+
response = query_service(url)
|
51
|
+
return [] if response.empty?
|
52
|
+
|
53
|
+
json_data = JSON.parse(response)
|
54
|
+
raw_attrs = json_data['attributes']
|
55
|
+
return [] if raw_attrs.empty?
|
56
|
+
|
57
|
+
attrs = raw_attrs.map { |a| Attribute.new(a) }
|
58
|
+
|
59
|
+
return attrs
|
60
|
+
end
|
61
|
+
|
62
|
+
def services_with_attrs
|
63
|
+
log.info "querying all services and attributes"
|
64
|
+
services = all_services.map do |service|
|
65
|
+
service.attrs = attrs_from_code(service.code)
|
66
|
+
service
|
67
|
+
end
|
68
|
+
return services
|
69
|
+
end
|
70
|
+
|
71
|
+
def query_service(path)
|
72
|
+
params = build_params
|
73
|
+
log.debug "GET #{path}"
|
74
|
+
response = @resource[path].get(params)
|
75
|
+
log.debug "RESPONSE #{response}"
|
76
|
+
return response.strip
|
77
|
+
end
|
78
|
+
|
79
|
+
def build_params
|
80
|
+
params = {}
|
81
|
+
params[:jurisdiction_id] = @jurisdiction_id unless @jurisdiction_id.nil?
|
82
|
+
return params
|
83
|
+
end
|
84
|
+
|
85
|
+
def send_request(request)
|
86
|
+
log.info "sending request #{request}"
|
87
|
+
|
88
|
+
params = request.to_post_params
|
89
|
+
params['api_key'] = @api_key
|
90
|
+
log.debug "params #{params}"
|
91
|
+
|
92
|
+
response = @resource['/requests.json'].post(params) do |http_response|
|
93
|
+
log.debug "RESPONSE #{http_response.code} #{http_response.body}"
|
94
|
+
if http_response.code.to_i >= 400
|
95
|
+
parse_error_response(http_response.body)
|
96
|
+
else
|
97
|
+
parse_post_response(http_response.body)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
log.info "response #{response}"
|
102
|
+
return response
|
103
|
+
end
|
104
|
+
|
105
|
+
def parse_post_response(body)
|
106
|
+
json_response = JSON.parse(body)
|
107
|
+
return Response.new(json_response.first)
|
108
|
+
end
|
109
|
+
|
110
|
+
def parse_error_response(body)
|
111
|
+
json_response = JSON.parse(body)
|
112
|
+
response = Response.new
|
113
|
+
response.error_message = json_response.map { |r| r['description'] }.join(",")
|
114
|
+
return response
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.logger=(logger)
|
120
|
+
@logger = logger
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.logger
|
124
|
+
@logger ||= rails_logger || default_logger
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.rails_logger
|
128
|
+
defined?(::Rails) && ::Rails.respond_to?(:logger) && ::Rails.logger
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.default_logger
|
132
|
+
Logger.new(STDOUT)
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Easy311
|
2
|
+
|
3
|
+
class Attribute
|
4
|
+
include ActiveAttr::Model
|
5
|
+
|
6
|
+
attribute :code
|
7
|
+
attribute :datatype
|
8
|
+
attribute :description
|
9
|
+
attribute :datatype_description
|
10
|
+
attribute :order
|
11
|
+
attribute :required
|
12
|
+
attribute :values
|
13
|
+
attribute :variable
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
FactoryGirl.define do
|
3
|
+
factory :easy311_service, :class => 'Easy311::Service' do
|
4
|
+
service_code "1934303d-7f43-e111-85e1-005056a60032"
|
5
|
+
service_name "Collecte des encombrants - secteur résidentiel"
|
6
|
+
group "Ordures"
|
7
|
+
description "Description à venir"
|
8
|
+
metadata true
|
9
|
+
type "batch"
|
10
|
+
|
11
|
+
trait :with_optionnal do
|
12
|
+
keywords "Collecte,Encombrants,Gros déchets,Grosses poubelles,Grosses vidanges,Meubles,Monstres"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
factory :easy311_attribute, :class => 'Easy311::Attribute' do
|
17
|
+
code "7041ac51-ec75-e211-9483-005056a613ac"
|
18
|
+
datatype "text"
|
19
|
+
datatype_description "Pour disposer d`appareils contenant des halocarbures (congélateur réfrigérateur climatiseur etc.) veuillez communiquer avec votre bureau d'arrondissement."
|
20
|
+
description "Pour disposer d`appareils contenant des halocarbures (congélateur réfrigérateur climatiseur etc.) veuillez communiquer avec votre bureau d'arrondissement."
|
21
|
+
order 2
|
22
|
+
required false
|
23
|
+
values []
|
24
|
+
variable false
|
25
|
+
end
|
26
|
+
|
27
|
+
factory :easy311_request, :class => 'Easy311::Request' do
|
28
|
+
ignore do
|
29
|
+
service :nil
|
30
|
+
end
|
31
|
+
|
32
|
+
lat 12.34
|
33
|
+
long 56.78
|
34
|
+
|
35
|
+
initialize_with { new(FactoryGirl.build(:easy311_service)) }
|
36
|
+
end
|
37
|
+
|
38
|
+
factory :easy311_request_full, :class => 'Easy311Request' do
|
39
|
+
ignore do
|
40
|
+
service :nil
|
41
|
+
end
|
42
|
+
|
43
|
+
description "desc"
|
44
|
+
lat 12.34
|
45
|
+
long 56.78
|
46
|
+
email "email@test.com"
|
47
|
+
device_id 1
|
48
|
+
account_id 2
|
49
|
+
first_name "firstname"
|
50
|
+
last_name "lastname"
|
51
|
+
phone "55512345678"
|
52
|
+
description "description"
|
53
|
+
media_url "http://url"
|
54
|
+
|
55
|
+
initialize_with { new(FactoryGirl.build(:easy311_service)) }
|
56
|
+
end
|
57
|
+
|
58
|
+
factory :easy311_response, :class => 'Easy311::Response' do
|
59
|
+
token "53020772-c676-e211-9483-005056a613ac"
|
60
|
+
service_request_id nil
|
61
|
+
service_notice nil
|
62
|
+
account_id nil
|
63
|
+
error_message nil
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Easy311
|
2
|
+
module Rails
|
3
|
+
|
4
|
+
CACHE_KEY = 'easy311_services'
|
5
|
+
|
6
|
+
def self.config_path
|
7
|
+
"#{::Rails.root}/config/easy311.yml"
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.load_config
|
11
|
+
YAML.load(ERB.new(IO.read(config_path)).result)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.load_all_services!
|
15
|
+
api_wrapper = self.api_wrapper
|
16
|
+
services = api_wrapper.services_with_attrs
|
17
|
+
|
18
|
+
::Rails.cache.write CACHE_KEY, services
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.all_services
|
22
|
+
::Rails.cache.read(CACHE_KEY) || load_all_services!
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.api_wrapper
|
26
|
+
api_config = load_config
|
27
|
+
url = api_config['url']
|
28
|
+
jurisdiction_id = api_config['jurisdiction_id']
|
29
|
+
api_key = api_config['apikey']
|
30
|
+
|
31
|
+
ApiWrapper.from_url(url, api_key, jurisdiction_id)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
2
|
+
module Easy311
|
3
|
+
class Request
|
4
|
+
include ActiveAttr::Model
|
5
|
+
|
6
|
+
attribute :description
|
7
|
+
attribute :email
|
8
|
+
attribute :first_name
|
9
|
+
attribute :last_name
|
10
|
+
attribute :phone
|
11
|
+
attribute :password
|
12
|
+
attribute :lat
|
13
|
+
attribute :long
|
14
|
+
attribute :device_id
|
15
|
+
attribute :account_id
|
16
|
+
attribute :media_url
|
17
|
+
|
18
|
+
attr_reader :service
|
19
|
+
attr_accessor :attrs_values
|
20
|
+
|
21
|
+
POST_KEYS = [
|
22
|
+
:service_code,
|
23
|
+
:description,
|
24
|
+
:lat,
|
25
|
+
:long,
|
26
|
+
:email,
|
27
|
+
:device_id,
|
28
|
+
:account_id,
|
29
|
+
:first_name,
|
30
|
+
:last_name,
|
31
|
+
:phone,
|
32
|
+
:description,
|
33
|
+
:media_url,
|
34
|
+
]
|
35
|
+
|
36
|
+
# TODO : Move out of Easy311 to our app -> not required by the spec
|
37
|
+
validates :email, :presence => true
|
38
|
+
validate :validate_required_attrs
|
39
|
+
|
40
|
+
def initialize(service)
|
41
|
+
@attrs_values = ActiveSupport::HashWithIndifferentAccess.new
|
42
|
+
@service = service
|
43
|
+
end
|
44
|
+
|
45
|
+
def attributes
|
46
|
+
super.merge("attrs" => Hash[service.attrs.map { |k, v| [k.to_s, self.send(k)]}])
|
47
|
+
end
|
48
|
+
|
49
|
+
def method_missing(method, *args, &block)
|
50
|
+
attr_name = method.to_s.gsub(/=$/, '')
|
51
|
+
if service.attrs.has_key?(attr_name)
|
52
|
+
if method.to_s.match(/=$/)
|
53
|
+
@attrs_values[attr_name] = args.first
|
54
|
+
else
|
55
|
+
@attrs_values[attr_name]
|
56
|
+
end
|
57
|
+
else
|
58
|
+
super
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def respond_to?(method, include_private=false)
|
63
|
+
if service.attrs.has_key?(method.to_s.gsub(/=$/, ''))
|
64
|
+
true
|
65
|
+
else
|
66
|
+
super
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_post_params
|
71
|
+
params = attributes.select { |k,v| !v.nil? and POST_KEYS.include? k.to_sym }
|
72
|
+
params['service_code'] = @service.code if @service.code
|
73
|
+
|
74
|
+
attrs_values.each do |key, value|
|
75
|
+
param_key = "attribute[#{key}]"
|
76
|
+
params[param_key] = value
|
77
|
+
end
|
78
|
+
|
79
|
+
params
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
def validate_required_attrs
|
84
|
+
required_attrs = @service.attrs.select { |code, attr| attr.required }.keys
|
85
|
+
return if required_attrs.empty?
|
86
|
+
validator = ActiveModel::Validations::PresenceValidator.new(
|
87
|
+
attributes: required_attrs)
|
88
|
+
validator.validate(self)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Easy311
|
2
|
+
|
3
|
+
class Response
|
4
|
+
include ActiveAttr::Model
|
5
|
+
|
6
|
+
attribute :service_request_id
|
7
|
+
attribute :token
|
8
|
+
attribute :service_notice
|
9
|
+
attribute :account_id
|
10
|
+
attribute :error_message
|
11
|
+
|
12
|
+
def valid?
|
13
|
+
error_message.nil?
|
14
|
+
end
|
15
|
+
|
16
|
+
def invalid?
|
17
|
+
!valid?
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Easy311
|
2
|
+
|
3
|
+
class Service
|
4
|
+
include ActiveAttr::Model
|
5
|
+
|
6
|
+
attribute :name
|
7
|
+
attribute :code
|
8
|
+
attribute :group
|
9
|
+
attribute :description
|
10
|
+
attribute :metadata
|
11
|
+
attribute :type
|
12
|
+
attribute :attrs, :default => {}
|
13
|
+
|
14
|
+
def service_name=(value)
|
15
|
+
self.name = value
|
16
|
+
end
|
17
|
+
|
18
|
+
def service_code=(value)
|
19
|
+
self.code = value
|
20
|
+
end
|
21
|
+
|
22
|
+
def attrs=(values)
|
23
|
+
if values.is_a?(Array)
|
24
|
+
super Hash[values.map { |value| [value.code, value] }]
|
25
|
+
elsif values.is_a?(Hash)
|
26
|
+
super values
|
27
|
+
else
|
28
|
+
raise TypeError
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def ordered_attrs
|
33
|
+
Hash[attrs.sort_by { |k, v| v.code }]
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_param
|
37
|
+
code
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,298 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'easy311/json_helper.rb'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
class ResponseStub
|
7
|
+
|
8
|
+
def initialize(response)
|
9
|
+
@response = response
|
10
|
+
end
|
11
|
+
|
12
|
+
def get(params)
|
13
|
+
return @response
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
class GetStub
|
19
|
+
attr_accessor :get
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@responses = {}
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_response(url, response)
|
26
|
+
@responses[url] = response
|
27
|
+
end
|
28
|
+
|
29
|
+
def [](key)
|
30
|
+
return ResponseStub.new(@responses[key])
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
class PostStub
|
36
|
+
|
37
|
+
def initialize(response)
|
38
|
+
@response = response
|
39
|
+
end
|
40
|
+
|
41
|
+
def [](key)
|
42
|
+
return self
|
43
|
+
end
|
44
|
+
|
45
|
+
def post(payload)
|
46
|
+
code = 200
|
47
|
+
@result = payload
|
48
|
+
resp = Struct.new(:code, :body).new(code, @response)
|
49
|
+
yield resp
|
50
|
+
end
|
51
|
+
|
52
|
+
def post_result
|
53
|
+
@result
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
class ErrorStub < PostStub
|
59
|
+
|
60
|
+
def post(payload)
|
61
|
+
code = 400
|
62
|
+
body = "[{\"code\":\"Forbidden\",\"description\":\"'api_key' : L'argument ne peut pas \xC3\xAAtre null ou vide.\"}]"
|
63
|
+
response = Struct.new(:code, :body).new(code, body)
|
64
|
+
yield response
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
api_key = "12345678-1234-A1B2-C3D4-AA1A111A1A1A"
|
70
|
+
|
71
|
+
describe Easy311::ApiWrapper do
|
72
|
+
|
73
|
+
it "returns an empty list when no services" do
|
74
|
+
resource = GetStub.new
|
75
|
+
resource.add_response('/services.json', "")
|
76
|
+
|
77
|
+
wrapper = Easy311::ApiWrapper.new(resource, api_key)
|
78
|
+
wrapper.all_services.should == []
|
79
|
+
end
|
80
|
+
|
81
|
+
it "returns once service" do
|
82
|
+
resource = GetStub.new()
|
83
|
+
service_json = [sample_service].to_json
|
84
|
+
resource.add_response('/services.json', service_json)
|
85
|
+
|
86
|
+
wrapper = Easy311::ApiWrapper.new(resource, api_key)
|
87
|
+
all_services = wrapper.all_services
|
88
|
+
|
89
|
+
all_services.length.should == 1
|
90
|
+
service = all_services.first
|
91
|
+
|
92
|
+
service.name.should == sample_service['service_name']
|
93
|
+
service.code.should == sample_service['service_code']
|
94
|
+
service.group.should == sample_service['group']
|
95
|
+
service.description.should == sample_service['description']
|
96
|
+
service.metadata.should == sample_service['metadata']
|
97
|
+
service.type.should == sample_service['type']
|
98
|
+
service.to_param.should == sample_service['service_code']
|
99
|
+
end
|
100
|
+
|
101
|
+
it "returns a service with minimal keys" do
|
102
|
+
resource = GetStub.new
|
103
|
+
service_json = [min_sample_service].to_json
|
104
|
+
resource.add_response('/services.json', service_json)
|
105
|
+
|
106
|
+
wrapper = Easy311::ApiWrapper.new(resource, api_key)
|
107
|
+
all_services = wrapper.all_services
|
108
|
+
|
109
|
+
all_services.length.should == 1
|
110
|
+
service = all_services[0]
|
111
|
+
|
112
|
+
service.name.should == sample_service['service_name']
|
113
|
+
service.code.should == sample_service['service_code']
|
114
|
+
service.group.should == sample_service['group']
|
115
|
+
service.description.should == sample_service['description']
|
116
|
+
service.metadata.should == sample_service['metadata']
|
117
|
+
service.type.should == sample_service['type']
|
118
|
+
service.to_param.should == sample_service['service_code']
|
119
|
+
end
|
120
|
+
|
121
|
+
it "returns the names of the groups" do
|
122
|
+
resource = GetStub.new
|
123
|
+
service_json = [min_sample_service].to_json
|
124
|
+
resource.add_response('/services.json', service_json)
|
125
|
+
|
126
|
+
wrapper = Easy311::ApiWrapper.new(resource, "1234")
|
127
|
+
|
128
|
+
wrapper.group_names.should == ["Ordures"]
|
129
|
+
end
|
130
|
+
|
131
|
+
it "returns a single group name with 2 services" do
|
132
|
+
resource = GetStub.new
|
133
|
+
service_json = [sample_service, sample_service].to_json
|
134
|
+
resource.add_response('/services.json', service_json)
|
135
|
+
|
136
|
+
wrapper = Easy311::ApiWrapper.new(resource, "1234")
|
137
|
+
|
138
|
+
result = ["Ordures"]
|
139
|
+
expect(wrapper.group_names).to eq(result)
|
140
|
+
end
|
141
|
+
|
142
|
+
it "returns a single group name with 2 services" do
|
143
|
+
resource = GetStub.new
|
144
|
+
second_service = sample_service.dup
|
145
|
+
second_service['group'] = 'Trottoirs'
|
146
|
+
service_json = [sample_service, second_service].to_json
|
147
|
+
resource.add_response('/services.json', service_json)
|
148
|
+
|
149
|
+
wrapper = Easy311::ApiWrapper.new(resource, api_key)
|
150
|
+
|
151
|
+
result = ["Ordures", "Trottoirs"]
|
152
|
+
wrapper.group_names.should == result
|
153
|
+
end
|
154
|
+
|
155
|
+
it "groups services together" do
|
156
|
+
resource = GetStub.new
|
157
|
+
second_service = sample_service.dup
|
158
|
+
second_service['group'] = 'Trottoirs'
|
159
|
+
service_json = [sample_service, second_service].to_json
|
160
|
+
resource.add_response('/services.json', service_json)
|
161
|
+
|
162
|
+
wrapper = Easy311::ApiWrapper.new(resource, api_key)
|
163
|
+
|
164
|
+
groups = wrapper.groups
|
165
|
+
groups.keys.should == ["Ordures", "Trottoirs"]
|
166
|
+
groups["Ordures"].should be_an_instance_of(Array)
|
167
|
+
groups["Ordures"][0].should be_an_instance_of(Easy311::Service)
|
168
|
+
end
|
169
|
+
|
170
|
+
it "returns no attrs with an empty string" do
|
171
|
+
resource = GetStub.new
|
172
|
+
attribute_json = ""
|
173
|
+
resource.add_response('/services/01234-1234-1234-1234.json', attribute_json)
|
174
|
+
|
175
|
+
code = "01234-1234-1234-1234"
|
176
|
+
wrapper = Easy311::ApiWrapper.new(resource, "1234")
|
177
|
+
attrs = wrapper.attrs_from_code(code)
|
178
|
+
|
179
|
+
attrs.should == []
|
180
|
+
end
|
181
|
+
|
182
|
+
it "returns one attribute" do
|
183
|
+
resource = GetStub.new
|
184
|
+
attribute_json = sample_attribute.to_json
|
185
|
+
resource.add_response('/services/01234-1234-1234-1234.json', attribute_json)
|
186
|
+
|
187
|
+
code = "01234-1234-1234-1234"
|
188
|
+
wrapper = Easy311::ApiWrapper.new(resource, "1234")
|
189
|
+
result = wrapper.attrs_from_code(code)
|
190
|
+
|
191
|
+
expect(result.length).to eq(1)
|
192
|
+
attribute = result[0]
|
193
|
+
|
194
|
+
expect(attribute.code).to eq("7041ac51-ec75-e211-9483-005056a613ac")
|
195
|
+
expect(attribute.datatype).to eq("text")
|
196
|
+
expect(attribute.description).to eq("Pour disposer d`appareils contenant des halocarbures (congélateur, réfrigérateur, climatiseur, etc.), veuillez communiquer avec votre bureau d'arrondissement.")
|
197
|
+
expect(attribute.datatype_description).to eq("Pour disposer d`appareils contenant des halocarbures (congélateur, réfrigérateur, climatiseur, etc.), veuillez communiquer avec votre bureau d'arrondissement.")
|
198
|
+
expect(attribute.order).to eq(2)
|
199
|
+
expect(attribute.required).to eq(false)
|
200
|
+
expect(attribute.values).to eq([])
|
201
|
+
expect(attribute.variable).to eq(false)
|
202
|
+
end
|
203
|
+
|
204
|
+
it "returns a service with an attribute" do
|
205
|
+
resource = GetStub.new
|
206
|
+
service_json = [sample_service].to_json
|
207
|
+
attribute_json = sample_attribute.to_json
|
208
|
+
resource.add_response('/services.json', [sample_service].to_json)
|
209
|
+
resource.add_response('/services/1934303d-7f43-e111-85e1-005056a60032.json', attribute_json)
|
210
|
+
|
211
|
+
wrapper = Easy311::ApiWrapper.new(resource, "1234")
|
212
|
+
services = wrapper.services_with_attrs
|
213
|
+
service = services[0]
|
214
|
+
|
215
|
+
expect(service.name).to eq("Collecte des encombrants - secteur résidentiel")
|
216
|
+
expect(service.code).to eq("1934303d-7f43-e111-85e1-005056a60032")
|
217
|
+
expect(service.group).to eq("Ordures")
|
218
|
+
expect(service.description).to eq("Description à venir")
|
219
|
+
expect(service.metadata).to eq(true)
|
220
|
+
expect(service.type).to eq("batch")
|
221
|
+
|
222
|
+
service.attrs.should be_an_instance_of(Hash)
|
223
|
+
attribute = service.attrs.values.first
|
224
|
+
|
225
|
+
expect(attribute.code).to eq("7041ac51-ec75-e211-9483-005056a613ac")
|
226
|
+
expect(attribute.datatype).to eq("text")
|
227
|
+
expect(attribute.description).to eq("Pour disposer d`appareils contenant des halocarbures (congélateur, réfrigérateur, climatiseur, etc.), veuillez communiquer avec votre bureau d'arrondissement.")
|
228
|
+
expect(attribute.datatype_description).to eq("Pour disposer d`appareils contenant des halocarbures (congélateur, réfrigérateur, climatiseur, etc.), veuillez communiquer avec votre bureau d'arrondissement.")
|
229
|
+
expect(attribute.order).to eq(2)
|
230
|
+
expect(attribute.required).to eq(false)
|
231
|
+
expect(attribute.values).to eq([])
|
232
|
+
expect(attribute.variable).to eq(false)
|
233
|
+
end
|
234
|
+
|
235
|
+
it "sends a request to the api" do
|
236
|
+
resource = PostStub.new([sample_response].to_json)
|
237
|
+
request = FactoryGirl.build(:easy311_request)
|
238
|
+
|
239
|
+
wrapper = Easy311::ApiWrapper.new(resource, api_key)
|
240
|
+
wrapper.send_request(request)
|
241
|
+
|
242
|
+
expected = {
|
243
|
+
'api_key' => api_key,
|
244
|
+
'service_code' => request.service.code,
|
245
|
+
'lat' => request.lat,
|
246
|
+
'long' => request.long,
|
247
|
+
}
|
248
|
+
|
249
|
+
resource.post_result.should == expected
|
250
|
+
end
|
251
|
+
|
252
|
+
it "sends a request with attributes to the api" do
|
253
|
+
resource = PostStub.new([sample_response].to_json)
|
254
|
+
request = FactoryGirl.build(:easy311_request)
|
255
|
+
request.attrs_values = {
|
256
|
+
'1234-1234-1234-1234' => '2345-2345-2345-2345',
|
257
|
+
'3456-3456-3456-3456' => '4567-4567-4567-4567'
|
258
|
+
}
|
259
|
+
|
260
|
+
wrapper = Easy311::ApiWrapper.new(resource, api_key)
|
261
|
+
wrapper.send_request(request)
|
262
|
+
|
263
|
+
expected = {
|
264
|
+
'api_key' => api_key,
|
265
|
+
'service_code' => request.service.code,
|
266
|
+
'lat' => request.lat,
|
267
|
+
'long' => request.long,
|
268
|
+
'attribute[1234-1234-1234-1234]' => '2345-2345-2345-2345',
|
269
|
+
'attribute[3456-3456-3456-3456]' => '4567-4567-4567-4567',
|
270
|
+
}
|
271
|
+
|
272
|
+
resource.post_result.should == expected
|
273
|
+
end
|
274
|
+
|
275
|
+
it "returns a response when sending a request" do
|
276
|
+
resource = PostStub.new([sample_response].to_json)
|
277
|
+
request = FactoryGirl.build(:easy311_request)
|
278
|
+
wrapper = Easy311::ApiWrapper.new(resource, api_key)
|
279
|
+
|
280
|
+
response = wrapper.send_request(request)
|
281
|
+
|
282
|
+
response.account_id.should be_nil
|
283
|
+
response.service_notice.should be_nil
|
284
|
+
response.service_request_id.should be_nil
|
285
|
+
response.token.should == "c5f0d6ad-59ca-44a4-86e8-a2d72708f54c"
|
286
|
+
end
|
287
|
+
|
288
|
+
it "raises an exception when api returns a 400 error" do
|
289
|
+
resource = ErrorStub.new([sample_response].to_json)
|
290
|
+
request = FactoryGirl.build(:easy311_request)
|
291
|
+
|
292
|
+
wrapper = Easy311::ApiWrapper.new(resource, api_key)
|
293
|
+
response = wrapper.send_request(request)
|
294
|
+
response.invalid?.should == true
|
295
|
+
response.error_message.should == "'api_key' : L'argument ne peut pas \xC3\xAAtre null ou vide."
|
296
|
+
end
|
297
|
+
|
298
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
def min_sample_service(opts={})
|
3
|
+
{
|
4
|
+
"service_code"=>"1934303d-7f43-e111-85e1-005056a60032",
|
5
|
+
"service_name"=>"Collecte des encombrants - secteur résidentiel",
|
6
|
+
"group"=>"Ordures",
|
7
|
+
"description"=>"Description à venir",
|
8
|
+
"metadata"=>true,
|
9
|
+
"type"=>"batch"
|
10
|
+
}.merge(opts)
|
11
|
+
end
|
12
|
+
|
13
|
+
def sample_service(opts={})
|
14
|
+
{
|
15
|
+
"description"=>"Description à venir",
|
16
|
+
"group"=>"Ordures",
|
17
|
+
"keywords"=> "Collecte,Encombrants,Gros déchets,Grosses poubelles,Grosses vidanges,Meubles,Monstres",
|
18
|
+
"metadata"=>true,
|
19
|
+
"service_code"=>"1934303d-7f43-e111-85e1-005056a60032",
|
20
|
+
"service_name"=>"Collecte des encombrants - secteur résidentiel",
|
21
|
+
"type"=>"batch"
|
22
|
+
}.merge(opts)
|
23
|
+
end
|
24
|
+
|
25
|
+
def sample_attribute
|
26
|
+
{
|
27
|
+
"attributes"=> [one_attribute]
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def one_attribute(opts={})
|
32
|
+
{
|
33
|
+
"code"=>"7041ac51-ec75-e211-9483-005056a613ac",
|
34
|
+
"datatype"=>"text",
|
35
|
+
"datatype_description"=> "Pour disposer d`appareils contenant des halocarbures (congélateur, réfrigérateur, climatiseur, etc.), veuillez communiquer avec votre bureau d'arrondissement.",
|
36
|
+
"description"=> "Pour disposer d`appareils contenant des halocarbures (congélateur, réfrigérateur, climatiseur, etc.), veuillez communiquer avec votre bureau d'arrondissement.",
|
37
|
+
"order"=>2,
|
38
|
+
"required"=>false,
|
39
|
+
"values"=>[],
|
40
|
+
"variable"=>false
|
41
|
+
}.merge(opts)
|
42
|
+
end
|
43
|
+
|
44
|
+
def sample_response(opts={})
|
45
|
+
{
|
46
|
+
"account_id" => nil,
|
47
|
+
"service_notice" => nil,
|
48
|
+
"service_request_id" => nil,
|
49
|
+
"token" => "c5f0d6ad-59ca-44a4-86e8-a2d72708f54c"
|
50
|
+
}.merge(opts)
|
51
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
describe Easy311::Request do
|
2
|
+
|
3
|
+
describe "responds to method missing" do
|
4
|
+
|
5
|
+
subject do
|
6
|
+
service = FactoryGirl.build(:easy311_service, attrs: [
|
7
|
+
FactoryGirl.build(:easy311_attribute, :code => "some_attribute")])
|
8
|
+
Easy311::Request.new(service)
|
9
|
+
end
|
10
|
+
it { should respond_to(:some_attribute) }
|
11
|
+
it { should respond_to(:some_attribute=) }
|
12
|
+
it { should_not respond_to(:invalid_attribute) }
|
13
|
+
it { should_not respond_to(:invalid_attribute=) }
|
14
|
+
it "writes Easy311 attribute values to ActiveAttr's" do
|
15
|
+
subject.some_attribute = 'Value 123'
|
16
|
+
subject.some_attribute.should == 'Value 123'
|
17
|
+
end
|
18
|
+
|
19
|
+
it "merges ActiveAttr and Easy311 fields when calling #attributes" do
|
20
|
+
subject.some_attribute = "some_value"
|
21
|
+
subject.attrs_values.should have_key("some_attribute")
|
22
|
+
end
|
23
|
+
|
24
|
+
it "has the same value in attributes" do
|
25
|
+
subject.some_attribute = "some_value"
|
26
|
+
subject.attributes["attrs"]["some_attribute"].should == "some_value"
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "generates attributes to send to api_wrapper" do
|
32
|
+
|
33
|
+
it "converts lat and long" do
|
34
|
+
service = FactoryGirl.build(:easy311_service)
|
35
|
+
service.code = "1234"
|
36
|
+
|
37
|
+
request = Easy311::Request.new(service)
|
38
|
+
request.lat = 12.23
|
39
|
+
request.long = 23.34
|
40
|
+
|
41
|
+
expected = {
|
42
|
+
'service_code' => "1234",
|
43
|
+
'lat' => 12.23,
|
44
|
+
'long' => 23.34,
|
45
|
+
}
|
46
|
+
|
47
|
+
request.to_post_params.should == expected
|
48
|
+
end
|
49
|
+
|
50
|
+
it "converts all attributes to send to api_wrapper" do
|
51
|
+
service = FactoryGirl.build(:easy311_service)
|
52
|
+
service.code = "1234"
|
53
|
+
|
54
|
+
params = {
|
55
|
+
'service_code' => service.code,
|
56
|
+
'description' => "desc",
|
57
|
+
'lat' => 12.34,
|
58
|
+
'long' => 56.78,
|
59
|
+
'email' => "email@test.com",
|
60
|
+
'device_id' => 1,
|
61
|
+
'account_id' => 2,
|
62
|
+
'first_name' => "firstname",
|
63
|
+
'last_name' => "lastname",
|
64
|
+
'phone' => "55512345678",
|
65
|
+
'description' => "description",
|
66
|
+
'media_url' => "http://url"
|
67
|
+
}
|
68
|
+
|
69
|
+
request = Easy311::Request.new(service)
|
70
|
+
params.except('service_code').each do |key, value|
|
71
|
+
request.send("#{key}=", value)
|
72
|
+
end
|
73
|
+
|
74
|
+
request.to_post_params.should == params
|
75
|
+
end
|
76
|
+
|
77
|
+
it "converts question values to send to api_wrapper" do
|
78
|
+
service = FactoryGirl.build(:easy311_service)
|
79
|
+
service.code = "1234"
|
80
|
+
|
81
|
+
request = Easy311::Request.new(service)
|
82
|
+
request.lat = 12.34
|
83
|
+
request.long = 23.45
|
84
|
+
request.attrs_values['value1'] = "param1"
|
85
|
+
|
86
|
+
expected = {
|
87
|
+
'service_code' => "1234",
|
88
|
+
'lat' => 12.34,
|
89
|
+
'long' => 23.45,
|
90
|
+
'attribute[value1]' => "param1",
|
91
|
+
}
|
92
|
+
|
93
|
+
request.to_post_params.should == expected
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "validations" do
|
99
|
+
it "responds to valid?" do
|
100
|
+
FactoryGirl.build(:easy311_service).should respond_to(:valid?)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "validates required attributes" do
|
104
|
+
required_attr = FactoryGirl.build(:easy311_attribute, required: true)
|
105
|
+
service = FactoryGirl.build(:easy311_service, :attrs => [required_attr])
|
106
|
+
|
107
|
+
request = Easy311::Request.new(service)
|
108
|
+
request.valid?.should be_false
|
109
|
+
request.errors.should have_key(required_attr.code.to_sym)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "easy311"
|
2
|
+
require "factory_girl"
|
3
|
+
require "easy311/factories"
|
4
|
+
|
5
|
+
Easy311.logger = Logger.new('/dev/null')
|
6
|
+
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
9
|
+
|
10
|
+
# Run specs in random order to surface order dependencies. If you find an
|
11
|
+
# order dependency and want to debug it, you can fix the order by providing
|
12
|
+
# the seed, which is printed after each run.
|
13
|
+
# --seed 1234
|
14
|
+
config.order = 'random'
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: easy311
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jimmy Bourassa
|
9
|
+
- Gregory Eric Sanderson
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2013-02-23 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
version_requirements: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ! '>='
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
none: false
|
22
|
+
name: active_attr
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
requirement: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
none: false
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
version_requirements: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - ! '>='
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '0'
|
37
|
+
none: false
|
38
|
+
name: rest-client
|
39
|
+
type: :runtime
|
40
|
+
prerelease: false
|
41
|
+
requirement: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
none: false
|
47
|
+
description: Set of tools to ease the development of Open311 applications
|
48
|
+
email:
|
49
|
+
- jimmy.bourassa@hooktstudios.com
|
50
|
+
- gzou2000@gmail.com
|
51
|
+
executables: []
|
52
|
+
extensions: []
|
53
|
+
extra_rdoc_files: []
|
54
|
+
files:
|
55
|
+
- .gitignore
|
56
|
+
- .rspec
|
57
|
+
- Gemfile
|
58
|
+
- LICENSE.txt
|
59
|
+
- README.md
|
60
|
+
- Rakefile
|
61
|
+
- easy311.gemspec
|
62
|
+
- easy311.yml
|
63
|
+
- lib/easy311.rb
|
64
|
+
- lib/easy311/api_wrapper.rb
|
65
|
+
- lib/easy311/attribute.rb
|
66
|
+
- lib/easy311/factories.rb
|
67
|
+
- lib/easy311/rails.rb
|
68
|
+
- lib/easy311/request.rb
|
69
|
+
- lib/easy311/response.rb
|
70
|
+
- lib/easy311/service.rb
|
71
|
+
- lib/easy311/version.rb
|
72
|
+
- spec/easy311/api_wrapper_spec.rb
|
73
|
+
- spec/easy311/json_helper.rb
|
74
|
+
- spec/easy311/request_spec.rb
|
75
|
+
- spec/spec_helper.rb
|
76
|
+
homepage: https://github.com/jbourassa/easy311
|
77
|
+
licenses: []
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
none: false
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
none: false
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 1.8.23
|
97
|
+
signing_key:
|
98
|
+
specification_version: 3
|
99
|
+
summary: ! 'Easy311 is a set of tools to ease the development of Open311 applications:
|
100
|
+
it provides a wrapper around the REST API and a close-to-compliant ActiveModel `Request`
|
101
|
+
object.'
|
102
|
+
test_files:
|
103
|
+
- spec/easy311/api_wrapper_spec.rb
|
104
|
+
- spec/easy311/json_helper.rb
|
105
|
+
- spec/easy311/request_spec.rb
|
106
|
+
- spec/spec_helper.rb
|
107
|
+
has_rdoc:
|