amee 2.0.31 → 2.0.32

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- def start_date
38
- @start_date
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
- data[:uid] = REXML::XPath.first(doc, "/Resources/DataItemValueResource/ItemValue/@uid").to_s
64
- data[:created] = DateTime.parse(REXML::XPath.first(doc, "/Resources/DataItemValueResource/ItemValue/@Created").to_s)
65
- data[:modified] = DateTime.parse(REXML::XPath.first(doc, "/Resources/DataItemValueResource/ItemValue/@Modified").to_s)
66
- data[:name] = REXML::XPath.first(doc, '/Resources/DataItemValueResource/ItemValue/Name').text
67
- data[:path] = path.gsub(/^\/data/, '')
68
- data[:value] = REXML::XPath.first(doc, '/Resources/DataItemValueResource/ItemValue/Value').text
69
- data[:type] = REXML::XPath.first(doc, '/Resources/DataItemValueResource/ItemValue/ItemValueDefinition/ValueDefinition/ValueType').text
70
- data[:from_profile] = REXML::XPath.first(doc, '/Resources/DataItemValueResource/ItemValue/ItemValueDefinition/FromProfile').text == "true" ? true : false
71
- data[:from_data] = REXML::XPath.first(doc, '/Resources/DataItemValueResource/ItemValue/ItemValueDefinition/FromData').text == "true" ? true : false
72
- data[:start_date] = DateTime.parse(REXML::XPath.first(doc, "/Resources/DataItemValueResource/ItemValue/StartDate").text) rescue nil
73
- # Create object
74
- ItemValue.new(data)
75
- rescue
76
- raise AMEE::BadData.new("Couldn't load DataItemValue from XML. Check that your URL is correct.\n#{xml}")
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
- response = @connection.put(full_path, :value => value).body
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
- rescue
162
- raise AMEE::BadData.new("Couldn't delete DataItemValue. Check that your information is correct.")
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
@@ -1,9 +1,10 @@
1
1
  module AMEE
2
2
  module Data
3
3
  class Object < AMEE::Object
4
-
4
+
5
+ attr_accessor :path
5
6
  def full_path
6
- "/data#{@path}"
7
+ "/data#{path}"
7
8
  end
8
9
 
9
10
  end
@@ -23,5 +23,7 @@ module AMEE
23
23
 
24
24
  class UnknownError < Exception
25
25
  end
26
-
26
+
27
+ class NotSupported < Exception
28
+ end
27
29
  end
data/lib/amee/object.rb CHANGED
@@ -7,7 +7,7 @@ module AMEE
7
7
  @modified = data ? data[:modified] : @created
8
8
  @path = data ? data[:path] : nil
9
9
  @name = data ? data[:name] : nil
10
- @connection = nil
10
+ @connection = data ? data[:connection] : nil
11
11
  end
12
12
 
13
13
  attr_accessor :connection
data/lib/amee.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  require 'rexml/document'
2
- require 'activesupport'
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.31
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: 2009-12-11 00:00:00 +00:00
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: json
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