ama_layout 5.2.0 → 5.4.0
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.
- checksums.yaml +4 -4
- data/.travis.yml +2 -0
- data/ama_layout.gemspec +21 -19
- data/lib/ama_layout.rb +20 -14
- data/lib/ama_layout/notification.rb +84 -0
- data/lib/ama_layout/notification_set.rb +130 -0
- data/lib/ama_layout/notifications.rb +73 -0
- data/lib/ama_layout/notifications/abstract_store.rb +17 -0
- data/lib/ama_layout/notifications/redis_store.rb +38 -0
- data/lib/ama_layout/version.rb +1 -1
- data/spec/ama_layout/notification_set_spec.rb +237 -0
- data/spec/ama_layout/notification_spec.rb +193 -0
- data/spec/ama_layout/notifications/abstract_store_spec.rb +23 -0
- data/spec/ama_layout/notifications/redis_store_spec.rb +94 -0
- data/spec/ama_layout/notifications_spec.rb +109 -0
- data/spec/spec_helper.rb +9 -8
- metadata +49 -7
- data/styles.scss +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3c84576ab846039685676fea5a445c6bde97a418
|
4
|
+
data.tar.gz: 898f53f578677836a9f708c44117142217b49d3e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1d15a754ed97115d0312f123885c591bb28498f6c7f17a99b00e5316256026ec9e2e0a25ca8a468a0fea441b26a8ebc0a622a6bd4b8c3e66f4df480254fa4fb2
|
7
|
+
data.tar.gz: 0fc6b11bd21ee5739d94dc2e3188bf9030302bdcb5eff52c4ffbf364e90045da1ec01d7bb9ab252deac6ffdb418203c543814c149ed51173cd1948091acfa505
|
data/.travis.yml
CHANGED
data/ama_layout.gemspec
CHANGED
@@ -4,31 +4,33 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
require 'ama_layout/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
7
|
+
spec.name = 'ama_layout'
|
8
8
|
spec.version = AmaLayout::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
9
|
+
spec.authors = ['Michael van den Beuken', 'Ruben Estevez', 'Jordan Babe', 'Mathieu Gilbert', 'Ryan Jones', 'Darko Dosenovic', 'Jonathan Weyermann', 'Adam Melnyk', 'Kayt Campbell', 'Kathleen Robertson', 'Jesse Doyle']
|
10
|
+
spec.email = ['michael.beuken@gmail.com', 'ruben.a.estevez@gmail.com', 'jorbabe@gmail.com', 'mathieu.gilbert@ama.ab.ca', 'ryan.michael.jones@gmail.com', 'darko.dosenovic@ama.ab.ca', 'jonathan.weyermann@ama.ab.ca', 'adam.melnyk@ama.ab.ca', 'kayt.campbell@ama.ab.ca', 'kathleen.robertson@ama.ab.ca', 'jesse.doyle@ama.ab.ca']
|
11
11
|
spec.summary = %q{.ama.ab.ca site layouts}
|
12
12
|
spec.description = %q{.ama.ab.ca site layouts}
|
13
|
-
spec.homepage =
|
14
|
-
spec.license =
|
13
|
+
spec.homepage = 'https://github.com/amaabca/ama_layout'
|
14
|
+
spec.license = 'MIT'
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0")
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
-
spec.require_paths = [
|
19
|
+
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.add_dependency
|
22
|
-
spec.add_dependency
|
23
|
-
spec.add_dependency
|
24
|
-
spec.add_dependency
|
25
|
-
spec.add_dependency
|
26
|
-
spec.
|
27
|
-
spec.add_development_dependency
|
28
|
-
spec.add_development_dependency
|
29
|
-
spec.add_development_dependency
|
30
|
-
spec.add_development_dependency
|
31
|
-
spec.add_development_dependency
|
32
|
-
spec.add_development_dependency
|
33
|
-
spec.add_development_dependency
|
21
|
+
spec.add_dependency 'foundation-rails', '~> 6.3.1.0'
|
22
|
+
spec.add_dependency 'rails', '~> 4.2'
|
23
|
+
spec.add_dependency 'draper', '~> 2.1'
|
24
|
+
spec.add_dependency 'browser', '~> 2.0'
|
25
|
+
spec.add_dependency 'breadcrumbs_on_rails', '~> 3.0.1'
|
26
|
+
spec.add_dependency 'redis-rails'
|
27
|
+
spec.add_development_dependency 'bundler', '~> 1.11'
|
28
|
+
spec.add_development_dependency 'rake', '>= 11.0'
|
29
|
+
spec.add_development_dependency 'rspec-rails'
|
30
|
+
spec.add_development_dependency 'factory_girl'
|
31
|
+
spec.add_development_dependency 'simplecov'
|
32
|
+
spec.add_development_dependency 'pry'
|
33
|
+
spec.add_development_dependency 'combustion'
|
34
|
+
spec.add_development_dependency 'sqlite3'
|
35
|
+
spec.add_development_dependency 'timecop'
|
34
36
|
end
|
data/lib/ama_layout.rb
CHANGED
@@ -1,17 +1,23 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
13
|
-
require
|
14
|
-
require
|
1
|
+
require 'ama_layout/version'
|
2
|
+
require 'rails/all'
|
3
|
+
require 'foundation-rails'
|
4
|
+
require 'draper'
|
5
|
+
require 'browser'
|
6
|
+
require 'breadcrumbs_on_rails'
|
7
|
+
require 'redis-rails'
|
8
|
+
require 'ama_layout/breadcrumb_builder'
|
9
|
+
require 'ama_layout/moneris'
|
10
|
+
require 'ama_layout/navigation'
|
11
|
+
require 'ama_layout/navigation_item'
|
12
|
+
require 'ama_layout/decorators/moneris_decorator'
|
13
|
+
require 'ama_layout/decorators/navigation_decorator'
|
14
|
+
require 'ama_layout/decorators/navigation_item_decorator'
|
15
|
+
require 'ama_layout/controllers/action_controller'
|
16
|
+
require 'ama_layout/notifications/abstract_store'
|
17
|
+
require 'ama_layout/notifications/redis_store'
|
18
|
+
require 'ama_layout/notification'
|
19
|
+
require 'ama_layout/notification_set'
|
20
|
+
require 'ama_layout/notifications'
|
15
21
|
|
16
22
|
module AmaLayout
|
17
23
|
module Rails
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module AmaLayout
|
2
|
+
class Notification
|
3
|
+
TYPES = %i[notice warning alert].freeze
|
4
|
+
DEFAULT_LIFESPAN = 1.year.freeze
|
5
|
+
FORMAT_VERSION = '1.0.0'.freeze
|
6
|
+
|
7
|
+
# NOTE: The following attributes are designed to be immutable - you need
|
8
|
+
# make a new instance to change them. The only mutable attribute is :active.
|
9
|
+
attr_reader :id, :type, :header, :content, :created_at, :lifespan, :version
|
10
|
+
attr_accessor :active
|
11
|
+
|
12
|
+
def initialize(args = {})
|
13
|
+
args = args.with_indifferent_access
|
14
|
+
@id = args[:id]
|
15
|
+
@type = args.fetch(:type, :notice).to_sym
|
16
|
+
@header = args.fetch(:header)
|
17
|
+
@content = args.fetch(:content)
|
18
|
+
@created_at = parse_time(args.fetch(:created_at))
|
19
|
+
@lifespan = parse_duration(args.fetch(:lifespan, DEFAULT_LIFESPAN))
|
20
|
+
@version = args.fetch(:version, FORMAT_VERSION)
|
21
|
+
self.active = args.fetch(:active)
|
22
|
+
invalid_type! if TYPES.exclude?(type)
|
23
|
+
end
|
24
|
+
|
25
|
+
def <=>(other)
|
26
|
+
created_at <=> other.created_at
|
27
|
+
end
|
28
|
+
|
29
|
+
def active?
|
30
|
+
active
|
31
|
+
end
|
32
|
+
|
33
|
+
def dismissed?
|
34
|
+
!active?
|
35
|
+
end
|
36
|
+
|
37
|
+
def dismiss!
|
38
|
+
self.active = false
|
39
|
+
dismissed?
|
40
|
+
end
|
41
|
+
|
42
|
+
def digest
|
43
|
+
Digest::SHA256.hexdigest(
|
44
|
+
"#{type}#{header}#{content}#{lifespan.to_i}#{version}"
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
def stale?
|
49
|
+
Time.current > created_at + lifespan
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_h
|
53
|
+
# NOTE: We want the following keys to be strings to provide
|
54
|
+
# consistency with the underlying data store.
|
55
|
+
{
|
56
|
+
'type' => type.to_s,
|
57
|
+
'header' => header,
|
58
|
+
'content' => content,
|
59
|
+
'created_at' => created_at.iso8601,
|
60
|
+
'active' => active,
|
61
|
+
'lifespan' => lifespan.to_i,
|
62
|
+
'version' => version
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def invalid_type!
|
69
|
+
raise ArgumentError, "invalid notification type: #{type}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def parse_time(time)
|
73
|
+
time.is_a?(String) ? Time.zone.parse(time) : time
|
74
|
+
end
|
75
|
+
|
76
|
+
def parse_duration(duration)
|
77
|
+
if duration.is_a?(ActiveSupport::Duration)
|
78
|
+
duration
|
79
|
+
else
|
80
|
+
duration.seconds
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module AmaLayout
|
2
|
+
# An array-like object that handles the storage and retrieval of notifications
|
3
|
+
# from the underlying data store.
|
4
|
+
#
|
5
|
+
# The raw serialization format is JSON as follows (keys are SHA256 hashes):
|
6
|
+
#
|
7
|
+
# {
|
8
|
+
# "8ca9f850c18acc17643038b2341bee3ede8a24c0f3e92f56f2109ce49fdcb616": {
|
9
|
+
# "type": "notice",
|
10
|
+
# "header": "test",
|
11
|
+
# "content": "test",
|
12
|
+
# "created_at": "2017-06-19T11:26:57.730-06:00",
|
13
|
+
# "lifespan": 31557600,
|
14
|
+
# "active": true,
|
15
|
+
# "version": "1.0.0"
|
16
|
+
# }
|
17
|
+
# }
|
18
|
+
#
|
19
|
+
class NotificationSet
|
20
|
+
include Enumerable
|
21
|
+
attr_accessor :base, :data_store, :key
|
22
|
+
|
23
|
+
delegate :each, :first, :last, :size, :[], :empty?, :any?, to: :active
|
24
|
+
|
25
|
+
def initialize(data_store, key)
|
26
|
+
self.data_store = data_store
|
27
|
+
self.key = key
|
28
|
+
self.base = fetch
|
29
|
+
clean!
|
30
|
+
end
|
31
|
+
|
32
|
+
def active
|
33
|
+
all.select(&:active?)
|
34
|
+
end
|
35
|
+
|
36
|
+
def all
|
37
|
+
@all ||= normalize(base_notifications)
|
38
|
+
end
|
39
|
+
|
40
|
+
def create(args = {})
|
41
|
+
args[:created_at] = Time.current
|
42
|
+
args[:active] = true
|
43
|
+
notification = Notification.new(args)
|
44
|
+
# previously dismissed notifications always take precendence
|
45
|
+
all.push(notification) unless base.key?(notification.digest)
|
46
|
+
save
|
47
|
+
end
|
48
|
+
|
49
|
+
def destroy!
|
50
|
+
data_store.delete(key) && reload!
|
51
|
+
end
|
52
|
+
|
53
|
+
def find(digest)
|
54
|
+
all.find { |n| n.id == digest }
|
55
|
+
end
|
56
|
+
|
57
|
+
def save
|
58
|
+
data_store.transaction do |store|
|
59
|
+
normalized = normalize(all)
|
60
|
+
self.base = serialize(normalized)
|
61
|
+
store.set(key, base.to_json)
|
62
|
+
end
|
63
|
+
reload!
|
64
|
+
end
|
65
|
+
|
66
|
+
def inspect
|
67
|
+
"<#{self.class.name}>: #{all}"
|
68
|
+
end
|
69
|
+
alias_method :to_s, :inspect
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def clean!
|
74
|
+
if dirty?
|
75
|
+
all.reject!(&:stale?)
|
76
|
+
save
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def dirty?
|
81
|
+
all.any?(&:stale?)
|
82
|
+
end
|
83
|
+
|
84
|
+
def reload!
|
85
|
+
@all = nil
|
86
|
+
self.base = fetch
|
87
|
+
all
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
91
|
+
def base_notifications
|
92
|
+
base.map { |k, v| Notification.new(v.merge(id: k)) }
|
93
|
+
end
|
94
|
+
|
95
|
+
def serialize(data)
|
96
|
+
data.inject({}) do |hash, element|
|
97
|
+
hash[element.digest] = element.to_h
|
98
|
+
hash
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def normalize(data)
|
103
|
+
# sort by reverse chronological order
|
104
|
+
data.sort { |a, b| b <=> a }
|
105
|
+
end
|
106
|
+
|
107
|
+
def fetch
|
108
|
+
result = data_store.get(key)
|
109
|
+
result.present? ? build(result) : {}
|
110
|
+
end
|
111
|
+
|
112
|
+
def build(raw)
|
113
|
+
JSON.parse(raw)
|
114
|
+
rescue JSON::ParserError
|
115
|
+
data_store.delete(key) # we should try to prevent further errors
|
116
|
+
::Rails.logger.error json_message(__FILE__, __LINE__, raw)
|
117
|
+
{}
|
118
|
+
end
|
119
|
+
|
120
|
+
def json_message(file, line, raw)
|
121
|
+
{
|
122
|
+
error: "#{self.class.name} - Invalid JSON",
|
123
|
+
file: file,
|
124
|
+
line: line,
|
125
|
+
key: key,
|
126
|
+
raw: raw
|
127
|
+
}.to_json
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module AmaLayout
|
2
|
+
# Usage:
|
3
|
+
#
|
4
|
+
# class MyClass
|
5
|
+
# include AmaLayout::Notifications
|
6
|
+
#
|
7
|
+
# notification_store AmaLayout::Notifications::RedisStore.new(options)
|
8
|
+
# notification_foreign_key :a_method_name_or_proc # defaults to :id
|
9
|
+
#
|
10
|
+
# ...
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
module Notifications
|
14
|
+
InvalidNotificationStore = Class.new(StandardError)
|
15
|
+
|
16
|
+
def self.included(base)
|
17
|
+
base.extend(ClassMethods)
|
18
|
+
base.include(InstanceMethods)
|
19
|
+
end
|
20
|
+
|
21
|
+
module InstanceMethods
|
22
|
+
def notifications
|
23
|
+
@notifications ||= NotificationSet.new(_store, _foreign_key)
|
24
|
+
end
|
25
|
+
|
26
|
+
def notifications=(other)
|
27
|
+
@notifications = other
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def _store
|
33
|
+
self.class._notification_store || invalid_store!
|
34
|
+
end
|
35
|
+
|
36
|
+
def _foreign_key
|
37
|
+
self.class._notification_foreign_key.call(self)
|
38
|
+
end
|
39
|
+
|
40
|
+
def invalid_store!
|
41
|
+
raise InvalidNotificationStore, 'a notification store must be specified'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module ClassMethods
|
46
|
+
def notification_store(store)
|
47
|
+
self._notification_store = store
|
48
|
+
end
|
49
|
+
|
50
|
+
def notification_foreign_key(key)
|
51
|
+
self._notification_foreign_key = key
|
52
|
+
end
|
53
|
+
|
54
|
+
def _notification_foreign_key
|
55
|
+
@_notification_foreign_key || Proc.new(&:id)
|
56
|
+
end
|
57
|
+
|
58
|
+
def _notification_store
|
59
|
+
@_notification_store
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def _notification_store=(store)
|
65
|
+
@_notification_store = store
|
66
|
+
end
|
67
|
+
|
68
|
+
def _notification_foreign_key=(key)
|
69
|
+
@_notification_foreign_key = key.to_proc
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module AmaLayout
|
2
|
+
module Notifications
|
3
|
+
class AbstractStore
|
4
|
+
def get(key, opts = {})
|
5
|
+
raise NotImplementedError, 'you must define a #get method in a subclass'
|
6
|
+
end
|
7
|
+
|
8
|
+
def set(key, value, opts = {})
|
9
|
+
raise NotImplementedError, 'you must define a #set method in a subclass'
|
10
|
+
end
|
11
|
+
|
12
|
+
def delete(key, opts = {})
|
13
|
+
raise NotImplementedError, 'you must define a #delete method in a subclass'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module AmaLayout
|
2
|
+
module Notifications
|
3
|
+
class RedisStore < AbstractStore
|
4
|
+
delegate :clear, to: :base
|
5
|
+
|
6
|
+
attr_accessor :base
|
7
|
+
|
8
|
+
def initialize(opts = {})
|
9
|
+
self.base = ActiveSupport::Cache.lookup_store(
|
10
|
+
:redis_store,
|
11
|
+
opts.merge(raw: true)
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
def get(key, opts = {})
|
16
|
+
if opts.fetch(:default, false)
|
17
|
+
base.fetch(key) { opts[:default] }
|
18
|
+
else
|
19
|
+
base.read(key)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def set(key, value, opts = {})
|
24
|
+
base.write(key, value, opts) == 'OK'
|
25
|
+
end
|
26
|
+
|
27
|
+
def delete(key, opts = {})
|
28
|
+
base.delete(key, opts) == 1
|
29
|
+
end
|
30
|
+
|
31
|
+
def transaction
|
32
|
+
base.data.multi do
|
33
|
+
yield self
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/ama_layout/version.rb
CHANGED
@@ -0,0 +1,237 @@
|
|
1
|
+
describe AmaLayout::NotificationSet do
|
2
|
+
let(:store) do
|
3
|
+
AmaLayout::Notifications::RedisStore.new(
|
4
|
+
db: 4,
|
5
|
+
namespace: 'test_notifications',
|
6
|
+
host: 'localhost'
|
7
|
+
)
|
8
|
+
end
|
9
|
+
let(:key) { 1 }
|
10
|
+
let(:duration) { AmaLayout::Notification::DEFAULT_LIFESPAN.to_i }
|
11
|
+
let(:store_key) { key.to_s }
|
12
|
+
let(:json) do
|
13
|
+
<<-JSON
|
14
|
+
{
|
15
|
+
"8ca9f850c18acc17643038b2341bee3ede8a24c0f3e92f56f2109ce49fdcb616": {
|
16
|
+
"type": "notice",
|
17
|
+
"header": "test",
|
18
|
+
"content": "test",
|
19
|
+
"created_at": "2017-06-19T06:00:00.000Z",
|
20
|
+
"active": true,
|
21
|
+
"lifespan": #{duration},
|
22
|
+
"version": "1.0.0"
|
23
|
+
}
|
24
|
+
}
|
25
|
+
JSON
|
26
|
+
end
|
27
|
+
let(:stale_json) do
|
28
|
+
<<-JSON
|
29
|
+
{
|
30
|
+
"d3c2bc71904100674325791b371db7446097f956ea76a304e787abd5f2588665": {
|
31
|
+
"type": "notice",
|
32
|
+
"header": "stale",
|
33
|
+
"content": "stale",
|
34
|
+
"created_at": "2012-06-19T06:00:00.000Z",
|
35
|
+
"active": true,
|
36
|
+
"lifespan": #{duration},
|
37
|
+
"version": "1.0.0"
|
38
|
+
}
|
39
|
+
}
|
40
|
+
JSON
|
41
|
+
end
|
42
|
+
|
43
|
+
subject { described_class.new(store, key) }
|
44
|
+
|
45
|
+
around(:each) do |example|
|
46
|
+
Timecop.freeze(Time.zone.local(2017, 6, 19)) do
|
47
|
+
store.clear
|
48
|
+
example.run
|
49
|
+
store.clear
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#intialize' do
|
54
|
+
context 'with valid JSON in data store' do
|
55
|
+
before(:each) do
|
56
|
+
store.set(store_key, notification)
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'without stale notifications in the data store' do
|
60
|
+
let(:notification) { json }
|
61
|
+
|
62
|
+
it 'fetches the notifications' do
|
63
|
+
expect(subject.size).to eq(1)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'with stale notifications in the data store' do
|
68
|
+
let(:notification) { stale_json }
|
69
|
+
|
70
|
+
it 'returns an empty set' do
|
71
|
+
expect(subject).to be_empty
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'cleans out stale notifications from the data store' do
|
75
|
+
subject
|
76
|
+
expect(store.get(store_key)).to eq('{}')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'with invalid JSON in data store' do
|
82
|
+
before(:each) do
|
83
|
+
store.set(store_key, '{"invalid_json":')
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'logs to Rails logger' do
|
87
|
+
expect(Rails.logger).to receive(:error).with(instance_of(String))
|
88
|
+
subject
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'deletes the key in data store' do
|
92
|
+
subject
|
93
|
+
expect(store.get(store_key)).to be nil
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'returns an empty set' do
|
97
|
+
expect(subject).to be_empty
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'sets the base attribute to a hash' do
|
101
|
+
expect(subject.base).to be_a(Hash)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'with no entry in data store' do
|
106
|
+
it 'returns an empty set' do
|
107
|
+
expect(subject).to be_empty
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe '#create' do
|
113
|
+
it 'returns the NotificationSet instance' do
|
114
|
+
expect(subject.create(header: 'test', content: 'test')).to be_a(described_class)
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'creates a new active notification' do
|
118
|
+
subject.create(header: 'test', content: 'test')
|
119
|
+
expect(subject.size).to eq(1)
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'saves a notification in data store' do
|
123
|
+
subject.create(header: 'test', content: 'test')
|
124
|
+
expect(store.get(store_key)).to be_a(String)
|
125
|
+
end
|
126
|
+
|
127
|
+
context 'when the same notification exists but is dismissed' do
|
128
|
+
before(:each) do
|
129
|
+
store.set(store_key, json)
|
130
|
+
subject.first.dismiss!
|
131
|
+
subject.save
|
132
|
+
subject.create(header: 'test', content: 'test')
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'does not overwrite the notification' do
|
136
|
+
expect(subject).to be_empty # we have only non-active notifications
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'still has the dismissed notification in the data store' do
|
140
|
+
data = JSON.parse(store.get(store_key))
|
141
|
+
notification = data.values.first
|
142
|
+
expect(data.values.first['active']).to be false
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe '#destroy' do
|
148
|
+
context 'when data is removed' do
|
149
|
+
before(:each) do
|
150
|
+
subject.create(header: 'test', content: 'test', lifespan: 1.day)
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'returns a NotificationSet instance' do
|
154
|
+
expect(subject.destroy!).to be_a(described_class)
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'removes the notifications from the data store' do
|
158
|
+
subject.destroy!
|
159
|
+
expect(store.get(store_key)).to be nil
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'returns an empty set' do
|
163
|
+
expect(subject.destroy!).to be_empty
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
context 'when data is not removed' do
|
168
|
+
it 'returns false' do
|
169
|
+
expect(subject.destroy!).to be false
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
describe '#find' do
|
175
|
+
context 'when id is not preset' do
|
176
|
+
it 'returns nil' do
|
177
|
+
expect(subject.find('invalid')).to be nil
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
context 'when id is present' do
|
182
|
+
let(:id) { subject.last.id }
|
183
|
+
|
184
|
+
before(:each) do
|
185
|
+
subject.create(header: 'test', content: 'test')
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'returns the notification' do
|
189
|
+
expect(subject.find(id)).to eq(subject.last)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
describe '#save' do
|
195
|
+
before(:each) do
|
196
|
+
subject.create(header: 'test', content: 'test')
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'saves the notifications' do
|
200
|
+
expect(subject.last.active?).to be true
|
201
|
+
subject.last.dismiss!
|
202
|
+
subject.save
|
203
|
+
expect(subject).to be_empty
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'returns the NotificationSet instance' do
|
207
|
+
expect(subject.save).to be_a(described_class)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
describe '#inspect' do
|
212
|
+
it 'returns a stringified instance' do
|
213
|
+
expect(subject.inspect).to eq('<AmaLayout::NotificationSet>: []')
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
context 'scoping' do
|
218
|
+
before(:each) do
|
219
|
+
subject.create(header: 'test', content: 'test')
|
220
|
+
subject.create(header: 'inactive', content: 'inactive')
|
221
|
+
subject.last.dismiss!
|
222
|
+
subject.save
|
223
|
+
end
|
224
|
+
|
225
|
+
describe '#all' do
|
226
|
+
it 'returns both active and inactive notifications' do
|
227
|
+
expect(subject.all.size).to eq(2)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
describe '#active' do
|
232
|
+
it 'returns only active notifications' do
|
233
|
+
expect(subject.active.size).to eq(1)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
describe AmaLayout::Notification do
|
2
|
+
let(:instance) do
|
3
|
+
described_class.new(
|
4
|
+
header: 'test',
|
5
|
+
content: 'test',
|
6
|
+
active: true,
|
7
|
+
created_at: Time.current
|
8
|
+
)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#initialize' do
|
12
|
+
let(:lifespan) { 1.day }
|
13
|
+
|
14
|
+
subject do
|
15
|
+
described_class.new(
|
16
|
+
header: 'test',
|
17
|
+
'content': 'test',
|
18
|
+
'active': true,
|
19
|
+
'created_at': time,
|
20
|
+
'lifespan': lifespan
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'with a time created_at attribute' do
|
25
|
+
let(:time) { Time.current }
|
26
|
+
|
27
|
+
it 'accepts both symbols and strings as hash keys' do
|
28
|
+
expect(subject.header).to eq('test')
|
29
|
+
expect(subject.active).to be true
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'sets a version by default' do
|
33
|
+
expect(subject.version).to eq(described_class::FORMAT_VERSION)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'with a string created_at attribute' do
|
38
|
+
let(:time) { '1984-01-01' }
|
39
|
+
|
40
|
+
it 'parses the string as a time' do
|
41
|
+
expect(subject.created_at).to eq(Time.zone.parse(time))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'with an Integer/Fixnum lifespan attribute' do
|
46
|
+
let(:time) { Time.current }
|
47
|
+
let(:lifespan) { 3600 }
|
48
|
+
|
49
|
+
it 'parses the integer to a duration of seconds' do
|
50
|
+
expect(subject.lifespan).to eq(lifespan.seconds)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'with an invalid type attribute' do
|
55
|
+
let(:parameters) do
|
56
|
+
{
|
57
|
+
type: :invalid,
|
58
|
+
header: 'test',
|
59
|
+
content: 'test',
|
60
|
+
created_at: Time.current,
|
61
|
+
active: true
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'raises ArgumentError' do
|
66
|
+
expect { described_class.new(parameters) }.to raise_error(ArgumentError)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe '#<=>' do
|
72
|
+
let(:old) do
|
73
|
+
described_class.new(
|
74
|
+
header: 'test',
|
75
|
+
content: 'test',
|
76
|
+
active: true,
|
77
|
+
created_at: Date.new(1984)
|
78
|
+
)
|
79
|
+
end
|
80
|
+
let(:new) do
|
81
|
+
described_class.new(
|
82
|
+
header: 'test',
|
83
|
+
content: 'test',
|
84
|
+
active: true,
|
85
|
+
created_at: Date.new(2017)
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'sorts by created_at date' do
|
90
|
+
expect(old <=> new).to eq(-1)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe '#active?' do
|
95
|
+
subject do
|
96
|
+
described_class.new(
|
97
|
+
header: 'test',
|
98
|
+
content: 'test',
|
99
|
+
active: active,
|
100
|
+
created_at: Time.current
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'when active' do
|
105
|
+
let(:active) { true }
|
106
|
+
|
107
|
+
it 'returns true' do
|
108
|
+
expect(subject.active?).to be true
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'when inactive' do
|
113
|
+
let(:active) { false }
|
114
|
+
|
115
|
+
it 'returns false' do
|
116
|
+
expect(subject.active?).to be false
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe 'dismissed?' do
|
122
|
+
subject do
|
123
|
+
described_class.new(
|
124
|
+
header: 'test',
|
125
|
+
content: 'test',
|
126
|
+
active: active,
|
127
|
+
created_at: Time.current
|
128
|
+
)
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'when active' do
|
132
|
+
let(:active) { true }
|
133
|
+
|
134
|
+
it 'returns false' do
|
135
|
+
expect(subject.dismissed?).to be false
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context 'when inactive' do
|
140
|
+
let(:active) { false }
|
141
|
+
|
142
|
+
it 'returns true' do
|
143
|
+
expect(subject.dismissed?).to be true
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe '#dismiss!' do
|
149
|
+
it 'returns true' do
|
150
|
+
expect(instance.dismiss!).to be true
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'sets the :active flag to false' do
|
154
|
+
instance.dismiss!
|
155
|
+
expect(instance.active?).to be false
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe '#digest' do
|
160
|
+
context 'with the same objects' do
|
161
|
+
let(:other) { instance.dup }
|
162
|
+
|
163
|
+
it 'produces the same digest' do
|
164
|
+
expect(instance.digest).to eq(other.digest)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
context 'with different objects' do
|
169
|
+
let(:other) do
|
170
|
+
described_class.new(
|
171
|
+
header: 'other',
|
172
|
+
content: 'other',
|
173
|
+
active: true,
|
174
|
+
created_at: Time.current
|
175
|
+
)
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'produces different digests' do
|
179
|
+
expect(instance.digest).to_not eq(other.digest)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
describe '#to_h' do
|
185
|
+
it 'returns a hash' do
|
186
|
+
expect(instance.to_h).to be_a(Hash)
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'is not empty' do
|
190
|
+
expect(instance.to_h).to_not be_empty
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
describe AmaLayout::Notifications::AbstractStore do
|
2
|
+
context 'when inheriting' do
|
3
|
+
subject { Class.new(described_class).new }
|
4
|
+
|
5
|
+
describe '#get' do
|
6
|
+
it 'raises NotImplementedError' do
|
7
|
+
expect { subject.get('test') }.to raise_error(NotImplementedError)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#set' do
|
12
|
+
it 'raises NotImplementedError' do
|
13
|
+
expect { subject.set('test', 'test') }.to raise_error(NotImplementedError)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#delete' do
|
18
|
+
it 'raises NotImplementedError' do
|
19
|
+
expect { subject.delete('test') }.to raise_error(NotImplementedError)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
describe AmaLayout::Notifications::RedisStore do
|
2
|
+
subject do
|
3
|
+
described_class.new(
|
4
|
+
db: 4,
|
5
|
+
namespace: 'test_notifications',
|
6
|
+
host: 'localhost'
|
7
|
+
)
|
8
|
+
end
|
9
|
+
|
10
|
+
around(:each) do |example|
|
11
|
+
subject.clear
|
12
|
+
example.run
|
13
|
+
subject.clear
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#get' do
|
17
|
+
context 'when a key is not present' do
|
18
|
+
it 'returns nil' do
|
19
|
+
expect(subject.get('missing')).to be nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'when a key is present' do
|
24
|
+
before(:each) do
|
25
|
+
subject.set('key', 'value')
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'returns the value' do
|
29
|
+
expect(subject.get('key')).to eq('value')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'with a default value' do
|
34
|
+
it 'sets a nil key to the default value' do
|
35
|
+
subject.get('missing', default: 'test')
|
36
|
+
expect(subject.get('missing')).to eq('test')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#set' do
|
42
|
+
it 'sets the value for a given key' do
|
43
|
+
subject.set('test', 'value')
|
44
|
+
expect(subject.get('test')).to eq('value')
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'returns true' do
|
48
|
+
expect(subject.set('test', 'value')).to be true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#delete' do
|
53
|
+
context 'when a value is deleted' do
|
54
|
+
before(:each) do
|
55
|
+
subject.set('key', 'value')
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'returns true' do
|
59
|
+
expect(subject.delete('key')).to be true
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'deletes the key' do
|
63
|
+
subject.delete('key')
|
64
|
+
expect(subject.get('key')).to be nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'when a value is not deleted' do
|
69
|
+
it 'returns false' do
|
70
|
+
expect(subject.delete('key')).to be false
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe '#transaction' do
|
76
|
+
it 'does not commit if an exception is raised' do
|
77
|
+
begin
|
78
|
+
subject.transaction do |store|
|
79
|
+
store.set('key', 'value')
|
80
|
+
raise StandardError
|
81
|
+
end
|
82
|
+
rescue StandardError
|
83
|
+
end
|
84
|
+
expect(subject.get('key')).to be nil
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'commits to redis successfully' do
|
88
|
+
subject.transaction do |store|
|
89
|
+
store.set('key', 'value')
|
90
|
+
end
|
91
|
+
expect(subject.get('key')).to eq('value')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
describe AmaLayout::Notifications do
|
2
|
+
let(:store) do
|
3
|
+
AmaLayout::Notifications::RedisStore.new(
|
4
|
+
db: 4,
|
5
|
+
namespace: 'test_notifications',
|
6
|
+
host: 'localhost'
|
7
|
+
)
|
8
|
+
end
|
9
|
+
let(:json) do
|
10
|
+
<<-JSON
|
11
|
+
{
|
12
|
+
"8ca9f850c18acc17643038b2341bee3ede8a24c0f3e92f56f2109ce49fdcb616": {
|
13
|
+
"type": "notice",
|
14
|
+
"header": "test",
|
15
|
+
"content": "test",
|
16
|
+
"created_at": "2017-06-19T06:00:00.000Z",
|
17
|
+
"active": true,
|
18
|
+
"lifespan": 31557600,
|
19
|
+
"version": "1.0.0"
|
20
|
+
}
|
21
|
+
}
|
22
|
+
JSON
|
23
|
+
end
|
24
|
+
|
25
|
+
around(:each) do |example|
|
26
|
+
Timecop.freeze(Time.zone.local(2017, 6, 19)) do
|
27
|
+
store.clear
|
28
|
+
example.run
|
29
|
+
store.clear
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when including module' do
|
34
|
+
let(:klass) { Class.new.include(described_class) }
|
35
|
+
|
36
|
+
context 'class methods' do
|
37
|
+
before(:each) do
|
38
|
+
klass.class_eval do
|
39
|
+
notification_store AmaLayout::Notifications::RedisStore.new(
|
40
|
+
db: 4,
|
41
|
+
namespace: 'test_notifications',
|
42
|
+
host: 'localhost'
|
43
|
+
)
|
44
|
+
notification_foreign_key :my_id
|
45
|
+
|
46
|
+
def my_id
|
47
|
+
@id ||= SecureRandom.uuid
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#_notification_foreign_key' do
|
53
|
+
it 'returns the id method as a proc' do
|
54
|
+
expect(klass._notification_foreign_key).to be_a(Proc)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#_notification_store' do
|
59
|
+
it 'returns the set data store' do
|
60
|
+
expect(klass._notification_store).to be_an(AmaLayout::Notifications::RedisStore)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'instance methods' do
|
66
|
+
context 'with a valid notification store' do
|
67
|
+
subject { klass.new }
|
68
|
+
|
69
|
+
before(:each) do
|
70
|
+
klass.class_eval do
|
71
|
+
notification_store AmaLayout::Notifications::RedisStore.new(
|
72
|
+
db: 4,
|
73
|
+
namespace: 'test_notifications',
|
74
|
+
host: 'localhost'
|
75
|
+
)
|
76
|
+
notification_foreign_key :my_id
|
77
|
+
|
78
|
+
def my_id
|
79
|
+
@id ||= SecureRandom.uuid
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe '#notifications' do
|
85
|
+
before(:each) do
|
86
|
+
store.set(subject.my_id, json)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'fetches notifications from data store' do
|
90
|
+
expect(subject.notifications.size).to eq(1)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe '#notifications=' do
|
95
|
+
it 'resets the notifications to nil' do
|
96
|
+
subject.notifications = nil
|
97
|
+
expect(subject.notifications).to be_empty
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'with an undefined notification store' do
|
103
|
+
it 'raises InvalidNotificationStore' do
|
104
|
+
expect { klass.new.notifications }.to raise_error(AmaLayout::Notifications::InvalidNotificationStore)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,15 +1,16 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
1
|
+
require 'simplecov'
|
2
|
+
require 'factory_girl'
|
3
|
+
require 'ama_layout'
|
4
|
+
require 'pry'
|
5
|
+
require 'rspec/rails'
|
6
|
+
require 'combustion'
|
7
|
+
require 'timecop'
|
7
8
|
|
8
|
-
ENV[
|
9
|
+
ENV['RAILS_ENV'] ||= 'test'
|
9
10
|
|
10
11
|
Combustion.initialize! :all
|
11
12
|
|
12
|
-
Dir[
|
13
|
+
Dir['./spec/support/**/*.rb'].sort.each { |file| require file }
|
13
14
|
|
14
15
|
FactoryGirl.find_definitions
|
15
16
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ama_layout
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael van den Beuken
|
@@ -18,7 +18,7 @@ authors:
|
|
18
18
|
autorequire:
|
19
19
|
bindir: bin
|
20
20
|
cert_chain: []
|
21
|
-
date: 2017-
|
21
|
+
date: 2017-06-22 00:00:00.000000000 Z
|
22
22
|
dependencies:
|
23
23
|
- !ruby/object:Gem::Dependency
|
24
24
|
name: foundation-rails
|
@@ -90,6 +90,20 @@ dependencies:
|
|
90
90
|
- - "~>"
|
91
91
|
- !ruby/object:Gem::Version
|
92
92
|
version: 3.0.1
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: redis-rails
|
95
|
+
requirement: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
type: :runtime
|
101
|
+
prerelease: false
|
102
|
+
version_requirements: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
93
107
|
- !ruby/object:Gem::Dependency
|
94
108
|
name: bundler
|
95
109
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,14 +122,14 @@ dependencies:
|
|
108
122
|
name: rake
|
109
123
|
requirement: !ruby/object:Gem::Requirement
|
110
124
|
requirements:
|
111
|
-
- - "
|
125
|
+
- - ">="
|
112
126
|
- !ruby/object:Gem::Version
|
113
127
|
version: '11.0'
|
114
128
|
type: :development
|
115
129
|
prerelease: false
|
116
130
|
version_requirements: !ruby/object:Gem::Requirement
|
117
131
|
requirements:
|
118
|
-
- - "
|
132
|
+
- - ">="
|
119
133
|
- !ruby/object:Gem::Version
|
120
134
|
version: '11.0'
|
121
135
|
- !ruby/object:Gem::Dependency
|
@@ -202,6 +216,20 @@ dependencies:
|
|
202
216
|
- - ">="
|
203
217
|
- !ruby/object:Gem::Version
|
204
218
|
version: '0'
|
219
|
+
- !ruby/object:Gem::Dependency
|
220
|
+
name: timecop
|
221
|
+
requirement: !ruby/object:Gem::Requirement
|
222
|
+
requirements:
|
223
|
+
- - ">="
|
224
|
+
- !ruby/object:Gem::Version
|
225
|
+
version: '0'
|
226
|
+
type: :development
|
227
|
+
prerelease: false
|
228
|
+
version_requirements: !ruby/object:Gem::Requirement
|
229
|
+
requirements:
|
230
|
+
- - ">="
|
231
|
+
- !ruby/object:Gem::Version
|
232
|
+
version: '0'
|
205
233
|
description: ".ama.ab.ca site layouts"
|
206
234
|
email:
|
207
235
|
- michael.beuken@gmail.com
|
@@ -278,6 +306,11 @@ files:
|
|
278
306
|
- lib/ama_layout/navigation.rb
|
279
307
|
- lib/ama_layout/navigation.yml
|
280
308
|
- lib/ama_layout/navigation_item.rb
|
309
|
+
- lib/ama_layout/notification.rb
|
310
|
+
- lib/ama_layout/notification_set.rb
|
311
|
+
- lib/ama_layout/notifications.rb
|
312
|
+
- lib/ama_layout/notifications/abstract_store.rb
|
313
|
+
- lib/ama_layout/notifications/redis_store.rb
|
281
314
|
- lib/ama_layout/version.rb
|
282
315
|
- spec/ama_layout/breadcrumb_builder_spec.rb
|
283
316
|
- spec/ama_layout/controllers/pages_controller_spec.rb
|
@@ -290,6 +323,11 @@ files:
|
|
290
323
|
- spec/ama_layout/moneris_spec.rb
|
291
324
|
- spec/ama_layout/navigation_item_spec.rb
|
292
325
|
- spec/ama_layout/navigation_spec.rb
|
326
|
+
- spec/ama_layout/notification_set_spec.rb
|
327
|
+
- spec/ama_layout/notification_spec.rb
|
328
|
+
- spec/ama_layout/notifications/abstract_store_spec.rb
|
329
|
+
- spec/ama_layout/notifications/redis_store_spec.rb
|
330
|
+
- spec/ama_layout/notifications_spec.rb
|
293
331
|
- spec/factories/navigation.rb
|
294
332
|
- spec/factories/navigation_item.rb
|
295
333
|
- spec/helpers/ama_layout_breadcrumb_helper_spec.rb
|
@@ -303,8 +341,7 @@ files:
|
|
303
341
|
- spec/internal/log/.gitignore
|
304
342
|
- spec/internal/public/favicon.ico
|
305
343
|
- spec/spec_helper.rb
|
306
|
-
|
307
|
-
homepage: ''
|
344
|
+
homepage: https://github.com/amaabca/ama_layout
|
308
345
|
licenses:
|
309
346
|
- MIT
|
310
347
|
metadata: {}
|
@@ -324,7 +361,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
324
361
|
version: '0'
|
325
362
|
requirements: []
|
326
363
|
rubyforge_project:
|
327
|
-
rubygems_version: 2.
|
364
|
+
rubygems_version: 2.6.12
|
328
365
|
signing_key:
|
329
366
|
specification_version: 4
|
330
367
|
summary: ".ama.ab.ca site layouts"
|
@@ -340,6 +377,11 @@ test_files:
|
|
340
377
|
- spec/ama_layout/moneris_spec.rb
|
341
378
|
- spec/ama_layout/navigation_item_spec.rb
|
342
379
|
- spec/ama_layout/navigation_spec.rb
|
380
|
+
- spec/ama_layout/notification_set_spec.rb
|
381
|
+
- spec/ama_layout/notification_spec.rb
|
382
|
+
- spec/ama_layout/notifications/abstract_store_spec.rb
|
383
|
+
- spec/ama_layout/notifications/redis_store_spec.rb
|
384
|
+
- spec/ama_layout/notifications_spec.rb
|
343
385
|
- spec/factories/navigation.rb
|
344
386
|
- spec/factories/navigation_item.rb
|
345
387
|
- spec/helpers/ama_layout_breadcrumb_helper_spec.rb
|
data/styles.scss
DELETED
File without changes
|