easy311 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 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: