Class: Cosmos::MetadataModel
- Defined in:
- lib/cosmos/models/metadata_model.rb
Constant Summary collapse
- CHRONICLE_TYPE =
'metadata'.freeze
- CURRENT_VALUE =
'__current.metadata.value'.freeze
- PRIMARY_KEY =
'__METADATA'.freeze
Instance Attribute Summary collapse
-
#color ⇒ Object
readonly
Returns the value of attribute color.
-
#metadata ⇒ Object
readonly
Returns the value of attribute metadata.
-
#start ⇒ Object
readonly
Returns the value of attribute start.
-
#target ⇒ Object
readonly
Returns the value of attribute target.
-
#type ⇒ Object
readonly
Returns the value of attribute type.
Attributes inherited from Model
#name, #plugin, #scope, #updated_at
Class Method Summary collapse
-
.all(scope:, limit: 100) ⇒ Array<Hash>
Array up to the limit of the models (as Hash objects) stored under the primary key.
-
.count(scope:) ⇒ Integer
Count of the members stored under the primary key.
-
.destroy(scope:, score:) ⇒ Integer
Remove member from a sorted set based on the score.
-
.from_json(json, scope:) ⇒ MetadataModel
Model generated from the passed JSON.
-
.get(start:, stop:, scope:, limit: 100) ⇒ Array|nil
Array up to 100 of this model or empty array.
-
.get_current_value(target:, scope:) ⇒ String|nil
String of the saved json or nil if score not found under current value.
- .pk(scope) ⇒ Object
-
.range_destroy(scope:, min:, max:) ⇒ Integer
Remove members from min to max of the sorted set.
-
.score(score:, scope:) ⇒ String|nil
String of the saved json or nil if score not found under primary_key.
Instance Method Summary collapse
-
#as_json ⇒ Hash
Generated from the MetadataModel.
-
#create ⇒ Object
Update the Redis hash at primary_key and set the score equal to the start Epoch time the member is set to the JSON generated via calling as_json.
-
#destroy ⇒ Object
destroy the activity from the redis database.
-
#initialize(target:, start:, color: nil, metadata:, scope:, type: CHRONICLE_TYPE, updated_at: 0) ⇒ MetadataModel
constructor
A new instance of MetadataModel.
-
#notify(kind:, extra: nil) ⇒ Object
-
update the redis stream / timeline topic that something has changed.
-
-
#set_input(start:, color:, metadata:) ⇒ Object
Set the values of the instance, @start, @stop, @metadata…
-
#to_s ⇒ String
String view of metadata.
-
#update(start:, color:, metadata:) ⇒ Object
Update the Redis hash at primary_key and remove the current activity at the current score and update the score to the new score equal to the start Epoch time this uses a multi to execute both the remove and create.
-
#update_current_value(old_start: nil) ⇒ Object
Update the Redis hash at primary_key and check if this metadata instance is newer than the current instance stored in the hash.
-
#validate_color(color) ⇒ Object
validate color.
-
#validate_input(start:, color:, metadata:) ⇒ Object
validate the input to the rules we have created for timelines.
-
#validate_time(ignore_score: nil) ⇒ Object
validate_time will be called on create and update this will validate that no other chronicle event or metadata had been saved for that time.
Methods inherited from Model
#as_config, #deploy, filter, find_all_by_plugin, get_all_models, get_model, handle_config, names, set, #undeploy
Constructor Details
#initialize(target:, start:, color: nil, metadata:, scope:, type: CHRONICLE_TYPE, updated_at: 0) ⇒ MetadataModel
Returns a new instance of MetadataModel.
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/cosmos/models/metadata_model.rb', line 119 def initialize( target:, start:, color: nil, metadata:, scope:, type: CHRONICLE_TYPE, updated_at: 0 ) super(MetadataModel.pk(scope), name: start.to_s, scope: scope) set_input(start: start, color: color, metadata: ) @target = target @type = type @updated_at = updated_at end |
Instance Attribute Details
#color ⇒ Object (readonly)
Returns the value of attribute color.
112 113 114 |
# File 'lib/cosmos/models/metadata_model.rb', line 112 def color @color end |
#metadata ⇒ Object (readonly)
Returns the value of attribute metadata.
112 113 114 |
# File 'lib/cosmos/models/metadata_model.rb', line 112 def @metadata end |
#start ⇒ Object (readonly)
Returns the value of attribute start.
112 113 114 |
# File 'lib/cosmos/models/metadata_model.rb', line 112 def start @start end |
#target ⇒ Object (readonly)
Returns the value of attribute target.
112 113 114 |
# File 'lib/cosmos/models/metadata_model.rb', line 112 def target @target end |
#type ⇒ Object (readonly)
Returns the value of attribute type.
112 113 114 |
# File 'lib/cosmos/models/metadata_model.rb', line 112 def type @type end |
Class Method Details
.all(scope:, limit: 100) ⇒ Array<Hash>
Returns Array up to the limit of the models (as Hash objects) stored under the primary key.
64 65 66 67 68 69 70 71 72 |
# File 'lib/cosmos/models/metadata_model.rb', line 64 def self.all(scope:, limit: 100) pk = self.pk(scope) array = Store.zrange(pk, 0, -1, :limit => [0, limit]) ret_array = Array.new array.each do |value| ret_array << JSON.parse(value) end return ret_array end |
.count(scope:) ⇒ Integer
Returns count of the members stored under the primary key.
75 76 77 |
# File 'lib/cosmos/models/metadata_model.rb', line 75 def self.count(scope:) return Store.zcard(self.pk(scope)) end |
.destroy(scope:, score:) ⇒ Integer
Remove member from a sorted set based on the score.
91 92 93 94 |
# File 'lib/cosmos/models/metadata_model.rb', line 91 def self.destroy(scope:, score:) pk = self.pk(scope) Store.zremrangebyscore(pk, score, score) end |
.from_json(json, scope:) ⇒ MetadataModel
Returns Model generated from the passed JSON.
104 105 106 107 108 109 110 |
# File 'lib/cosmos/models/metadata_model.rb', line 104 def self.from_json(json, scope:) json = JSON.parse(json) if String === json raise "json data is nil" if json.nil? json.transform_keys!(&:to_sym) self.new(**json, scope: scope) end |
.get(start:, stop:, scope:, limit: 100) ⇒ Array|nil
Returns Array up to 100 of this model or empty array.
50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/cosmos/models/metadata_model.rb', line 50 def self.get(start:, stop:, scope:, limit: 100) if start > stop raise MetadataInputError.new "start: #{start} must be before stop: #{stop}" end pk = self.pk(scope) array = Store.zrangebyscore(pk, start, stop, :limit => [0, limit]) ret_array = Array.new array.each do |value| ret_array << JSON.parse(value) end return ret_array end |
.get_current_value(target:, scope:) ⇒ String|nil
Returns String of the saved json or nil if score not found under current value.
43 44 45 46 47 |
# File 'lib/cosmos/models/metadata_model.rb', line 43 def self.get_current_value(target:, scope:) json = Store.hget("#{scope}#{CURRENT_VALUE}", target) return nil unless json return self.from_json(JSON.parse(json), scope: scope) end |
.pk(scope) ⇒ Object
38 39 40 |
# File 'lib/cosmos/models/metadata_model.rb', line 38 def self.pk(scope) return "#{scope}#{PRIMARY_KEY}" end |
.range_destroy(scope:, min:, max:) ⇒ Integer
Remove members from min to max of the sorted set.
98 99 100 101 |
# File 'lib/cosmos/models/metadata_model.rb', line 98 def self.range_destroy(scope:, min:, max:) pk = self.pk(scope) Store.zremrangebyscore(pk, min, max) end |
.score(score:, scope:) ⇒ String|nil
Returns String of the saved json or nil if score not found under primary_key.
80 81 82 83 84 85 86 87 |
# File 'lib/cosmos/models/metadata_model.rb', line 80 def self.score(score:, scope:) pk = self.pk(scope) array = Store.zrangebyscore(pk, score, score, :limit => [0, 1]) array.each do |value| return JSON.parse(value) end return nil end |
Instance Method Details
#as_json ⇒ Hash
Returns generated from the MetadataModel.
272 273 274 275 276 277 278 279 280 281 282 |
# File 'lib/cosmos/models/metadata_model.rb', line 272 def as_json return { 'target' => @target, 'scope' => @scope, 'updated_at' => @updated_at, 'start' => @start, 'color' => @color, 'metadata' => @metadata, 'type' => CHRONICLE_TYPE, } end |
#create ⇒ Object
Update the Redis hash at primary_key and set the score equal to the start Epoch time the member is set to the JSON generated via calling as_json
198 199 200 201 202 203 204 205 206 207 208 209 |
# File 'lib/cosmos/models/metadata_model.rb', line 198 def create validate_input(start: @start, color: @color, metadata: @metadata) collision = validate_time() unless collision.nil? raise MetadataOverlapError.new "no chronicle can overlap, collision: #{collision}" end @updated_at = Time.now.to_nsec_from_epoch Store.zadd(@primary_key, @start, JSON.generate(as_json())) update_current_value() notify(kind: 'created') end |
#destroy ⇒ Object
destroy the activity from the redis database
251 252 253 254 |
# File 'lib/cosmos/models/metadata_model.rb', line 251 def destroy Store.zremrangebyscore(@primary_key, @start, @start) notify(kind: 'deleted') end |
#notify(kind:, extra: nil) ⇒ Object
Returns [] update the redis stream / timeline topic that something has changed.
257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/cosmos/models/metadata_model.rb', line 257 def notify(kind:, extra: nil) notification = { 'data' => JSON.generate(as_json()), 'kind' => kind, 'type' => 'calendar', } notification['extra'] = extra unless extra.nil? begin CalendarTopic.write_entry(notification, scope: @scope) rescue StandardError => e raise MetadataError.new "Failed to write to stream: #{notification}, #{e}" end end |
#set_input(start:, color:, metadata:) ⇒ Object
Set the values of the instance, @start, @stop, @metadata…
168 169 170 171 172 173 174 175 |
# File 'lib/cosmos/models/metadata_model.rb', line 168 def set_input(start:, color:, metadata:) if start.is_a?(Integer) == false raise MetadataInputError.new "start input must be integer: #{start}" end @start = start @color = color @metadata = end |
#to_s ⇒ String
Returns string view of metadata.
285 286 287 |
# File 'lib/cosmos/models/metadata_model.rb', line 285 def to_s return "<MetadataModel t: #{@target}, s: #{@start}, c: #{@color}, m: #{@metadata}>" end |
#update(start:, color:, metadata:) ⇒ Object
Update the Redis hash at primary_key and remove the current activity at the current score and update the score to the new score equal to the start Epoch time this uses a multi to execute both the remove and create. The member via the JSON generated via calling as_json
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/cosmos/models/metadata_model.rb', line 214 def update(start:, color:, metadata:) validate_input(start: start, color: color, metadata: ) old_start = @start @updated_at = Time.now.to_nsec_from_epoch set_input(start: start, color: color, metadata: ) # copy of create collision = validate_time(ignore_score: old_start) unless collision.nil? raise MetadataOverlapError.new "failed to update #{old_start}, no chronicles can overlap, collision: #{collision}" end Store.multi do |multi| multi.zremrangebyscore(@primary_key, old_start, old_start) multi.zadd(@primary_key, @start, JSON.generate(as_json())) end update_current_value(old_start: old_start) notify(kind: 'updated', extra: old_start) return @start end |
#update_current_value(old_start: nil) ⇒ Object
Update the Redis hash at primary_key and check if this metadata instance is newer than the current instance stored in the hash. If the hash does NOT contain an instance or this metadata instance is newer it will update the current hash.
238 239 240 241 242 243 244 245 246 247 248 |
# File 'lib/cosmos/models/metadata_model.rb', line 238 def update_current_value(old_start: nil) update = true json = Store.hget("#{@scope}#{CURRENT_VALUE}", @target) unless json.nil? model = MetadataModel.from_json(JSON.parse(json), scope: @scope) update = model.start <= @start || model.start == old_start end if update return Store.hset("#{@scope}#{CURRENT_VALUE}", @target, JSON.generate(as_json())) end end |
#validate_color(color) ⇒ Object
validate color
136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/cosmos/models/metadata_model.rb', line 136 def validate_color(color) if color.nil? color = '#%06x' % (rand * 0xffffff) end valid_color = color =~ /(#*)([0-9,a-f,A-f]{6})/ if valid_color.nil? raise MetadataInputError.new "invalid color, must be in hex format, e.g. #FF0000" end color = "##{color}" unless color.start_with?('#') return color end |
#validate_input(start:, color:, metadata:) ⇒ Object
validate the input to the rules we have created for timelines.
-
An entry’s start MUST be valid.
-
An entry’s start MUST NOT be in the future.
-
An entry’s metadata MUST a hash/object.
153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/cosmos/models/metadata_model.rb', line 153 def validate_input(start:, color:, metadata:) if start.is_a?(Integer) == false raise MetadataInputError.new "failed validation input must be integer: #{start}" end now = Time.now.strftime('%s%3N').to_i if start > now raise MetadataInputError.new "start can not be in the future: #{start} > #{now}" end validate_color(color) if .is_a?(Hash) == false raise MetadataInputError.new "Metadata must be a hash/object: #{}" end end |
#validate_time(ignore_score: nil) ⇒ Object
validate_time will be called on create and update this will validate that no other chronicle event or metadata had been saved for that time. One event or metadata per second to ensure data can be updated.
183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/cosmos/models/metadata_model.rb', line 183 def validate_time(ignore_score: nil) array = Store.zrangebyscore(@primary_key, @start, @start, :limit => [0, 1]) array.each do |value| entry = JSON.parse(value) if ignore_score == entry['start'] next else return entry end end return nil end |