fulcrum 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,16 @@
1
+ module Fulcrum
2
+ class Member < Api
3
+
4
+ class << self
5
+
6
+ def all(opts = {})
7
+ params = parse_opts([:page], opts)
8
+ call(:get, 'members.json', params)
9
+ end
10
+
11
+ def find(id)
12
+ call(:get, "members/#{id}.json")
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,36 @@
1
+ module Fulcrum
2
+ class Photo < Api
3
+
4
+ ALLOWED_FORMATS = %w(json image)
5
+
6
+ class << self
7
+
8
+ def find(id, opts = {})
9
+ format = image_opts(opts)
10
+ call(:get, "photos/#{id}.#{format}")
11
+ end
12
+
13
+ def thumbnail(id, opts = {})
14
+ format = image_opts(opts)
15
+ call(:get, "photos/#{id}/thumbnail.#{format}")
16
+ end
17
+
18
+ def create(file, content_type, id, label = '')
19
+ photo = Faraday::UploadIO.new(file, content_type)
20
+ call(:post, 'photos.json', { photo: { file: photo, access_key: id, label: label}})
21
+ end
22
+
23
+ def delete(id)
24
+ call(:delete, "photos/#{id}.json")
25
+ end
26
+
27
+ def image_opts(opts = {})
28
+ opts = opts.with_indifferent_access
29
+ format = opts.delete(:format) || 'json'
30
+ raise ArgumentError, "#{format} is not an allowed format, use either 'json' or 'image'" unless ALLOWED_FORMATS.include?(format)
31
+ format = "jpg" if format == 'image'
32
+ format
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,38 @@
1
+ module Fulcrum
2
+ class Record < Api
3
+
4
+ class << self
5
+
6
+ def all(opts = {})
7
+ params = parse_opts([:page, :form_id, :bounding_box, :updated_since], opts)
8
+ call(:get, 'records.json', params)
9
+ end
10
+
11
+ def find(id)
12
+ call(:get, "records/#{id}.json")
13
+ end
14
+
15
+ def create(record)
16
+ validation = RecordValidator.new(record)
17
+ if validation.valid?
18
+ call(:post, 'records.json', record)
19
+ else
20
+ { error: { validation: validation.errors } }
21
+ end
22
+ end
23
+
24
+ def update(id, record)
25
+ validation = RecordValidator.new(record)
26
+ if validation.valid?
27
+ call(:put, "records/#{id}.json", record)
28
+ else
29
+ { error: { validation: validation.errors } }
30
+ end
31
+ end
32
+
33
+ def delete(id)
34
+ call(:delete, "records/#{id}.json")
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,31 @@
1
+ require 'active_support/core_ext/hash'
2
+
3
+ module Fulcrum
4
+ class BaseValidator
5
+ attr_accessor :data
6
+ attr_accessor :errors
7
+
8
+ def initialize(data)
9
+ @data = (data.is_a?(Hash) ? data : JSON.parse(data)).with_indifferent_access
10
+ @errors = {}
11
+ validate!
12
+ end
13
+
14
+ def valid?
15
+ errors.empty?
16
+ end
17
+
18
+ def add_error(key, data_name, error)
19
+ if errors.has_key?(key)
20
+ if errors[key].has_key?(data_name)
21
+ errors[key][data_name].push(error)
22
+ else
23
+ errors[key][data_name] = [error]
24
+ end
25
+ else
26
+ errors[key] = {}
27
+ errors[key][data_name] = [error]
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ module Fulcrum
2
+ class ChoiceListValidator < BaseValidator
3
+
4
+ def validate!
5
+ if data['choice_list'].kind_of?(Hash) && !data['choice_list'].blank?
6
+ add_error('choice_list', 'name', 'must not be blank') if data['choice_list']['name'].blank?
7
+ choices(data['choice_list']['choices'])
8
+ else
9
+ @errors['choice_list'] = ['must be a non-empty hash']
10
+ end
11
+ return valid?
12
+ end
13
+
14
+ def choices(elements)
15
+ if elements.kind_of?(Array) && !elements.empty?
16
+ elements.each do |choice|
17
+ if choice.blank?
18
+ add_error('choices', 'choice', 'cannot be empty')
19
+ else
20
+ if choice['label'].blank?
21
+ add_error('choice', 'label', 'is required')
22
+ end
23
+ end
24
+ end
25
+ else
26
+ add_error('choice_list', 'choices', 'must be a non-empty array')
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,38 @@
1
+ module Fulcrum
2
+ class ClassificationSetValidator < BaseValidator
3
+
4
+ def validate!
5
+ if data['classification_set']
6
+ add_error('classification_set', 'name', 'cannot be blank') if data['classification_set']['name'].blank?
7
+
8
+ if data['classification_set']['items'].blank? || !data['classification_set']['items'].kind_of?(Array)
9
+ add_error('classification_set', 'items', 'must be a non-empty array')
10
+ else
11
+ items(data['classification_set']['items'])
12
+ end
13
+ else
14
+ @errors['classification_set'] = ['must exist and not be blank']
15
+ end
16
+ return valid?
17
+ end
18
+
19
+ def items(elements, child = false)
20
+ parent, child = child ? ['child_classification', 'item'] : ['items', 'item']
21
+ if elements.kind_of?(Array) && !elements.empty?
22
+ elements.each do |element|
23
+ if element.blank?
24
+ add_error(parent, child, 'cannot be empty')
25
+ else
26
+ if element['label'].blank?
27
+ add_error(child, 'label', 'is required')
28
+ end
29
+ end
30
+ items(element['child_classifications'], true) if element.has_key?('child_classifications')
31
+ end
32
+ else
33
+ add_error(parent, child, 'must be a non-empty array')
34
+ end
35
+ end
36
+ end
37
+ end
38
+
@@ -0,0 +1,150 @@
1
+ module Fulcrum
2
+ class FormValidator < BaseValidator
3
+
4
+ TYPES = %w(
5
+ TextField
6
+ ChoiceField
7
+ ClassificationField
8
+ PhotoField
9
+ DateTimeField
10
+ Section
11
+ )
12
+
13
+ def form_elements
14
+ data['form']['elements']
15
+ end
16
+
17
+ def form_name
18
+ data['form']['name']
19
+ end
20
+
21
+ def validate!
22
+ @items = {}
23
+ if data[:form]
24
+ if form_elements && !form_elements.empty?
25
+ add_error('form', 'name', 'cannot be blank') if data[:form][:name].blank?
26
+ fields(form_elements)
27
+ conditionals(form_elements)
28
+ else
29
+ add_error('form', 'elements', 'must be a non-empty array')
30
+ end
31
+ else
32
+ @errors['form'] = ['must exist and not be empty']
33
+ end
34
+ return valid?
35
+ end
36
+
37
+ def fields(elements)
38
+ if elements.kind_of?(Array) && !elements.empty?
39
+ elements.each { |element| field(element) }
40
+ else
41
+ add_error('form', 'elements', 'must be a non-empty array')
42
+ end
43
+ end
44
+
45
+ def field(element)
46
+ if element.blank?
47
+ add_error('elements', 'element', 'must not be empty')
48
+ else
49
+ if !element[:key]
50
+ add_error('element', 'key', 'must exist and not be nil')
51
+ return false
52
+ end
53
+
54
+ if @items.include?(element[:key])
55
+ add_error(element[:key], :key, 'must be unique')
56
+ return false
57
+ end
58
+
59
+ key = element[:key]
60
+ @items[key] = element[:type]
61
+
62
+ add_error(key, 'label', 'is required') if element[:label].blank?
63
+ add_error(key, 'data_name', 'is required') if element[:data_name].blank?
64
+ add_error(key, 'type', 'is not one of the valid types') unless TYPES.include?(element[:type])
65
+
66
+ %w(disabled hidden required).each do |attrib|
67
+ add_error(key, attrib, 'must be true or false') unless [true, false].include?(element[attrib])
68
+ end
69
+
70
+ case element[:type]
71
+
72
+ when 'ClassificationField'
73
+ if element[:classification_set_id]
74
+ add_error(key, 'classification_set_id', 'is required') if element[:classification_set_id].blank?
75
+ end
76
+
77
+ when 'Section'
78
+ if element['elements'].is_a?(Array)
79
+ if element['elements'].any?
80
+ fields(element['elements'])
81
+ else
82
+ add_error(key, 'elements', 'must contain additional elements')
83
+ end
84
+ else
85
+ add_error(key, 'elements', 'must be an array object')
86
+ end
87
+
88
+ when 'ChoiceField'
89
+ if element['choice_list_id']
90
+ add_error(key, 'choice_list_id', 'is required') if element[:choice_list_id].blank?
91
+ else
92
+ if element['choices'].is_a?(Array) && !element['choices'].blank?
93
+ element['choices'].each do |choice|
94
+ unless choice.has_key?('label') && choice['label'].present?
95
+ add_error('choices', 'label', 'contains an invalid label')
96
+ end
97
+ end
98
+ else
99
+ add_error(key, 'choices', 'must be a non-empty array')
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ def conditionals(elements)
107
+ elements.each { |element| conditional(element) }
108
+ end
109
+
110
+ def conditional(element)
111
+
112
+ operators = case element[:type]
113
+ when 'ChoiceField', 'ClassificationField'
114
+ %w(equal_to not_equal_to is_empty is_not_empty)
115
+ else
116
+ %w(equal_to not_equal_to contains starts_with greater_than less_than is_empty is_not_empty)
117
+ end
118
+
119
+ %w(required_conditions visible_conditions).each do |field|
120
+
121
+ if type = element["#{field}_type"]
122
+ add_error(key, "#{field}_type", 'is not valid') unless %(any all).include?(type)
123
+ end
124
+
125
+ if element[field]
126
+ if element[field].is_a?(Array)
127
+ element[field].each do |condition|
128
+
129
+ if key = condition[:field_key]
130
+ add_error(key, field, "key #{key} does not exist on the form") unless @items.keys.include?(key)
131
+ add_error(key, field, "operator for #{key} is invalid") unless operators.include?(condition[:operator])
132
+
133
+ if %w(is_empty is_not_empty).include?(condition[:operator]) && condition[:value].present?
134
+ add_error(key, field, 'value cannot be blank')
135
+ end
136
+ else
137
+ add_error(key, field, 'field key must exist for condition')
138
+ end
139
+
140
+ end
141
+ else
142
+ add_error(key, field, 'must be an array object')
143
+ end
144
+ end
145
+ end
146
+
147
+ conditionals(element[:elements]) if element[:type] == 'Section'
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,18 @@
1
+ module Fulcrum
2
+ class RecordValidator < BaseValidator
3
+ def validate!
4
+ if data['record'].kind_of?(Hash) && !data['record'].empty?
5
+ add_error('record', 'form_id', 'cannot be blank') if data['record']['form_id'].blank?
6
+ if data['record']['latitude'].to_f == 0.0 || data['record']['longitude'] == 0.0
7
+ add_error('record', 'coordinates', 'must be in WGS84 format')
8
+ end
9
+ if !data['record']['form_values'].kind_of?(Hash) || data['record']['form_values'].blank?
10
+ add_error('record', 'form_values', 'must be a non-empty hash')
11
+ end
12
+ else
13
+ @errors['record'] = ['must be a non-empty hash']
14
+ end
15
+ return valid?
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module Fulcrum
2
+ VERSION = "0.1.2"
3
+ end
@@ -0,0 +1,175 @@
1
+ {
2
+ "form": {
3
+ "name": "Park Maintinence",
4
+ "description": "Form for inspection of park's state of maintenance.",
5
+ "created_at": "2012-04-27T10:48:31-04:00",
6
+ "updated_at": "2012-04-27T10:58:39-04:00",
7
+ "elements": [
8
+ {
9
+ "disabled":false,
10
+ "hidden":false,
11
+ "key":"b8fbca33-cad6-1eb5-b4ec-d78cf8aa9323",
12
+ "type":"Section",
13
+ "data_name":"park_report",
14
+ "required":false,
15
+ "label":"Park Report",
16
+ "elements":[
17
+ {
18
+ "disabled":false,
19
+ "hidden":false,
20
+ "multiple":false,
21
+ "allow_other":true,
22
+ "key":"42277b35-92a0-2fe5-15df-baf4a2c11d69",
23
+ "type":"ChoiceField",
24
+ "data_name":"amenity_type",
25
+ "required":false,
26
+ "label":"Amenity Type",
27
+ "choices":[
28
+ {
29
+ "value":"Boardwalk",
30
+ "label":"Boardwalk"
31
+ },
32
+ {
33
+ "value":"Pavilion",
34
+ "label":"Pavilion"
35
+ },
36
+ {
37
+ "value":"Bench",
38
+ "label":"Bench"
39
+ },
40
+ {
41
+ "value":"Path",
42
+ "label":"Path"
43
+ },
44
+ {
45
+ "value":"Playground Equipment",
46
+ "label":"Playground Equipment"
47
+ },
48
+ {
49
+ "value":"Land",
50
+ "label":"Land"
51
+ },
52
+ {
53
+ "value":"Restroom",
54
+ "label":"Restroom"
55
+ },
56
+ {
57
+ "value":"Athletic Field",
58
+ "label":"Athletic Field"
59
+ },
60
+ {
61
+ "value":"Boat Ramp",
62
+ "label":"Boat Ramp"
63
+ },
64
+ {
65
+ "value":"Picnic Table",
66
+ "label":"Picnic Table"
67
+ },
68
+ {
69
+ "value":"Dog Park",
70
+ "label":"Dog Park"
71
+ }
72
+ ]
73
+ },
74
+ {
75
+ "disabled":false,
76
+ "hidden":false,
77
+ "multiple":false,
78
+ "allow_other":true,
79
+ "key":"dec7d431-981e-55fe-ce27-fa0ce588b7b3",
80
+ "type":"ChoiceField",
81
+ "data_name":"type_of_problem",
82
+ "required":false,
83
+ "label":"Type of Problem",
84
+ "choices":[
85
+ {
86
+ "value":"Vandalism",
87
+ "label":"Vandalism"
88
+ },
89
+ {
90
+ "value":"Invasive Plant Species",
91
+ "label":"Invasive Plant Species"
92
+ },
93
+ {
94
+ "value":"Pests",
95
+ "label":"Pests"
96
+ },
97
+ {
98
+ "value":"Damage",
99
+ "label":"Damage"
100
+ },
101
+ {
102
+ "value":"None",
103
+ "label":"None"
104
+ }
105
+ ]
106
+ },
107
+ {
108
+ "disabled":false,
109
+ "hidden":false,
110
+ "key":"bbd86b27-4792-1ba3-6bf3-65b7bb5ee124",
111
+ "type":"TextField",
112
+ "data_name":"problem_notes",
113
+ "required":false,
114
+ "label":"Problem Notes"
115
+ },
116
+ {
117
+ "disabled":false,
118
+ "hidden":false,
119
+ "multiple":false,
120
+ "allow_other":true,
121
+ "key":"36d58746-2b5c-a545-3009-5744de326e69",
122
+ "type":"ChoiceField",
123
+ "data_name":"urgency_",
124
+ "required":false,
125
+ "label":"Urgency ",
126
+ "choices":[
127
+ {
128
+ "value":"High",
129
+ "label":"High"
130
+ },
131
+ {
132
+ "value":"Medium",
133
+ "label":"Medium"
134
+ },
135
+ {
136
+ "value":"Low",
137
+ "label":"Low"
138
+ },
139
+ {
140
+ "value":"None",
141
+ "label":"None"
142
+ }
143
+ ]
144
+ },
145
+ {
146
+ "disabled":false,
147
+ "hidden":false,
148
+ "key":"4194cc93-99ff-fb2c-ec1f-ad4e394e6731",
149
+ "type":"TextField",
150
+ "data_name":"name_of_park",
151
+ "required":false,
152
+ "label":"Name of Park"
153
+ },
154
+ {
155
+ "disabled":false,
156
+ "hidden":false,
157
+ "key":"21f0b6d5-4154-030e-d552-13819dfb3a0a",
158
+ "type":"PhotoField",
159
+ "required":false,
160
+ "label":"Photos"
161
+ }
162
+ ]
163
+ },
164
+ {
165
+ "disabled":false,
166
+ "hidden":false,
167
+ "key":"1797370a-06c3-a263-6c8f-17dcf7186b11",
168
+ "type":"DateTimeField",
169
+ "data_name":"date_of_report",
170
+ "required":false,
171
+ "label":"Date of Report"
172
+ }
173
+ ]
174
+ }
175
+ }