amee 2.0.31 → 2.0.32
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/lib/amee/data_item_value.rb +77 -27
- data/lib/amee/data_item_value_history.rb +167 -0
- data/lib/amee/data_object.rb +3 -2
- data/lib/amee/exceptions.rb +3 -1
- data/lib/amee/object.rb +1 -1
- data/lib/amee.rb +2 -1
- metadata +14 -3
data/lib/amee/data_item_value.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
module AMEE
|
|
2
|
+
Epoch=DateTime.parse(Time.at(0).xmlschema).utc
|
|
2
3
|
module Data
|
|
3
4
|
class ItemValue < AMEE::Data::Object
|
|
4
5
|
|
|
@@ -34,17 +35,29 @@ module AMEE
|
|
|
34
35
|
@from_data
|
|
35
36
|
end
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
attr_accessor :start_date
|
|
39
|
+
attr_accessor :uid
|
|
40
|
+
|
|
41
|
+
def uid_path
|
|
42
|
+
# create a path which is safe for DIVHs by using the UID if one is avai
|
|
43
|
+
if uid
|
|
44
|
+
@path.split(/\//)[0..-2].push(uid).join('/')
|
|
45
|
+
else
|
|
46
|
+
@path
|
|
47
|
+
end
|
|
39
48
|
end
|
|
40
|
-
|
|
49
|
+
|
|
50
|
+
def full_uid_path
|
|
51
|
+
"/data#{uid_path}"
|
|
52
|
+
end
|
|
53
|
+
|
|
41
54
|
def self.from_json(json, path)
|
|
42
55
|
# Read JSON
|
|
43
|
-
doc = JSON.parse(json)['itemValue']
|
|
56
|
+
doc = json.is_a?(String) ? JSON.parse(json)['itemValue'] : json
|
|
44
57
|
data = {}
|
|
45
58
|
data[:uid] = doc['uid']
|
|
46
|
-
data[:created] = DateTime.parse(doc['created'])
|
|
47
|
-
data[:modified] = DateTime.parse(doc['modified'])
|
|
59
|
+
data[:created] = DateTime.parse(doc['created']) rescue nil
|
|
60
|
+
data[:modified] = DateTime.parse(doc['modified']) rescue nil
|
|
48
61
|
data[:name] = doc['name']
|
|
49
62
|
data[:path] = path.gsub(/^\/data/, '')
|
|
50
63
|
data[:value] = doc['value']
|
|
@@ -58,22 +71,27 @@ module AMEE
|
|
|
58
71
|
|
|
59
72
|
def self.from_xml(xml, path)
|
|
60
73
|
# Read XML
|
|
61
|
-
doc = REXML::Document.new(xml)
|
|
74
|
+
doc = xml.is_a?(String) ? REXML::Document.new(xml) : xml
|
|
62
75
|
data = {}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
if REXML::XPath.match(doc,"descendant-or-self::ItemValue").length>1
|
|
77
|
+
raise AMEE::BadData.new("Couldn't load DataItemValue from XML. This is an item value history.\n#{xml}")
|
|
78
|
+
end
|
|
79
|
+
begin
|
|
80
|
+
data[:uid] = REXML::XPath.first(doc, "descendant-or-self::ItemValue/@uid").to_s
|
|
81
|
+
data[:created] = DateTime.parse(REXML::XPath.first(doc, "descendant-or-self::ItemValue/@Created").to_s) rescue nil
|
|
82
|
+
data[:modified] = DateTime.parse(REXML::XPath.first(doc, "descendant-or-self::ItemValue/@Modified").to_s) rescue nil
|
|
83
|
+
data[:name] = REXML::XPath.first(doc, 'descendant-or-self::ItemValue/Name').text
|
|
84
|
+
data[:path] = path.gsub(/^\/data/, '')
|
|
85
|
+
data[:value] = REXML::XPath.first(doc, 'descendant-or-self::ItemValue/Value').text
|
|
86
|
+
data[:type] = REXML::XPath.first(doc, 'descendant-or-self::ItemValue/ItemValueDefinition/ValueDefinition/ValueType').text
|
|
87
|
+
data[:from_profile] = false
|
|
88
|
+
data[:from_data] = true
|
|
89
|
+
data[:start_date] = DateTime.parse(REXML::XPath.first(doc, "descendant-or-self::ItemValue/StartDate").text) rescue nil
|
|
90
|
+
# Create object
|
|
91
|
+
ItemValue.new(data)
|
|
92
|
+
rescue
|
|
93
|
+
raise AMEE::BadData.new("Couldn't load DataItemValue from XML. Check that your URL is correct.\n#{xml}")
|
|
94
|
+
end
|
|
77
95
|
end
|
|
78
96
|
|
|
79
97
|
def self.get(connection, path)
|
|
@@ -89,7 +107,32 @@ module AMEE
|
|
|
89
107
|
end
|
|
90
108
|
|
|
91
109
|
def save!
|
|
92
|
-
|
|
110
|
+
if start_date
|
|
111
|
+
ItemValue.update(connection,full_uid_path,:value=>value,
|
|
112
|
+
:start_date=>start_date,:get_item=>false)
|
|
113
|
+
else
|
|
114
|
+
ItemValue.update(connection,full_uid_path,:value=>value,:get_item=>false)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def delete!
|
|
119
|
+
raise AMEE::BadData.new("Cannot delete initial value for time series") if start_date==AMEE::Epoch
|
|
120
|
+
ItemValue.delete @connection,full_uid_path
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def create!
|
|
124
|
+
data_item_path=path.split(/\//)[0..-2].join('/')
|
|
125
|
+
data_item=AMEE::Data::Item.new
|
|
126
|
+
data_item.path=data_item_path
|
|
127
|
+
data_item.connection=connection
|
|
128
|
+
data_item.connection or raise "No connection to AMEE available"
|
|
129
|
+
if start_date
|
|
130
|
+
ItemValue.create(data_item,:start_date=>start_date,
|
|
131
|
+
@path.split(/\//).pop.to_sym => value,:get_item=>false)
|
|
132
|
+
else
|
|
133
|
+
ItemValue.create(data_item,
|
|
134
|
+
@path.split(/\//).pop.to_sym => value,:get_item=>false)
|
|
135
|
+
end
|
|
93
136
|
end
|
|
94
137
|
|
|
95
138
|
def self.parse(connection, response, path)
|
|
@@ -107,6 +150,7 @@ module AMEE
|
|
|
107
150
|
end
|
|
108
151
|
|
|
109
152
|
def self.create(data_item, options = {})
|
|
153
|
+
|
|
110
154
|
# Do we want to automatically fetch the item afterwards?
|
|
111
155
|
get_item = options.delete(:get_item)
|
|
112
156
|
get_item = true if get_item.nil?
|
|
@@ -115,15 +159,15 @@ module AMEE
|
|
|
115
159
|
unless options.is_a?(Hash)
|
|
116
160
|
raise AMEE::ArgumentError.new("Third argument must be a hash of options!")
|
|
117
161
|
end
|
|
162
|
+
|
|
118
163
|
# Set startDate
|
|
119
164
|
if (options[:start_date])
|
|
120
165
|
options[:startDate] = options[:start_date].xmlschema
|
|
121
166
|
options.delete(:start_date)
|
|
122
167
|
end
|
|
123
|
-
|
|
168
|
+
|
|
124
169
|
response = data_item.connection.post(data_item.full_path, options)
|
|
125
170
|
location = response['Location'].match("http://.*?(/.*)")[1]
|
|
126
|
-
|
|
127
171
|
if get_item == true
|
|
128
172
|
get_options = {}
|
|
129
173
|
get_options[:format] = format if format
|
|
@@ -139,6 +183,11 @@ module AMEE
|
|
|
139
183
|
# Do we want to automatically fetch the item afterwards?
|
|
140
184
|
get_item = options.delete(:get_item)
|
|
141
185
|
get_item = true if get_item.nil?
|
|
186
|
+
# Set startDate
|
|
187
|
+
if (options[:start_date])
|
|
188
|
+
options[:startDate] = options[:start_date].xmlschema if options[:start_date]!=Epoch
|
|
189
|
+
options.delete(:start_date)
|
|
190
|
+
end
|
|
142
191
|
# Go
|
|
143
192
|
response = connection.put(path, options)
|
|
144
193
|
if get_item
|
|
@@ -155,11 +204,12 @@ module AMEE
|
|
|
155
204
|
def update(options = {})
|
|
156
205
|
AMEE::Data::ItemValue.update(connection, full_path, options)
|
|
157
206
|
end
|
|
158
|
-
|
|
207
|
+
|
|
208
|
+
|
|
159
209
|
def self.delete(connection, path)
|
|
160
210
|
connection.delete(path)
|
|
161
|
-
|
|
162
|
-
|
|
211
|
+
rescue
|
|
212
|
+
raise AMEE::BadData.new("Couldn't delete DataItemValue. Check that your information is correct.")
|
|
163
213
|
end
|
|
164
214
|
|
|
165
215
|
end
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
|
|
3
|
+
module AMEE
|
|
4
|
+
module Data
|
|
5
|
+
class ItemValueHistory
|
|
6
|
+
|
|
7
|
+
def initialize(data = {})
|
|
8
|
+
@type = data ? data[:type] : nil
|
|
9
|
+
@path = data ? data[:path] : nil
|
|
10
|
+
@connection = data ? data[:connection] : nil
|
|
11
|
+
@values=data&&data[:values] ? data[:values] : []
|
|
12
|
+
self.series=data[:series] if data[:series]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
attr_accessor :values
|
|
16
|
+
attr_accessor :path # the IV path corresponding to the series, without the /data
|
|
17
|
+
|
|
18
|
+
def full_path
|
|
19
|
+
"/data#{path}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def series
|
|
23
|
+
values.map {|x|
|
|
24
|
+
[x.start_date.utc,x.value]
|
|
25
|
+
}.sort {|x,y|
|
|
26
|
+
x[0]<=>y[0]
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def times
|
|
31
|
+
values.map {|x|
|
|
32
|
+
x.start_date.utc
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def series=(newseries)
|
|
37
|
+
raise AMEE::BadData.new("Series must have initial (Epoch) value") unless newseries.any?{|x| x[0]==Epoch}
|
|
38
|
+
@values=newseries.map{|x|
|
|
39
|
+
AMEE::Data::ItemValue.new(:value=>x[1],
|
|
40
|
+
:start_date=>x[0],
|
|
41
|
+
:path=>path,
|
|
42
|
+
:connection=>connection,
|
|
43
|
+
:type=>type
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def value_at(time)
|
|
49
|
+
selected=values.select{|x| x.start_date==time}
|
|
50
|
+
raise AMEE::BadData.new("Multiple data item values matching one time.") if selected.length >1
|
|
51
|
+
raise AMEE::BadData.new("No data item value for that time #{time}.") if selected.length ==0
|
|
52
|
+
selected[0]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def values_at(times)
|
|
56
|
+
times.map{|x| value_at(x)}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def connection=(con)
|
|
60
|
+
@connection=con
|
|
61
|
+
@values.each{|x| x.connection=con}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
attr_reader :connection
|
|
65
|
+
attr_reader :type
|
|
66
|
+
|
|
67
|
+
def self.from_json(json, path)
|
|
68
|
+
# Read JSON
|
|
69
|
+
data = {}
|
|
70
|
+
data[:path] = path.gsub(/^\/data/, '')
|
|
71
|
+
doc = JSON.parse(json)['itemValues']
|
|
72
|
+
doc=[JSON.parse(json)['itemValue']] unless doc
|
|
73
|
+
data[:values]=doc.map do |json_item_value|
|
|
74
|
+
ItemValue.from_json(json_item_value,path)
|
|
75
|
+
end
|
|
76
|
+
data[:type]=data[:values][0].type
|
|
77
|
+
# Create object
|
|
78
|
+
ItemValueHistory.new(data)
|
|
79
|
+
rescue
|
|
80
|
+
raise AMEE::BadData.new("Couldn't load DataItemValueHistory from JSON. Check that your URL is correct.\n#{json}")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def self.from_xml(xml, path)
|
|
84
|
+
# Read XML
|
|
85
|
+
data = {}
|
|
86
|
+
data[:path] = path.gsub(/^\/data/, '')
|
|
87
|
+
doc = REXML::Document.new(xml)
|
|
88
|
+
valuedocs=REXML::XPath.match(doc, '//ItemValue')
|
|
89
|
+
raise if valuedocs.length==0
|
|
90
|
+
data[:values] = valuedocs.map do |xml_item_value|
|
|
91
|
+
ItemValue.from_xml(xml_item_value,path)
|
|
92
|
+
end
|
|
93
|
+
data[:type]=data[:values][0].type
|
|
94
|
+
# Create object
|
|
95
|
+
ItemValueHistory.new(data)
|
|
96
|
+
rescue
|
|
97
|
+
raise AMEE::BadData.new("Couldn't load DataItemValueHistory from XML. Check that your URL is correct.\n#{xml}")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def self.get(connection, path)
|
|
101
|
+
# Load data from path
|
|
102
|
+
response = connection.get(path,:valuesPerPage=>2).body
|
|
103
|
+
# Parse data from response
|
|
104
|
+
data = {}
|
|
105
|
+
value = ItemValueHistory.parse(connection, response, path)
|
|
106
|
+
# Done
|
|
107
|
+
return value
|
|
108
|
+
rescue
|
|
109
|
+
raise AMEE::BadData.new("Couldn't load DataItemValueHistory. Check that your URL is correct.")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def save!
|
|
113
|
+
raise AMEE::BadData.new("Can't save without a path") unless @path
|
|
114
|
+
raise AMEE::BadData.new("Can't save without a connection") unless @connection
|
|
115
|
+
origin=ItemValueHistory.get(connection,full_path)
|
|
116
|
+
changes=compare(origin)
|
|
117
|
+
changes[:updates].each do |update|
|
|
118
|
+
# we've decided to identify these, but the version in the thing to be
|
|
119
|
+
# saved is probably home made, so copy over the uid
|
|
120
|
+
update.uid=origin.value_at(update.start_date).uid
|
|
121
|
+
update.save!
|
|
122
|
+
end
|
|
123
|
+
changes[:insertions].each do |insertion|
|
|
124
|
+
insertion.create!
|
|
125
|
+
end
|
|
126
|
+
changes[:deletions].each do |deletion|
|
|
127
|
+
deletion.delete!
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def delete!
|
|
132
|
+
# deprecated, as DI cannot exist without at least one point
|
|
133
|
+
raise AMEE::NotSupported.new("Cannot delete all of history: at least one data point must always exist.")
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def create!
|
|
137
|
+
# deprecated, as DI cannot exist without at least one point
|
|
138
|
+
raise AMEE::NotSupported.new("Cannot create a Data Item Value History from scratch: at least one data point must exist when the DI is created")
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def self.parse(connection, response, path)
|
|
142
|
+
if response.is_json?
|
|
143
|
+
history = ItemValueHistory.from_json(response, path)
|
|
144
|
+
else
|
|
145
|
+
history = ItemValueHistory.from_xml(response, path)
|
|
146
|
+
end
|
|
147
|
+
# Store connection in object for future use
|
|
148
|
+
history.connection = connection
|
|
149
|
+
# Done
|
|
150
|
+
return history
|
|
151
|
+
rescue
|
|
152
|
+
raise AMEE::BadData.new("Couldn't load DataItemValueHistory. Check that your URL is correct.\n#{response}")
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def compare(origin)
|
|
156
|
+
new=Set.new(times)
|
|
157
|
+
old=Set.new(origin.times)
|
|
158
|
+
{
|
|
159
|
+
:insertions=>values_at(new-old),
|
|
160
|
+
:deletions=>origin.values_at(old-new),
|
|
161
|
+
:updates=>values_at(old&new)
|
|
162
|
+
}
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
data/lib/amee/data_object.rb
CHANGED
data/lib/amee/exceptions.rb
CHANGED
data/lib/amee/object.rb
CHANGED
data/lib/amee.rb
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
require 'rexml/document'
|
|
2
|
-
require '
|
|
2
|
+
require 'active_support'
|
|
3
3
|
|
|
4
4
|
# We don't NEED the JSON gem, but if it's available, use it.
|
|
5
5
|
begin
|
|
@@ -35,6 +35,7 @@ require 'amee/profile_object'
|
|
|
35
35
|
require 'amee/data_category'
|
|
36
36
|
require 'amee/data_item'
|
|
37
37
|
require 'amee/data_item_value'
|
|
38
|
+
require 'amee/data_item_value_history'
|
|
38
39
|
require 'amee/profile'
|
|
39
40
|
require 'amee/profile_category'
|
|
40
41
|
require 'amee/profile_item'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: amee
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.
|
|
4
|
+
version: 2.0.32
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- James Smith
|
|
@@ -9,13 +9,23 @@ autorequire:
|
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
11
|
|
|
12
|
-
date:
|
|
12
|
+
date: 2010-02-26 00:00:00 +00:00
|
|
13
13
|
default_executable:
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: activesupport
|
|
17
17
|
type: :runtime
|
|
18
18
|
version_requirement:
|
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
20
|
+
requirements:
|
|
21
|
+
- - ~>
|
|
22
|
+
- !ruby/object:Gem::Version
|
|
23
|
+
version: 2.3.5
|
|
24
|
+
version:
|
|
25
|
+
- !ruby/object:Gem::Dependency
|
|
26
|
+
name: json
|
|
27
|
+
type: :runtime
|
|
28
|
+
version_requirement:
|
|
19
29
|
version_requirements: !ruby/object:Gem::Requirement
|
|
20
30
|
requirements:
|
|
21
31
|
- - ">="
|
|
@@ -23,7 +33,7 @@ dependencies:
|
|
|
23
33
|
version: "0"
|
|
24
34
|
version:
|
|
25
35
|
- !ruby/object:Gem::Dependency
|
|
26
|
-
name:
|
|
36
|
+
name: rspec_spinner
|
|
27
37
|
type: :runtime
|
|
28
38
|
version_requirement:
|
|
29
39
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -55,6 +65,7 @@ files:
|
|
|
55
65
|
- lib/amee/version.rb
|
|
56
66
|
- lib/amee/data_category.rb
|
|
57
67
|
- lib/amee/data_item_value.rb
|
|
68
|
+
- lib/amee/data_item_value_history.rb
|
|
58
69
|
- lib/amee/data_object.rb
|
|
59
70
|
- lib/amee/object.rb
|
|
60
71
|
- lib/amee/shell.rb
|