easy311 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in easy311.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem "factory_girl"
8
+ gem "rspec", "~> 2.12"
9
+ end
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
@@ -0,0 +1,4 @@
1
+ # This is a sample configuration file for Easy311
2
+ url: __URL__
3
+ apikey: __API_KEY__
4
+ jurisdiction_id: _ID__
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,3 @@
1
+ module Easy311
2
+ VERSION = "0.0.1"
3
+ 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
+
@@ -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: