hoodoo 1.0.2
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 +7 -0
- data/bin/hoodoo +5 -0
- data/lib/hoodoo.rb +27 -0
- data/lib/hoodoo/active.rb +32 -0
- data/lib/hoodoo/active/active_model/uuid_validator.rb +45 -0
- data/lib/hoodoo/active/active_record/base.rb +81 -0
- data/lib/hoodoo/active/active_record/creator.rb +134 -0
- data/lib/hoodoo/active/active_record/dated.rb +343 -0
- data/lib/hoodoo/active/active_record/error_mapping.rb +351 -0
- data/lib/hoodoo/active/active_record/finder.rb +606 -0
- data/lib/hoodoo/active/active_record/search_helper.rb +189 -0
- data/lib/hoodoo/active/active_record/secure.rb +431 -0
- data/lib/hoodoo/active/active_record/support.rb +106 -0
- data/lib/hoodoo/active/active_record/translated.rb +87 -0
- data/lib/hoodoo/active/active_record/uuid.rb +80 -0
- data/lib/hoodoo/active/active_record/writer.rb +321 -0
- data/lib/hoodoo/client.rb +23 -0
- data/lib/hoodoo/client/augmented_array.rb +29 -0
- data/lib/hoodoo/client/augmented_base.rb +168 -0
- data/lib/hoodoo/client/augmented_hash.rb +23 -0
- data/lib/hoodoo/client/client.rb +354 -0
- data/lib/hoodoo/client/endpoint/endpoint.rb +427 -0
- data/lib/hoodoo/client/endpoint/endpoints/amqp.rb +180 -0
- data/lib/hoodoo/client/endpoint/endpoints/auto_session.rb +194 -0
- data/lib/hoodoo/client/endpoint/endpoints/http.rb +203 -0
- data/lib/hoodoo/client/endpoint/endpoints/http_based.rb +367 -0
- data/lib/hoodoo/client/endpoint/endpoints/not_found.rb +59 -0
- data/lib/hoodoo/client/headers.rb +269 -0
- data/lib/hoodoo/communicators.rb +23 -0
- data/lib/hoodoo/communicators/fast.rb +44 -0
- data/lib/hoodoo/communicators/pool.rb +601 -0
- data/lib/hoodoo/communicators/slow.rb +84 -0
- data/lib/hoodoo/data.rb +51 -0
- data/lib/hoodoo/data/resources/caller.rb +39 -0
- data/lib/hoodoo/data/resources/errors.rb +28 -0
- data/lib/hoodoo/data/resources/log.rb +31 -0
- data/lib/hoodoo/data/resources/session.rb +26 -0
- data/lib/hoodoo/data/types/error_primitive.rb +27 -0
- data/lib/hoodoo/data/types/permissions.rb +40 -0
- data/lib/hoodoo/data/types/permissions_defaults.rb +32 -0
- data/lib/hoodoo/data/types/permissions_full.rb +28 -0
- data/lib/hoodoo/data/types/permissions_resources.rb +31 -0
- data/lib/hoodoo/discovery.rb +20 -0
- data/lib/hoodoo/errors.rb +19 -0
- data/lib/hoodoo/errors/error_descriptions.rb +229 -0
- data/lib/hoodoo/errors/errors.rb +322 -0
- data/lib/hoodoo/generator.rb +139 -0
- data/lib/hoodoo/logger.rb +23 -0
- data/lib/hoodoo/logger/fast_writer.rb +27 -0
- data/lib/hoodoo/logger/flattener_mixin.rb +36 -0
- data/lib/hoodoo/logger/logger.rb +387 -0
- data/lib/hoodoo/logger/slow_writer.rb +49 -0
- data/lib/hoodoo/logger/writer_mixin.rb +52 -0
- data/lib/hoodoo/logger/writers/file_writer.rb +45 -0
- data/lib/hoodoo/logger/writers/log_entries_dot_com_writer.rb +64 -0
- data/lib/hoodoo/logger/writers/stream_writer.rb +43 -0
- data/lib/hoodoo/middleware.rb +33 -0
- data/lib/hoodoo/presenters.rb +45 -0
- data/lib/hoodoo/presenters/base.rb +281 -0
- data/lib/hoodoo/presenters/base_dsl.rb +519 -0
- data/lib/hoodoo/presenters/common_resource_fields.rb +31 -0
- data/lib/hoodoo/presenters/embedding.rb +232 -0
- data/lib/hoodoo/presenters/types/array.rb +118 -0
- data/lib/hoodoo/presenters/types/boolean.rb +26 -0
- data/lib/hoodoo/presenters/types/date.rb +26 -0
- data/lib/hoodoo/presenters/types/date_time.rb +26 -0
- data/lib/hoodoo/presenters/types/decimal.rb +47 -0
- data/lib/hoodoo/presenters/types/enum.rb +55 -0
- data/lib/hoodoo/presenters/types/field.rb +158 -0
- data/lib/hoodoo/presenters/types/float.rb +26 -0
- data/lib/hoodoo/presenters/types/hash.rb +361 -0
- data/lib/hoodoo/presenters/types/integer.rb +26 -0
- data/lib/hoodoo/presenters/types/object.rb +117 -0
- data/lib/hoodoo/presenters/types/string.rb +53 -0
- data/lib/hoodoo/presenters/types/tags.rb +24 -0
- data/lib/hoodoo/presenters/types/text.rb +26 -0
- data/lib/hoodoo/presenters/types/uuid.rb +54 -0
- data/lib/hoodoo/services.rb +34 -0
- data/lib/hoodoo/services/discovery/discoverers/by_consul.rb +66 -0
- data/lib/hoodoo/services/discovery/discoverers/by_convention.rb +173 -0
- data/lib/hoodoo/services/discovery/discoverers/by_drb/by_drb.rb +195 -0
- data/lib/hoodoo/services/discovery/discoverers/by_drb/drb_server.rb +166 -0
- data/lib/hoodoo/services/discovery/discoverers/by_drb/drb_server_start.rb +37 -0
- data/lib/hoodoo/services/discovery/discovery.rb +186 -0
- data/lib/hoodoo/services/discovery/results/for_amqp.rb +58 -0
- data/lib/hoodoo/services/discovery/results/for_http.rb +85 -0
- data/lib/hoodoo/services/discovery/results/for_local.rb +85 -0
- data/lib/hoodoo/services/discovery/results/for_remote.rb +57 -0
- data/lib/hoodoo/services/middleware/amqp_log_message.rb +186 -0
- data/lib/hoodoo/services/middleware/amqp_log_writer.rb +119 -0
- data/lib/hoodoo/services/middleware/endpoints/inter_resource_local.rb +130 -0
- data/lib/hoodoo/services/middleware/endpoints/inter_resource_remote.rb +202 -0
- data/lib/hoodoo/services/middleware/exception_reporting/base_reporter.rb +105 -0
- data/lib/hoodoo/services/middleware/exception_reporting/exception_reporting.rb +115 -0
- data/lib/hoodoo/services/middleware/exception_reporting/reporters/airbrake_reporter.rb +64 -0
- data/lib/hoodoo/services/middleware/exception_reporting/reporters/raygun_reporter.rb +63 -0
- data/lib/hoodoo/services/middleware/interaction.rb +127 -0
- data/lib/hoodoo/services/middleware/middleware.rb +2705 -0
- data/lib/hoodoo/services/middleware/rack_monkey_patch.rb +73 -0
- data/lib/hoodoo/services/services/context.rb +153 -0
- data/lib/hoodoo/services/services/implementation.rb +132 -0
- data/lib/hoodoo/services/services/interface.rb +934 -0
- data/lib/hoodoo/services/services/permissions.rb +250 -0
- data/lib/hoodoo/services/services/request.rb +189 -0
- data/lib/hoodoo/services/services/response.rb +316 -0
- data/lib/hoodoo/services/services/service.rb +141 -0
- data/lib/hoodoo/services/services/session.rb +729 -0
- data/lib/hoodoo/utilities.rb +12 -0
- data/lib/hoodoo/utilities/string_inquirer.rb +54 -0
- data/lib/hoodoo/utilities/utilities.rb +380 -0
- data/lib/hoodoo/utilities/uuid.rb +44 -0
- data/lib/hoodoo/version.rb +17 -0
- data/spec/active/active_record/base_spec.rb +57 -0
- data/spec/active/active_record/creator_spec.rb +88 -0
- data/spec/active/active_record/dated_spec.rb +248 -0
- data/spec/active/active_record/error_mapping_spec.rb +360 -0
- data/spec/active/active_record/finder_spec.rb +744 -0
- data/spec/active/active_record/search_helper_spec.rb +384 -0
- data/spec/active/active_record/secure_spec.rb +435 -0
- data/spec/active/active_record/support_spec.rb +225 -0
- data/spec/active/active_record/translated_spec.rb +19 -0
- data/spec/active/active_record/uuid_spec.rb +72 -0
- data/spec/active/active_record/writer_spec.rb +272 -0
- data/spec/alchemy/alchemy-amq.rb +33 -0
- data/spec/client/augmented_array_spec.rb +15 -0
- data/spec/client/augmented_base_spec.rb +50 -0
- data/spec/client/augmented_hash_spec.rb +15 -0
- data/spec/client/client_spec.rb +955 -0
- data/spec/client/endpoint/endpoint_spec.rb +70 -0
- data/spec/client/endpoint/endpoints/amqp_spec.rb +16 -0
- data/spec/client/endpoint/endpoints/auto_session_spec.rb +9 -0
- data/spec/client/endpoint/endpoints/http_based_spec.rb +9 -0
- data/spec/client/endpoint/endpoints/http_spec.rb +103 -0
- data/spec/client/endpoint/endpoints/not_found_spec.rb +35 -0
- data/spec/client/headers_spec.rb +172 -0
- data/spec/communicators/fast_spec.rb +9 -0
- data/spec/communicators/pool_spec.rb +339 -0
- data/spec/communicators/slow_spec.rb +15 -0
- data/spec/data/resources/caller_spec.rb +156 -0
- data/spec/data/resources/errors_spec.rb +22 -0
- data/spec/data/resources/log_spec.rb +20 -0
- data/spec/data/resources/session_spec.rb +15 -0
- data/spec/data/types/error_primitive_spec.rb +15 -0
- data/spec/data/types/permissions_defaults_spec.rb +25 -0
- data/spec/data/types/permissions_full_spec.rb +44 -0
- data/spec/data/types/permissions_resources_spec.rb +34 -0
- data/spec/data/types/permissions_spec.rb +37 -0
- data/spec/errors/error_descriptions_spec.rb +98 -0
- data/spec/errors/errors_spec.rb +346 -0
- data/spec/integration/service_actions_spec.rb +112 -0
- data/spec/logger/fast_writer_spec.rb +18 -0
- data/spec/logger/logger_spec.rb +259 -0
- data/spec/logger/slow_writer_spec.rb +144 -0
- data/spec/logger/writers/file_writer_spec.rb +37 -0
- data/spec/logger/writers/log_entries_dot_com_writer_spec.rb +29 -0
- data/spec/logger/writers/stream_writer_spec.rb +38 -0
- data/spec/presenters/base_dsl_spec.rb +111 -0
- data/spec/presenters/base_spec.rb +871 -0
- data/spec/presenters/common_resource_fields_spec.rb +30 -0
- data/spec/presenters/embedding_spec.rb +87 -0
- data/spec/presenters/types/array_spec.rb +249 -0
- data/spec/presenters/types/boolean_spec.rb +51 -0
- data/spec/presenters/types/date_spec.rb +57 -0
- data/spec/presenters/types/date_time_spec.rb +59 -0
- data/spec/presenters/types/decimal_spec.rb +58 -0
- data/spec/presenters/types/enum_spec.rb +71 -0
- data/spec/presenters/types/field_spec.rb +77 -0
- data/spec/presenters/types/float_spec.rb +50 -0
- data/spec/presenters/types/hash_spec.rb +1069 -0
- data/spec/presenters/types/integer_spec.rb +50 -0
- data/spec/presenters/types/object_spec.rb +177 -0
- data/spec/presenters/types/string_spec.rb +65 -0
- data/spec/presenters/types/tags_spec.rb +56 -0
- data/spec/presenters/types/text_spec.rb +50 -0
- data/spec/presenters/types/uuid_spec.rb +46 -0
- data/spec/presenters/walk_spec.rb +198 -0
- data/spec/services/discovery/discoverers/by_consul_spec.rb +29 -0
- data/spec/services/discovery/discoverers/by_convention_spec.rb +67 -0
- data/spec/services/discovery/discoverers/by_drb/by_drb_spec.rb +80 -0
- data/spec/services/discovery/discoverers/by_drb/drb_server_spec.rb +205 -0
- data/spec/services/discovery/discovery_spec.rb +73 -0
- data/spec/services/discovery/results/for_amqp_spec.rb +17 -0
- data/spec/services/discovery/results/for_http_spec.rb +37 -0
- data/spec/services/discovery/results/for_local_spec.rb +21 -0
- data/spec/services/discovery/results/for_remote_spec.rb +15 -0
- data/spec/services/middleware/amqp_log_message_spec.rb +60 -0
- data/spec/services/middleware/amqp_log_writer_spec.rb +95 -0
- data/spec/services/middleware/endpoints/inter_resource_local_spec.rb +9 -0
- data/spec/services/middleware/endpoints/inter_resource_remote_spec.rb +9 -0
- data/spec/services/middleware/exception_reporting/base_reporter_spec.rb +16 -0
- data/spec/services/middleware/exception_reporting/exception_reporting_spec.rb +92 -0
- data/spec/services/middleware/exception_reporting/reporters/airbrake_reporter_spec.rb +24 -0
- data/spec/services/middleware/exception_reporting/reporters/raygun_reporter_spec.rb +23 -0
- data/spec/services/middleware/middleware_cors_spec.rb +93 -0
- data/spec/services/middleware/middleware_create_update_spec.rb +489 -0
- data/spec/services/middleware/middleware_dated_at_spec.rb +186 -0
- data/spec/services/middleware/middleware_exotic_communication_spec.rb +560 -0
- data/spec/services/middleware/middleware_logging_spec.rb +356 -0
- data/spec/services/middleware/middleware_multi_local_spec.rb +1094 -0
- data/spec/services/middleware/middleware_multi_remote_spec.rb +1440 -0
- data/spec/services/middleware/middleware_permissions_spec.rb +1014 -0
- data/spec/services/middleware/middleware_public_spec.rb +238 -0
- data/spec/services/middleware/middleware_spec.rb +1569 -0
- data/spec/services/middleware/string_inquirer_spec.rb +30 -0
- data/spec/services/services/application_spec.rb +74 -0
- data/spec/services/services/context_spec.rb +48 -0
- data/spec/services/services/implementation_spec.rb +45 -0
- data/spec/services/services/interface_spec.rb +262 -0
- data/spec/services/services/permissions_spec.rb +249 -0
- data/spec/services/services/request_spec.rb +95 -0
- data/spec/services/services/response_spec.rb +250 -0
- data/spec/services/services/session_spec.rb +432 -0
- data/spec/spec_helper.rb +298 -0
- data/spec/utilities/utilities_spec.rb +537 -0
- data/spec/utilities/uuid_spec.rb +20 -0
- metadata +615 -0
@@ -0,0 +1,141 @@
|
|
1
|
+
########################################################################
|
2
|
+
# File:: service.rb
|
3
|
+
# (C):: Loyalty New Zealand 2014
|
4
|
+
#
|
5
|
+
# Purpose:: Define a class that service authors subclass and use to
|
6
|
+
# declare the component interfaces within the service via a
|
7
|
+
# very small DSL.
|
8
|
+
#
|
9
|
+
# This class is passed to Rack and treated like an endpoint
|
10
|
+
# Rack application, though the service middleware in practice
|
11
|
+
# does not pass on calls using the Rack interface; it uses the
|
12
|
+
# custom calls exposed by Hoodoo::Services::Implementation.
|
13
|
+
# Rack's involvement between the two is really limited to just
|
14
|
+
# passing an instance of the service application subclass to
|
15
|
+
# the middleware so it knows who to "talk to".
|
16
|
+
# ----------------------------------------------------------------------
|
17
|
+
# 23-Sep-2014 (ADH): Created.
|
18
|
+
########################################################################
|
19
|
+
|
20
|
+
module Hoodoo; module Services
|
21
|
+
|
22
|
+
# Hoodoo::Services::Service is subclassed by people writing service
|
23
|
+
# implementations; the subclasses are the entrypoint for platform services.
|
24
|
+
#
|
25
|
+
# It's really just a container of one or more interface classes, which are
|
26
|
+
# all Hoodoo::Services::Interface subclasses. The Rack middleware in
|
27
|
+
# Hoodoo::Services::Middleware uses the Hoodoo::Services::Service to find
|
28
|
+
# out what interfaces it implements. Those interface classes nominate a Ruby
|
29
|
+
# class of the author's choice in which they've written the implementation
|
30
|
+
# for that interface. Interfaces also declare themselves to be available at
|
31
|
+
# a particular URL endpoint (as a path fragment); this is used by the
|
32
|
+
# middleware to route inbound requests to the correct implementation class.
|
33
|
+
#
|
34
|
+
# Suppose we defined a PurchaseInterface and RefundInterface which we wanted
|
35
|
+
# both to be available as a Shopping Service:
|
36
|
+
#
|
37
|
+
# class PurchaseImplementation < Hoodoo::Services::Implementation
|
38
|
+
# # ...
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# class PurchaseInterface < Hoodoo::Services::Interface
|
42
|
+
# interface :Purchase do
|
43
|
+
# endpoint :purchases, PurchaseImplementation
|
44
|
+
# # ...
|
45
|
+
# end
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# class RefundImplementation < Hoodoo::Services::Implementation
|
49
|
+
# # ...
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# class RefundInterface < Hoodoo::Services::Interface
|
53
|
+
# interface :Refund do
|
54
|
+
# endpoint :refunds, RefundImplementation
|
55
|
+
# # ...
|
56
|
+
# end
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# ...then the *entire* Service subclass for the Shopping Service
|
60
|
+
# could be as small as this:
|
61
|
+
#
|
62
|
+
# class ShoppingService < Hoodoo::Services::Service
|
63
|
+
# comprised_of PurchaseInterface,
|
64
|
+
# RefundInterface
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# Names of subclasses in the above examples are chosen for clarity and the
|
68
|
+
# naming approach indicated is recommended, but it's not mandatory. Choose
|
69
|
+
# choose whatever you feel best fits your code and style.
|
70
|
+
#
|
71
|
+
# Conceptually, one might just have a single interface per application for
|
72
|
+
# very small services, but you may want to logically group more interfaces in
|
73
|
+
# one service for code clarity/locality. More realistically, efficiency may
|
74
|
+
# dictate that certain interfaces have such heavy reliance and relationships
|
75
|
+
# between database contents that sharing the data models between those
|
76
|
+
# interface classes makes sense; you would group them under the same service
|
77
|
+
# application, sacrificing full decoupling. As a service author, the choice
|
78
|
+
# is yours.
|
79
|
+
#
|
80
|
+
class Service
|
81
|
+
|
82
|
+
# Return an array of the classes that make up the interfaces for this
|
83
|
+
# service. Each is a Hoodoo::Services::Interface subclass that was
|
84
|
+
# registered by the subclass through a call to #comprised_of.
|
85
|
+
#
|
86
|
+
def self.component_interfaces
|
87
|
+
@component_interfaces
|
88
|
+
end
|
89
|
+
|
90
|
+
# Instance method which calls through to ::component_interfaces and returns
|
91
|
+
# its result.
|
92
|
+
#
|
93
|
+
def component_interfaces
|
94
|
+
self.class.component_interfaces
|
95
|
+
end
|
96
|
+
|
97
|
+
# Since service implementations are not pure Rack apps but really service
|
98
|
+
# middleware clients, they shouldn't ever have "call" invoked directly.
|
99
|
+
# This method is not intended to be overridden and just complains if Rack
|
100
|
+
# ends up calling here directly by accident.
|
101
|
+
#
|
102
|
+
# +env+:: Rack environment (ignored).
|
103
|
+
#
|
104
|
+
def call( env )
|
105
|
+
raise "Hoodoo::Services::Implementation subclasses should only be called through the middleware - add 'use Hoodoo::Services::Middleware' to (e.g.) config.ru"
|
106
|
+
end
|
107
|
+
|
108
|
+
protected
|
109
|
+
|
110
|
+
# Called by subclasses listing one or more Hoodoo::Services::Interface
|
111
|
+
# subclasses that make up the service implementation as a whole.
|
112
|
+
#
|
113
|
+
# Example:
|
114
|
+
#
|
115
|
+
# class ShoppingService < Hoodoo::Services::Service
|
116
|
+
# comprised_of PurchaseInterface,
|
117
|
+
# RefundInterface
|
118
|
+
# end
|
119
|
+
#
|
120
|
+
# See this class's general Hoodoo::Services::Service documentation for
|
121
|
+
# more details.
|
122
|
+
#
|
123
|
+
def self.comprised_of( *classes )
|
124
|
+
|
125
|
+
# http://www.ruby-doc.org/core-2.2.3/Module.html#method-i-3C
|
126
|
+
#
|
127
|
+
classes.each do | klass |
|
128
|
+
unless klass < Hoodoo::Services::Interface
|
129
|
+
raise "Hoodoo::Services::Implementation::comprised_of expects Hoodoo::Services::Interface subclasses only - got '#{ klass }'"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Add the classes from this call to any given in a previous call.
|
134
|
+
|
135
|
+
@component_interfaces ||= []
|
136
|
+
@component_interfaces += classes
|
137
|
+
@component_interfaces.uniq!
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
end; end
|
@@ -0,0 +1,729 @@
|
|
1
|
+
########################################################################
|
2
|
+
# File:: session.rb
|
3
|
+
# (C):: Loyalty New Zealand 2015
|
4
|
+
#
|
5
|
+
# Purpose:: Container for information about the context in which a
|
6
|
+
# service is called.
|
7
|
+
# ----------------------------------------------------------------------
|
8
|
+
# 04-Feb-2015 (ADH): Created.
|
9
|
+
########################################################################
|
10
|
+
|
11
|
+
require 'ostruct'
|
12
|
+
require 'dalli'
|
13
|
+
|
14
|
+
module Hoodoo
|
15
|
+
module Services
|
16
|
+
|
17
|
+
# A container for functionality related to a context session.
|
18
|
+
#
|
19
|
+
class Session
|
20
|
+
|
21
|
+
# Time To Live: Number of seconds for which a session remains valid
|
22
|
+
# after being saved. Only applicable from the save time onwards in
|
23
|
+
# stores that support TTL such as Memcached - see #save_to_memcached.
|
24
|
+
#
|
25
|
+
TTL = 172800 # 48 hours
|
26
|
+
|
27
|
+
# A Session must have its own UUID.
|
28
|
+
#
|
29
|
+
attr_accessor :session_id
|
30
|
+
|
31
|
+
# A Session must always refer to a Caller instance by UUID.
|
32
|
+
#
|
33
|
+
attr_accessor :caller_id
|
34
|
+
|
35
|
+
# Callers can change; if so, related sessions must be invalidated.
|
36
|
+
# This must be achieved by keeping a version count on the Caller. A
|
37
|
+
# session is associated with a particular Caller version and if the
|
38
|
+
# version changes, associated sessions are flushed.
|
39
|
+
#
|
40
|
+
# If you _change_ a Caller version in a Session, you _really_ should
|
41
|
+
# call #save_to_memcached as soon as possible afterwards so that the
|
42
|
+
# change gets recognised in Memcached.
|
43
|
+
#
|
44
|
+
attr_accessor :caller_version
|
45
|
+
|
46
|
+
# An OpenStruct instance with session-creator defined key/value pairs
|
47
|
+
# that define the _identity_ of the session holder. This is usually
|
48
|
+
# related to a Caller resource instance - see also
|
49
|
+
# Hoodoo::Data::Resources::Caller - and will often contain a Caller
|
50
|
+
# resource instance's UUID, amongst other data.
|
51
|
+
#
|
52
|
+
# The object describes "who the session's owner is".
|
53
|
+
#
|
54
|
+
attr_reader :identity
|
55
|
+
|
56
|
+
# Set the identity data via a Hash of key/value pairs - see also
|
57
|
+
# #identity.
|
58
|
+
#
|
59
|
+
def identity=( hash )
|
60
|
+
@identity = OpenStruct.new( hash )
|
61
|
+
end
|
62
|
+
|
63
|
+
# A Hoodoo::Services::Permissions instance.
|
64
|
+
#
|
65
|
+
# The instance describes "what the session is allowed to do".
|
66
|
+
#
|
67
|
+
attr_accessor :permissions
|
68
|
+
|
69
|
+
# An OpenStruct instance with session-creator defined values that
|
70
|
+
# describe the _scoping_, that is, visbility of data, for the session.
|
71
|
+
# Its contents relate to service resource interface descriptions (see
|
72
|
+
# the DSL for Hoodoo::Services::Interface) and may be partially or
|
73
|
+
# entirely supported by the ActiveRecord finder extensions in
|
74
|
+
# Hoodoo::ActiveRecord::Finder.
|
75
|
+
#
|
76
|
+
# The object describes the "data that the session can 'see'".
|
77
|
+
#
|
78
|
+
attr_accessor :scoping
|
79
|
+
|
80
|
+
# Set the scoping data via a Hash of key/value pairs - see also
|
81
|
+
# #scoping.
|
82
|
+
#
|
83
|
+
def scoping=( hash )
|
84
|
+
@scoping = OpenStruct.new( hash )
|
85
|
+
end
|
86
|
+
|
87
|
+
# The creation date of this session instance as a Time instance in
|
88
|
+
# UTC.
|
89
|
+
#
|
90
|
+
attr_reader :created_at
|
91
|
+
|
92
|
+
# The expiry date for this session - the session should be considered
|
93
|
+
# expired at or after this date. Some session stores may support
|
94
|
+
# automatic expiry of session data, but there may be a small window
|
95
|
+
# between the expiry date passing and the store expiring the data; so
|
96
|
+
# always check the expiry.
|
97
|
+
#
|
98
|
+
# Only set when the session is saved (or loaded from a representation
|
99
|
+
# that includes an existing expiry date). See e.g.:
|
100
|
+
#
|
101
|
+
# * #save_to_memcached
|
102
|
+
#
|
103
|
+
# The value is a Time instance in UTC. If +nil+, the session has not
|
104
|
+
# yet been saved.
|
105
|
+
#
|
106
|
+
attr_reader :expires_at
|
107
|
+
|
108
|
+
# Connection IP address/port String for Memcached.
|
109
|
+
#
|
110
|
+
# If you are using Memcached for a session store, you can set the
|
111
|
+
# Memcached connection host either through this accessor, or via the
|
112
|
+
# object's constructor.
|
113
|
+
#
|
114
|
+
attr_accessor :memcached_host
|
115
|
+
|
116
|
+
# Create a new instance.
|
117
|
+
#
|
118
|
+
# +options+:: Optional Hash of options, described below.
|
119
|
+
#
|
120
|
+
# Options are:
|
121
|
+
#
|
122
|
+
# +session_id+:: UUID of this session. If unset, a new UUID is
|
123
|
+
# generated for you. You can read the UUID with
|
124
|
+
# the #session_id accessor method.
|
125
|
+
#
|
126
|
+
# +caller_id+:: UUID of the Caller instance associated with this
|
127
|
+
# session. This can be set either now or later, but
|
128
|
+
# the session cannot be saved without it.
|
129
|
+
#
|
130
|
+
# +caller_version+:: Version of the Caller instance; defaults to zero.
|
131
|
+
#
|
132
|
+
# +memcached_host+:: Host for Memcached connections; required if you
|
133
|
+
# want to use the #load_from_memcached! or
|
134
|
+
# #save_to_memcached methods.
|
135
|
+
#
|
136
|
+
def initialize( options = {} )
|
137
|
+
@created_at = Time.now.utc
|
138
|
+
|
139
|
+
self.session_id = options[ :session_id ] || Hoodoo::UUID.generate()
|
140
|
+
self.memcached_host = options[ :memcached_host ]
|
141
|
+
self.caller_id = options[ :caller_id ]
|
142
|
+
self.caller_version = options[ :caller_version ] || 0
|
143
|
+
end
|
144
|
+
|
145
|
+
# Save this session to Memcached, in a manner that will allow it to
|
146
|
+
# be loaded by #load_from_memcached! later.
|
147
|
+
#
|
148
|
+
# A session can only be saved if it has a Caller ID - see #caller_id=
|
149
|
+
# or the options hash passed to the constructor.
|
150
|
+
#
|
151
|
+
# The Hoodoo::Services::Session::TTL constant determines how long the
|
152
|
+
# key lives in Memcached.
|
153
|
+
#
|
154
|
+
# Returns a symbol:
|
155
|
+
#
|
156
|
+
# * +:ok+: The session data was saved OK and is valid. There was either
|
157
|
+
# a Caller record with an earlier or matching value in Memcached, or
|
158
|
+
# no preexisting record of the Caller.
|
159
|
+
#
|
160
|
+
# * +:outdated+: The session data could not be saved because an existing
|
161
|
+
# Caller record was found in Memcached with a _newer_ version than
|
162
|
+
# 'this' session, implying that the session is already outdated.
|
163
|
+
#
|
164
|
+
# * +:fail+: The session data could not be saved (Memcached problem).
|
165
|
+
#
|
166
|
+
def save_to_memcached
|
167
|
+
if self.caller_id.nil?
|
168
|
+
raise 'Hoodoo::Services::Session\#save_to_memcached: Cannot save this session as it has no assigned Caller UUID'
|
169
|
+
end
|
170
|
+
|
171
|
+
begin
|
172
|
+
mclient = self.class.connect_to_memcached( self.memcached_host() )
|
173
|
+
|
174
|
+
# Try to update the Caller version in Memcached using this
|
175
|
+
# Session's data. If this fails, the Caller version is out of
|
176
|
+
# date or we couldn't talk to Memcached. Either way, bail out.
|
177
|
+
|
178
|
+
result = update_caller_version_in_memcached( self.caller_id,
|
179
|
+
self.caller_version,
|
180
|
+
mclient )
|
181
|
+
|
182
|
+
return result unless result === :ok
|
183
|
+
|
184
|
+
# Must set this before saving, even though the delay between
|
185
|
+
# setting this value and Memcached actually saving the value
|
186
|
+
# with a TTL will mean that Memcached expires the key slightly
|
187
|
+
# *after* the time we record.
|
188
|
+
|
189
|
+
@expires_at = ( ::Time.now + TTL ).utc()
|
190
|
+
|
191
|
+
return :ok if mclient.set( self.session_id,
|
192
|
+
self.to_h(),
|
193
|
+
TTL )
|
194
|
+
|
195
|
+
rescue Exception => exception
|
196
|
+
|
197
|
+
# Log error and return nil if the session can't be parsed
|
198
|
+
#
|
199
|
+
Hoodoo::Services::Middleware.logger.warn(
|
200
|
+
'Hoodoo::Services::Session\#save_to_memcached: Session saving failed - connection fault or session corrupt',
|
201
|
+
exception.to_s
|
202
|
+
)
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
return :fail
|
207
|
+
end
|
208
|
+
|
209
|
+
# Load session data into this instance, overwriting instance values
|
210
|
+
# if the session is found. Raises an exception if there is a problem
|
211
|
+
# connecting to Memcached. A Memcached connection host must have been
|
212
|
+
# set through the constructor or #memcached_host accessor first.
|
213
|
+
#
|
214
|
+
# +sid+:: The Session UUID to look up.
|
215
|
+
#
|
216
|
+
# Returns a symbol:
|
217
|
+
#
|
218
|
+
# * +:ok+: The session data was loaded OK and is valid.
|
219
|
+
#
|
220
|
+
# * +:outdated+: The session data was loaded, but is outdated; either
|
221
|
+
# the session has expired, or its Caller version mismatches the
|
222
|
+
# associated stored Caller version in Memcached.
|
223
|
+
#
|
224
|
+
# * +:not_found+: The session was not found.
|
225
|
+
#
|
226
|
+
# * +:fail+: The session data could not be loaded (Memcached problem).
|
227
|
+
#
|
228
|
+
def load_from_memcached!( sid )
|
229
|
+
begin
|
230
|
+
mclient = self.class.connect_to_memcached( self.memcached_host() )
|
231
|
+
session_hash = mclient.get( sid )
|
232
|
+
|
233
|
+
if session_hash.nil?
|
234
|
+
return :not_found
|
235
|
+
else
|
236
|
+
self.from_h!( session_hash )
|
237
|
+
return :outdated if self.expired?
|
238
|
+
|
239
|
+
cv = load_caller_version_from_memcached( mclient, self.caller_id )
|
240
|
+
|
241
|
+
if cv == nil || cv > self.caller_version
|
242
|
+
return :outdated
|
243
|
+
else
|
244
|
+
return :ok
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
rescue Exception => exception
|
249
|
+
|
250
|
+
# Log error and return nil if the session can't be parsed
|
251
|
+
#
|
252
|
+
Hoodoo::Services::Middleware.logger.warn(
|
253
|
+
'Hoodoo::Services::Session\#load_from_memcached!: Session loading failed - connection fault or session corrupt',
|
254
|
+
exception.to_s
|
255
|
+
)
|
256
|
+
|
257
|
+
end
|
258
|
+
|
259
|
+
return :fail
|
260
|
+
end
|
261
|
+
|
262
|
+
# Update the version of a given Caller in Memcached. This is done
|
263
|
+
# automatically when Sessions are saved to Memcached, but if external
|
264
|
+
# code alters any Callers independently, it *MUST* call here to keep
|
265
|
+
# Memcached records up to date.
|
266
|
+
#
|
267
|
+
# If no cached version is in Memcached for the Caller, the method
|
268
|
+
# assumes it is being called for the first time for that Caller and
|
269
|
+
# writes the version it has to hand, rather than considering it an
|
270
|
+
# error condition.
|
271
|
+
#
|
272
|
+
# +cid+:: Caller UUID of the Caller record to update.
|
273
|
+
#
|
274
|
+
# +cv+:: New version to store (an Integer).
|
275
|
+
#
|
276
|
+
# +mclient+:: Optional Dalli::Client instance to use for talking to
|
277
|
+
# Memcached. If omitted, a connection is established for
|
278
|
+
# you. This is mostly an optimisation parameter, used by
|
279
|
+
# code which already has established a connection and
|
280
|
+
# wants to avoid creating another unnecessarily.
|
281
|
+
#
|
282
|
+
# Returns a Symbol:
|
283
|
+
#
|
284
|
+
# * +:ok+: The Caller record was updated successfully.
|
285
|
+
#
|
286
|
+
# * +:outdated+: The Caller was already present in Memcached with a
|
287
|
+
# _higher version_ than the one you wanted to save. Your own local
|
288
|
+
# Caller data must therefore already be out of date.
|
289
|
+
#
|
290
|
+
# * +:fail+: The Caller could not be updated (Memcached problem).
|
291
|
+
#
|
292
|
+
def update_caller_version_in_memcached( cid, cv, mclient = nil )
|
293
|
+
begin
|
294
|
+
mclient ||= self.class.connect_to_memcached( self.memcached_host() )
|
295
|
+
|
296
|
+
cached_version = load_caller_version_from_memcached( mclient, cid )
|
297
|
+
|
298
|
+
if cached_version != nil && cached_version > cv
|
299
|
+
return :outdated
|
300
|
+
elsif save_caller_version_to_memcached( mclient, cid, cv ) == true
|
301
|
+
return :ok
|
302
|
+
end
|
303
|
+
|
304
|
+
rescue Exception => exception
|
305
|
+
|
306
|
+
# Log error and return nil if the session can't be parsed
|
307
|
+
#
|
308
|
+
Hoodoo::Services::Middleware.logger.warn(
|
309
|
+
'Hoodoo::Services::Session\#update_caller_version_in_memcached: Client version update - connection fault or corrupt record',
|
310
|
+
exception.to_s
|
311
|
+
)
|
312
|
+
|
313
|
+
end
|
314
|
+
|
315
|
+
return :fail
|
316
|
+
end
|
317
|
+
|
318
|
+
# Delete this session from Memcached. The Session object is not
|
319
|
+
# modified.
|
320
|
+
#
|
321
|
+
# Returns a symbol:
|
322
|
+
#
|
323
|
+
# * +:ok+: The Session was deleted from Memcached successfully.
|
324
|
+
#
|
325
|
+
# * +:not_found+: This session was not found in Memcached.
|
326
|
+
#
|
327
|
+
# * +:fail+: The session data could not be deleted (Memcached problem).
|
328
|
+
#
|
329
|
+
def delete_from_memcached
|
330
|
+
begin
|
331
|
+
|
332
|
+
mclient = self.class.connect_to_memcached( self.memcached_host() )
|
333
|
+
|
334
|
+
if mclient.delete( self.session_id )
|
335
|
+
return :ok
|
336
|
+
else
|
337
|
+
return :not_found
|
338
|
+
end
|
339
|
+
|
340
|
+
rescue Exception => exception
|
341
|
+
|
342
|
+
# Log error and return nil if the session can't be parsed
|
343
|
+
#
|
344
|
+
Hoodoo::Services::Middleware.logger.warn(
|
345
|
+
'Hoodoo::Services::Session\#delete_from_memcached: Session delete - connection fault',
|
346
|
+
exception.to_s
|
347
|
+
)
|
348
|
+
|
349
|
+
return :fail
|
350
|
+
end
|
351
|
+
|
352
|
+
end
|
353
|
+
|
354
|
+
# Has this session expired? Only valid if an expiry date is set;
|
355
|
+
# see #expires_at.
|
356
|
+
#
|
357
|
+
# Returns +true+ if the session has expired, or +false+ if it has
|
358
|
+
# either not expired, or has no expiry date set yet.
|
359
|
+
#
|
360
|
+
def expired?
|
361
|
+
exp = self.expires_at()
|
362
|
+
now = Time.now.utc
|
363
|
+
|
364
|
+
return ! ( exp.nil? || now < exp )
|
365
|
+
end
|
366
|
+
|
367
|
+
# Represent this session's data as a Hash, for uses such as
|
368
|
+
# storage in Memcached or loading into another session instance.
|
369
|
+
# See also #from_h!.
|
370
|
+
#
|
371
|
+
def to_h
|
372
|
+
hash = {}
|
373
|
+
|
374
|
+
%w(
|
375
|
+
|
376
|
+
session_id
|
377
|
+
caller_id
|
378
|
+
caller_version
|
379
|
+
|
380
|
+
).each do | property |
|
381
|
+
value = self.send( property )
|
382
|
+
hash[ property ] = value unless value.nil?
|
383
|
+
end
|
384
|
+
|
385
|
+
%w(
|
386
|
+
|
387
|
+
created_at
|
388
|
+
expires_at
|
389
|
+
|
390
|
+
).each do | property |
|
391
|
+
value = self.send( property )
|
392
|
+
hash[ property ] = value.iso8601() unless value.nil?
|
393
|
+
end
|
394
|
+
|
395
|
+
%w(
|
396
|
+
|
397
|
+
identity
|
398
|
+
scoping
|
399
|
+
permissions
|
400
|
+
|
401
|
+
).each do | property |
|
402
|
+
value = self.send( property )
|
403
|
+
hash[ property ] = Hoodoo::Utilities.stringify( value.to_h() ) unless value.nil?
|
404
|
+
end
|
405
|
+
|
406
|
+
return hash
|
407
|
+
end
|
408
|
+
|
409
|
+
# Load session parameters from a given Hash, of the form set by
|
410
|
+
# #to_h.
|
411
|
+
#
|
412
|
+
# If appropriate Hash keys are present, will set any or all of
|
413
|
+
# #session_id, #identity, #scoping and #permissions.
|
414
|
+
#
|
415
|
+
def from_h!( hash )
|
416
|
+
hash = Hoodoo::Utilities.stringify( hash )
|
417
|
+
|
418
|
+
%w(
|
419
|
+
|
420
|
+
session_id
|
421
|
+
caller_id
|
422
|
+
caller_version
|
423
|
+
|
424
|
+
).each do | property |
|
425
|
+
value = hash[ property ]
|
426
|
+
self.send( "#{ property }=", value ) unless value.nil?
|
427
|
+
end
|
428
|
+
|
429
|
+
%w(
|
430
|
+
|
431
|
+
created_at
|
432
|
+
expires_at
|
433
|
+
|
434
|
+
).each do | property |
|
435
|
+
if hash.has_key?( property )
|
436
|
+
begin
|
437
|
+
instance_variable_set( "@#{ property }", Time.parse( hash[ property ] ).utc() )
|
438
|
+
rescue => e
|
439
|
+
# Invalid time given; keep existing date
|
440
|
+
end
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
%w(
|
445
|
+
|
446
|
+
identity
|
447
|
+
scoping
|
448
|
+
|
449
|
+
).each do | property |
|
450
|
+
value = hash[ property ]
|
451
|
+
self.send( "#{ property }=", OpenStruct.new( value ) ) unless value.nil?
|
452
|
+
end
|
453
|
+
|
454
|
+
value = hash[ 'permissions' ]
|
455
|
+
self.permissions = Hoodoo::Services::Permissions.new( value ) unless value.nil?
|
456
|
+
end
|
457
|
+
|
458
|
+
# Speciality interface usually only called by the middleware, or
|
459
|
+
# components closely related to the middleware.
|
460
|
+
#
|
461
|
+
# Takes this session and creates a copy for an inter-resource call
|
462
|
+
# which adds any additional parameters that the calling interface
|
463
|
+
# says it needs in order to complete the currently handled action.
|
464
|
+
#
|
465
|
+
# Through calling this method, the middleware implements the access
|
466
|
+
# permission functionality described by
|
467
|
+
# Hoodoo::Services::Interface#additional_permissions_for.
|
468
|
+
#
|
469
|
+
# +interaction+:: Hoodoo::Services::Middleware::Interaction instance
|
470
|
+
# describing the current interaction. This is for the
|
471
|
+
# request that a resource implementation *is handling*
|
472
|
+
# at the point it wants to make an inter-resource call
|
473
|
+
# - it is *not* data related to the *target* of that
|
474
|
+
# call.
|
475
|
+
#
|
476
|
+
# Returns:
|
477
|
+
#
|
478
|
+
# * Hoodoo::Services::Session instance if everything works OK; this
|
479
|
+
# may be the same as, or different from, the input session depending
|
480
|
+
# on whether or not there were any permissions that needed adding.
|
481
|
+
#
|
482
|
+
# * +false+ if the session can't be saved due to a mismatched caller
|
483
|
+
# version - the session must have become invalid _during_ handling.
|
484
|
+
#
|
485
|
+
# If the augmented session cannot be saved due to a Memcached problem,
|
486
|
+
# an exception is raised and the generic handler will turn this into a
|
487
|
+
# 500 response for the caller. At this time, we really can't do much
|
488
|
+
# better than that since failure to save the augmented session means
|
489
|
+
# the inter-resource call cannot proceed; it's an internal fault.
|
490
|
+
#
|
491
|
+
def augment_with_permissions_for( interaction )
|
492
|
+
|
493
|
+
# Set up some convenience variables
|
494
|
+
|
495
|
+
interface = interaction.target_interface
|
496
|
+
action = interaction.requested_action
|
497
|
+
|
498
|
+
# If there are no additional permissions for this action, just return
|
499
|
+
# the current session back again.
|
500
|
+
|
501
|
+
action = action.to_s()
|
502
|
+
additional_permissions = ( interface.additional_permissions() || {} )[ action ]
|
503
|
+
|
504
|
+
return self if additional_permissions.nil?
|
505
|
+
|
506
|
+
# Otherwise, duplicate the session and its permissions (or generate
|
507
|
+
# defaults) and merge the additional permissions.
|
508
|
+
|
509
|
+
local_session = self.dup()
|
510
|
+
local_permissions = self.permissions ? self.permissions.dup() : Hoodoo::Services::Permissions.new
|
511
|
+
|
512
|
+
local_permissions.merge!( additional_permissions.to_h() )
|
513
|
+
|
514
|
+
# Make sure the new session has its own ID and set the updated
|
515
|
+
# permissions. Then try to save it and return the result.
|
516
|
+
|
517
|
+
local_session.session_id = Hoodoo::UUID.generate()
|
518
|
+
local_session.permissions = local_permissions
|
519
|
+
|
520
|
+
case local_session.save_to_memcached()
|
521
|
+
when :ok
|
522
|
+
return local_session
|
523
|
+
|
524
|
+
when :outdated
|
525
|
+
# Caller version mismatch; original session is now outdated and invalid
|
526
|
+
return false
|
527
|
+
|
528
|
+
else # Couldn't save it
|
529
|
+
raise "Unable to create interim session for inter-resource call from #{ interface.resource } / #{ action }"
|
530
|
+
end
|
531
|
+
end
|
532
|
+
|
533
|
+
private
|
534
|
+
|
535
|
+
# Connect to the Memcached server. Returns a new Dalli client
|
536
|
+
# instance. Raises an exception if no connection can be established.
|
537
|
+
#
|
538
|
+
# In test environments, returns a MockDalliClient instance.
|
539
|
+
#
|
540
|
+
# +host+:: Connection host (IP "address:port" String) for Memcached.
|
541
|
+
#
|
542
|
+
def self.connect_to_memcached( host )
|
543
|
+
|
544
|
+
if Hoodoo::Services::Middleware.environment.test? && MockDalliClient.bypass? == false
|
545
|
+
return MockDalliClient.new
|
546
|
+
end
|
547
|
+
|
548
|
+
if host.nil? || host.empty?
|
549
|
+
raise 'Hoodoo::Services::Session.connect_to_memcached: The Memcached connection host data is nil or empty'
|
550
|
+
end
|
551
|
+
|
552
|
+
stats = nil
|
553
|
+
mclient = nil
|
554
|
+
|
555
|
+
begin
|
556
|
+
@@dalli_clients ||= {}
|
557
|
+
@@dalli_clients[ host ] ||= ::Dalli::Client.new(
|
558
|
+
host,
|
559
|
+
{
|
560
|
+
:compress => false,
|
561
|
+
:serializer => JSON,
|
562
|
+
:namespace => :nz_co_loyalty_hoodoo_session_
|
563
|
+
}
|
564
|
+
)
|
565
|
+
|
566
|
+
stats = @@dalli_clients[ host ].stats()
|
567
|
+
|
568
|
+
rescue Exception => e
|
569
|
+
stats = nil
|
570
|
+
|
571
|
+
end
|
572
|
+
|
573
|
+
if stats.nil?
|
574
|
+
raise "Hoodoo::Services::Session.connect_to_memcached: Cannot connect to Memcached at '#{ host }'"
|
575
|
+
else
|
576
|
+
return @@dalli_clients[ host ]
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
580
|
+
# Try to read a cached Caller version from Memcached. Returns the
|
581
|
+
# cached version if available, or +nil+ if the record isn't found.
|
582
|
+
#
|
583
|
+
# May raise an exception for e.g. Memcached failures, via Dalli.
|
584
|
+
#
|
585
|
+
# TODO: As a temporary measure, compatibility bridge code in Authsome
|
586
|
+
# may call this private interface via ".send". Until that is
|
587
|
+
# decommissioned, the API shouldn't be changed without updating
|
588
|
+
# Authsome too.
|
589
|
+
#
|
590
|
+
# +mclient+:: A Dalli::Client instance to use for talking to
|
591
|
+
# Memcached.
|
592
|
+
#
|
593
|
+
# +cid+:: Caller UUID of interest.
|
594
|
+
#
|
595
|
+
def load_caller_version_from_memcached( mclient, cid )
|
596
|
+
version_hash = mclient.get( cid )
|
597
|
+
return version_hash.nil? ? nil : version_hash[ 'version' ]
|
598
|
+
end
|
599
|
+
|
600
|
+
# Save the Caller version for a given Caller ID to Memcached.
|
601
|
+
# Returns +true+ if successful, else +false+.
|
602
|
+
#
|
603
|
+
# May raise an exception for e.g. Memcached failures, via Dalli.
|
604
|
+
#
|
605
|
+
# Note that any existing record for the given Caller, if there
|
606
|
+
# is one, is unconditionally overwritten.
|
607
|
+
#
|
608
|
+
# +mclient+:: A Dalli::Client instance to use for talking to
|
609
|
+
# Memcached.
|
610
|
+
#
|
611
|
+
# +cid+:: Caller UUID of interest.
|
612
|
+
#
|
613
|
+
# +cv+:: Version to save for that Caller UUID.
|
614
|
+
#
|
615
|
+
def save_caller_version_to_memcached( mclient, cid, cv )
|
616
|
+
return !! mclient.set(
|
617
|
+
cid,
|
618
|
+
{ 'version' => cv }
|
619
|
+
)
|
620
|
+
end
|
621
|
+
|
622
|
+
# Mock known uses of Dalli::Client with test implementations.
|
623
|
+
# Use explicitly, or as an RSpec implicit mock via something like
|
624
|
+
# this:
|
625
|
+
#
|
626
|
+
# allow( Dalli::Client ).to receive( :new ).and_return( Hoodoo::Services::Session::MockDalliClient.new )
|
627
|
+
#
|
628
|
+
# ...whenever you need to stub out real Memcached. You will
|
629
|
+
# probably want to add:
|
630
|
+
#
|
631
|
+
# before :all do # (or ":each")
|
632
|
+
# Hoodoo::Services::Session::MockDalliClient.reset()
|
633
|
+
# end
|
634
|
+
#
|
635
|
+
# ...to "clean out Memcached" before or between tests. You can
|
636
|
+
# check the contents of mock Memcached by examining ::store's
|
637
|
+
# hash of data.
|
638
|
+
#
|
639
|
+
class MockDalliClient
|
640
|
+
@@store = {}
|
641
|
+
|
642
|
+
# For test analysis, return the hash of 'memcached' mock data.
|
643
|
+
#
|
644
|
+
# Entries are referenced by the key you used to originally
|
645
|
+
# store them; values are hashes with ":expires_at" giving an
|
646
|
+
# expiry time or "nil" and ":value" giving your stored value.
|
647
|
+
#
|
648
|
+
def self.store
|
649
|
+
@@store
|
650
|
+
end
|
651
|
+
|
652
|
+
# Wipe out all saved data.
|
653
|
+
#
|
654
|
+
def self.reset
|
655
|
+
@@store = {}
|
656
|
+
end
|
657
|
+
|
658
|
+
# Pass +true+ to bypass the mock client (subject to the caller
|
659
|
+
# reading ::bypass?) to e.g. get test code coverage on real
|
660
|
+
# Memcached. Pass +false+ otherwise.
|
661
|
+
#
|
662
|
+
def self.bypass( bypass_boolean )
|
663
|
+
@@bypass = bypass_boolean
|
664
|
+
end
|
665
|
+
|
666
|
+
@@bypass = false
|
667
|
+
|
668
|
+
# If +true+, bypass this class and use real Dalli::Client; else
|
669
|
+
# don't. Default return value is +false+.
|
670
|
+
#
|
671
|
+
def self.bypass?
|
672
|
+
@@bypass
|
673
|
+
end
|
674
|
+
|
675
|
+
# Get the data stored under the given key. Returns +nil+ if
|
676
|
+
# not found / expired.
|
677
|
+
#
|
678
|
+
# +key+:: Key to look up (see #set).
|
679
|
+
#
|
680
|
+
def get( key )
|
681
|
+
data = @@store[ key ]
|
682
|
+
return nil if data.nil?
|
683
|
+
|
684
|
+
expires_at = data[ :expires_at ]
|
685
|
+
return nil unless expires_at.nil? || Time.now < expires_at
|
686
|
+
|
687
|
+
return data[ :value ]
|
688
|
+
end
|
689
|
+
|
690
|
+
# Set data for a given key.
|
691
|
+
#
|
692
|
+
# +key+:: Key under which to store data.
|
693
|
+
#
|
694
|
+
# +value+:: Data to store.
|
695
|
+
#
|
696
|
+
# +ttl+:: (Optional) time-to-live ('live' as in living, not as in
|
697
|
+
# 'live TV') - a value in seconds, after which the data is
|
698
|
+
# considered expired. If omitted, the data does not expire.
|
699
|
+
#
|
700
|
+
def set( key, value, ttl = nil )
|
701
|
+
data = {
|
702
|
+
:expires_at => ttl.nil? ? nil : Time.now.utc + ttl,
|
703
|
+
:value => value
|
704
|
+
}
|
705
|
+
|
706
|
+
@@store[ key ] = data
|
707
|
+
true
|
708
|
+
end
|
709
|
+
|
710
|
+
# Remove data for the given key.
|
711
|
+
#
|
712
|
+
def delete( key )
|
713
|
+
if @@store.has_key?( key )
|
714
|
+
@@store.delete( key )
|
715
|
+
true
|
716
|
+
else
|
717
|
+
false
|
718
|
+
end
|
719
|
+
end
|
720
|
+
|
721
|
+
# Mock 'stats' health check.
|
722
|
+
#
|
723
|
+
def stats
|
724
|
+
true
|
725
|
+
end
|
726
|
+
end
|
727
|
+
end
|
728
|
+
end
|
729
|
+
end
|