cybercoach 0.2.0 → 0.3.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +45 -0
  3. data/.rspec +2 -0
  4. data/CHANGELOG.md +8 -0
  5. data/Gemfile +23 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +31 -0
  8. data/Rakefile +2 -0
  9. data/cybercoach.gemspec +27 -0
  10. data/lib/cybercoach/abstract_resource.rb +162 -0
  11. data/lib/cybercoach/entry.rb +166 -0
  12. data/lib/cybercoach/format_not_supported_error.rb +7 -0
  13. data/lib/cybercoach/http_error.rb +27 -0
  14. data/lib/cybercoach/pageable.rb +38 -0
  15. data/lib/cybercoach/partnership.rb +151 -0
  16. data/lib/cybercoach/post_createable.rb +49 -0
  17. data/lib/cybercoach/privacy_level.rb +21 -0
  18. data/lib/cybercoach/put_createable.rb +48 -0
  19. data/lib/cybercoach/resource.rb +94 -0
  20. data/lib/cybercoach/resource_page.rb +191 -0
  21. data/lib/cybercoach/settings.rb +16 -0
  22. data/lib/cybercoach/sport.rb +104 -0
  23. data/lib/cybercoach/subclass_responsibility_error.rb +7 -0
  24. data/lib/cybercoach/subscription.rb +145 -0
  25. data/lib/cybercoach/user.rb +159 -0
  26. data/lib/cybercoach/version.rb +10 -0
  27. data/lib/cybercoach.rb +19 -0
  28. data/spec/lib/cybercoach/entry_spec.rb +95 -0
  29. data/spec/lib/cybercoach/partnership_spec.rb +79 -0
  30. data/spec/lib/cybercoach/privacy_level_spec.rb +15 -0
  31. data/spec/lib/cybercoach/resource_helper.rb +11 -0
  32. data/spec/lib/cybercoach/resource_page_spec.rb +44 -0
  33. data/spec/lib/cybercoach/settings_spec.rb +10 -0
  34. data/spec/lib/cybercoach/sport_spec.rb +29 -0
  35. data/spec/lib/cybercoach/subscription_spec.rb +129 -0
  36. data/spec/lib/cybercoach/user_helper.rb +22 -0
  37. data/spec/lib/cybercoach/user_spec.rb +82 -0
  38. data/spec/lib/cybercoach_helper.rb +2 -0
  39. data/spec/lib/integration_spec.rb +56 -0
  40. data/spec/spec_helper.rb +19 -0
  41. metadata +56 -4
@@ -0,0 +1,151 @@
1
+ module CyberCoach
2
+ #
3
+ # A Partnership consists of two Users, which participate in different Sport
4
+ # with Subscriptions, to which Entries are submitted.
5
+ #
6
+ class Partnership < Resource
7
+ #
8
+ # It is pageable.
9
+ #
10
+ include Pageable
11
+
12
+ #
13
+ # It is creatable by PUT.
14
+ #
15
+ include PutCreateable
16
+
17
+ #
18
+ # :attr: proposer
19
+ # The User who proposed it.
20
+ #
21
+
22
+ #
23
+ # :attr: proposed
24
+ # The User it is proposed to.
25
+ #
26
+
27
+ #
28
+ # :attr: subscriptions
29
+ # The Subscriptions.
30
+ #
31
+
32
+ #
33
+ # :attr: confirmed_by_proposer
34
+ # True if the proposing User has confirmed it, false otherwise.
35
+ #
36
+
37
+ #
38
+ # :attr: confirmed_by_proposed
39
+ # True if the proposed User has confirmed it, false otherwise.
40
+ #
41
+
42
+ #
43
+ # :attr: privacy_level
44
+ # The privacy level, see PrivacyLevel constants.
45
+ #
46
+
47
+ attr_accessor :proposer,
48
+ :proposed,
49
+ :subscriptions,
50
+ :confirmed_by_proposer,
51
+ :confirmed_by_proposed,
52
+ :privacy_level
53
+
54
+ #
55
+ # :category: Serialization
56
+ #
57
+ # Creates itself from a serializable representation, which only contains
58
+ # simple data types.
59
+ # serializable:: A hash with the keys:
60
+ # * uri:: The URI.
61
+ # * id:: The identifier.
62
+ # * user1:: A User serializable of the proposer.
63
+ # * user2:: A User serializable of the proposed.
64
+ # * subscriptions:: Subscription serializables.
65
+ # * userconfirmed1:: True if the proposing User has confirmed it, false otherwise.
66
+ # * userconfirmed2:: True if the proposed User has confirmed it, false otherwise.
67
+ # * publicvisible:: The privacy level, see PrivacyLevel constants.
68
+ #
69
+ def from_serializable(serializable)
70
+ super(serializable)
71
+ @proposer = nil
72
+ unless serializable['user1'].nil?
73
+ @proposer = User.new
74
+ @proposer.from_serializable(serializable['user1'])
75
+ end
76
+ @proposed = nil
77
+ unless serializable['user2'].nil?
78
+ @proposed = User.new
79
+ @proposed.from_serializable(serializable['user2'])
80
+ end
81
+ @subscriptions = []
82
+ unless serializable['subscriptions'].nil?
83
+ @subscriptions = serializable['subscriptions'].map do
84
+ |subscription_serializable|
85
+ subscription = Subscription.new
86
+ subscription.from_serializable(subscription_serializable)
87
+ subscription
88
+ end
89
+ end
90
+ @confirmed_by_proposer = serializable['userconfirmed1']
91
+ @confirmed_by_proposed = serializable['userconfirmed2']
92
+ @privacy_level = serializable['publicvisible']
93
+ end
94
+
95
+ #
96
+ # :category: Serialization
97
+ #
98
+ # Returns a serializable representation, which only contains simple data
99
+ # types.
100
+ # The hash has the keys:
101
+ # * uri:: The URI.
102
+ # * id:: The identifier.
103
+ # * user1:: A User serializable of the proposer.
104
+ # * user2:: A User serializable of the proposed.
105
+ # * publicvisible:: The privacy level, see PrivacyLevel constants.
106
+ #
107
+ def to_serializable
108
+ serializable = super
109
+ unless @proposer.nil?
110
+ serializable['user1'] = @proposer.to_serializable
111
+ end
112
+ unless @proposed.nil?
113
+ serializable['user2'] = @proposed.to_serializable
114
+ end
115
+ serializable['publicvisible'] = @privacy_level
116
+ serializable
117
+ end
118
+
119
+ #
120
+ # :category: Configuration
121
+ #
122
+ # Returns 'partnership'.
123
+ #
124
+ def singular_name
125
+ 'partnership'
126
+ end
127
+
128
+ #
129
+ # :category: Configuration
130
+ #
131
+ # Returns 'partnerships'.
132
+ #
133
+ def plural_name
134
+ 'partnerships'
135
+ end
136
+
137
+ protected
138
+
139
+ #
140
+ # :category: Invalidation
141
+ #
142
+ # Sets the uri to the base uri and the proposer's and proposed's username if
143
+ # neither of them is nil.
144
+ #
145
+ def invalidate_uri
146
+ unless @proposer.nil? || @proposed.nil?
147
+ @uri = "#{resource_base_uri}#{@proposer.username};#{@proposed.username}/"
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,49 @@
1
+ module CyberCoach
2
+ #
3
+ # Mixin for a resource that gets its URI assigned from the server.
4
+ # Include it in a class to use it.
5
+ #
6
+ module PostCreateable
7
+ #
8
+ # Installs class and instance methods in the class it is included in.
9
+ #
10
+ def self.included(base)
11
+ base.extend ClassMethods
12
+ base.send :include, InstanceMethods
13
+ end
14
+
15
+ #
16
+ # The class methods to install.
17
+ #
18
+ module ClassMethods
19
+ end
20
+
21
+ #
22
+ # The instance methods to install.
23
+ #
24
+ module InstanceMethods
25
+ #
26
+ # :category: CRUD
27
+ #
28
+ # Creates it.
29
+ # Gets the URI from the response and reads itself again.
30
+ # Raises HttpError if the request is unsuccessful.
31
+ # options:: A hash of options to send with the request.
32
+ #
33
+ def create(options = {})
34
+ invalidate_uri
35
+ invalidate_options
36
+ options = @options.merge(options).merge(
37
+ body: serialize
38
+ )
39
+ response = self.class.post(resource_base_uri, options)
40
+ if response.success?
41
+ @uri = response.headers['location']
42
+ read(options)
43
+ else
44
+ raise HttpError.new(response.response)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,21 @@
1
+ module CyberCoach
2
+ #
3
+ # The CyberCoach privacy levels.
4
+ #
5
+ module PrivacyLevel
6
+ #
7
+ # Only the owner of the resource can access its properties.
8
+ #
9
+ OWNER = 0
10
+
11
+ #
12
+ # Only registered users can access the resource's properties.
13
+ #
14
+ REGISTERED_USER = 1
15
+
16
+ #
17
+ # Everybody can access the resource's properties.
18
+ #
19
+ EVERYBODY = 2
20
+ end
21
+ end
@@ -0,0 +1,48 @@
1
+ module CyberCoach
2
+ #
3
+ # Mixin for a resource that gets its URI assigned from the client.
4
+ # Include it in a class to use it.
5
+ #
6
+ module PutCreateable
7
+ #
8
+ # Installs class and instance methods in the class it is included in.
9
+ #
10
+ def self.included(base)
11
+ base.extend ClassMethods
12
+ base.send :include, InstanceMethods
13
+ end
14
+
15
+ #
16
+ # The class methods to install.
17
+ #
18
+ module ClassMethods
19
+ end
20
+
21
+ #
22
+ # The instance methods to install.
23
+ #
24
+ module InstanceMethods
25
+ #
26
+ # :category: CRUD
27
+ #
28
+ # Creates it.
29
+ # Reads itself from the response.
30
+ # Raises HttpError if the request is unsuccessful.
31
+ # options:: A hash of options to send with the request.
32
+ #
33
+ def create(options = {})
34
+ invalidate_uri
35
+ invalidate_options
36
+ options = @options.merge(options).merge(
37
+ body: serialize
38
+ )
39
+ response = self.class.put(@uri, options)
40
+ if response.success?
41
+ deserialize(response)
42
+ else
43
+ raise HttpError.new(response.response)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,94 @@
1
+ module CyberCoach
2
+ #
3
+ # A Resource can be created, read, updated and deleted.
4
+ #
5
+ class Resource < AbstractResource
6
+ #
7
+ # The identifier.
8
+ #
9
+ attr_accessor :id
10
+
11
+ #
12
+ # :category: CRUD
13
+ #
14
+ # Creates it.
15
+ # Must be overridden in a subclass.
16
+ # May use PutCreateable or PostCreateable.
17
+ # options:: A hash of options to send with the request.
18
+ #
19
+ def create(_options = {})
20
+ raise SubclassResponsibilityError.new
21
+ end
22
+
23
+ #
24
+ # :category: CRUD
25
+ #
26
+ # Updates it.
27
+ # Reads itself from the response.
28
+ # Raises HttpError if the request is unsuccessful.
29
+ # options:: A hash of options to send with the request.
30
+ #
31
+ def update(options = {})
32
+ invalidate_uri
33
+ invalidate_options
34
+ options = @options.merge(options).merge(
35
+ body: serialize
36
+ )
37
+ response = self.class.put(@uri, options)
38
+ if response.success?
39
+ deserialize(response)
40
+ else
41
+ raise HttpError.new(response.response)
42
+ end
43
+ end
44
+
45
+ #
46
+ # :category: CRUD
47
+ #
48
+ # Deletes it.
49
+ # Reads itself from the response.
50
+ # Raises HttpError if the request is unsuccessful.
51
+ # options:: A hash of options to send with the request.
52
+ #
53
+ def delete(options = {})
54
+ invalidate_uri
55
+ invalidate_options
56
+ options = @options.merge(options)
57
+ response = self.class.delete(@uri, options)
58
+ if response.success?
59
+ deserialize(response)
60
+ else
61
+ raise HttpError.new(response.response)
62
+ end
63
+ end
64
+
65
+ #
66
+ # :category: Serialization
67
+ #
68
+ # Creates itself from a serializable representation, which only contains
69
+ # simple data types.
70
+ # serializable:: A hash with the keys:
71
+ # * uri:: The URI.
72
+ # * id:: The identifier.
73
+ #
74
+ def from_serializable(serializable)
75
+ super(serializable)
76
+ @id = serializable['id']
77
+ end
78
+
79
+ #
80
+ # :category: Serialization
81
+ #
82
+ # Returns a serializable representation, which only contains simple data
83
+ # types.
84
+ # The hash has the keys:
85
+ # * uri:: The URI.
86
+ # * id:: The identifier.
87
+ #
88
+ def to_serializable
89
+ serializable = super
90
+ serializable['id'] = @id
91
+ serializable
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,191 @@
1
+ module CyberCoach
2
+ #
3
+ # A ResourcePage can be used to navigate through many resources of a type.
4
+ #
5
+ class ResourcePage < AbstractResource
6
+ #
7
+ # Raised when trying to get the next page from the last page.
8
+ #
9
+ class NoNextPageError < StandardError
10
+ end
11
+
12
+ #
13
+ # Raised when trying to get the previous page from the first page.
14
+ #
15
+ class NoPreviousPageError < StandardError
16
+ end
17
+
18
+ #
19
+ # :attr: start
20
+ # The start index.
21
+ #
22
+
23
+ #
24
+ # :attr: end
25
+ # The end index.
26
+ #
27
+
28
+ #
29
+ # :attr: size
30
+ # The size.
31
+ #
32
+
33
+ #
34
+ # :attr: available
35
+ # The resources available.
36
+ #
37
+
38
+ #
39
+ # :attr: type
40
+ # The class of the resource to page.
41
+ #
42
+
43
+ #
44
+ # :attr: resources
45
+ # The resources.
46
+ #
47
+
48
+ attr_accessor :start, :end, :size, :available, :type, :resources
49
+
50
+ #
51
+ # Create a ResourcePage of the specified type.
52
+ # type:: The class of resource to page.
53
+ #
54
+ def initialize(type)
55
+ super()
56
+ @type = type
57
+ end
58
+
59
+ #
60
+ # :category: CRUD
61
+ #
62
+ # Returns the next page.
63
+ # Raises NoNextPageError if the is none.
64
+ # options:: A hash of options to send with the request.
65
+ #
66
+ def next(options = {})
67
+ if @next.nil?
68
+ raise NoNextPageError.new
69
+ end
70
+ invalidate_options
71
+ options = @options.merge(options)
72
+ response = self.class.get(@next['href'], options)
73
+ if response.success?
74
+ page = self.class.new(@type)
75
+ page.deserialize(response)
76
+ page
77
+ else
78
+ raise HttpError.new(response.response)
79
+ end
80
+ end
81
+
82
+ #
83
+ # :category: CRUD
84
+ #
85
+ # Returns the previous page.
86
+ # Raises NoPreviousPageError if the is none.
87
+ # options:: A hash of options to send with the request.
88
+ #
89
+ def previous(options = {})
90
+ if @previous.nil?
91
+ raise NoPreviousPageError.new
92
+ end
93
+ invalidate_options
94
+ options = @options.merge(options)
95
+ response = self.class.get(@previous['href'], options)
96
+ if response.success?
97
+ page = self.class.new(@type)
98
+ page.deserialize(response)
99
+ page
100
+ else
101
+ raise HttpError.new(response.response)
102
+ end
103
+ end
104
+
105
+ #
106
+ # :category: Serialization
107
+ #
108
+ # Creates itself from a serializable representation, which only contains
109
+ # simple data types.
110
+ # serializable:: A hash with the keys:
111
+ # * uri:: The URI.
112
+ # * start:: The start index.
113
+ # * end:: The end index.
114
+ # * size:: The size.
115
+ # * available:: The resources available.
116
+ # * links:: Links as hashes with the keys:
117
+ # * description:: May be 'next' or 'previous'.
118
+ # * href:: The URI of the referenced page.
119
+ # * @plural_name:: Items mapped to the plural name of the @type.
120
+ #
121
+ def from_serializable(serializable)
122
+ super(serializable)
123
+ @start = serializable['start']
124
+ @end = serializable['end']
125
+ @size = serializable['size']
126
+ @available = serializable['available']
127
+ if serializable['links'].nil?
128
+ @next = nil
129
+ @previous = nil
130
+ else
131
+ @next = serializable['links'].find { |link| link['description'] == 'next' }
132
+ @previous = serializable['links'].find { |link| link['description'] == 'previous' }
133
+ end
134
+ @resources = serializable[plural_name].map do |resource_serializable|
135
+ resource = @type.new
136
+ resource.from_serializable(resource_serializable)
137
+ resource
138
+ end
139
+ end
140
+
141
+ #
142
+ # :category: Configuration
143
+ #
144
+ # Return the singular name of the type.
145
+ #
146
+ def singular_name
147
+ @type.new.singular_name
148
+ end
149
+
150
+ #
151
+ # :category: Configuration
152
+ #
153
+ # Return the plural name of the type.
154
+ #
155
+ def plural_name
156
+ @type.new.plural_name
157
+ end
158
+
159
+ #
160
+ # :category: Configuration
161
+ #
162
+ # Return the resource's base URI of the type.
163
+ #
164
+ def resource_base_uri
165
+ @type.new.resource_base_uri
166
+ end
167
+
168
+ protected
169
+
170
+ #
171
+ # :category: Invalidation
172
+ #
173
+ # Sets the start and size attributes as query parameters.
174
+ #
175
+ def invalidate_options
176
+ @options[:query] = {
177
+ start: @start,
178
+ size: @size
179
+ }
180
+ end
181
+
182
+ #
183
+ # :category: Invalidation
184
+ #
185
+ # Sets the URI to the types' base URI.
186
+ #
187
+ def invalidate_uri
188
+ @uri = resource_base_uri
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,16 @@
1
+ module CyberCoach
2
+ #
3
+ # Settings to access CyberCoach.
4
+ #
5
+ class Settings
6
+ #
7
+ # The URI to the server.
8
+ #
9
+ SERVER_URI = 'diufvm31.unifr.ch:8090'
10
+
11
+ #
12
+ # The URI to the resources, relative to the server URI.
13
+ #
14
+ BASE_URI = '/CyberCoachServer/resources'
15
+ end
16
+ end
@@ -0,0 +1,104 @@
1
+ module CyberCoach
2
+ #
3
+ # A Sport has Subscriptions to which Entries are submitted.
4
+ #
5
+ class Sport < Resource
6
+ #
7
+ # It is pageable.
8
+ #
9
+ include Pageable
10
+
11
+ #
12
+ # :attr: name
13
+ # The name.
14
+ #
15
+
16
+ #
17
+ # :attr: description
18
+ # The description.
19
+ #
20
+
21
+ #
22
+ # :attr: subscriptions
23
+ # The Subscriptions to it.
24
+ #
25
+
26
+ attr_accessor :name, :description, :subscriptions
27
+
28
+ #
29
+ # :category: Serialization
30
+ #
31
+ # Creates itself from a serializable representation, which only contains
32
+ # simple data types.
33
+ # serializable:: A hash with the keys:
34
+ # * uri:: The URI.
35
+ # * id:: The identifier.
36
+ # * name:: The name.
37
+ # * description:: The description.
38
+ # * subscriptions:: Subscription serializables.
39
+ #
40
+ def from_serializable(serializable)
41
+ super(serializable)
42
+ @name = serializable['name']
43
+ @description = serializable['description']
44
+ @subscriptions = []
45
+ unless serializable['subscriptions'].nil?
46
+ @subscriptions = serializable['subscriptions'].map do
47
+ |subscription_serializable|
48
+ subscription = Subscription.new
49
+ subscription.from_serializable(subscription_serializable)
50
+ subscription
51
+ end
52
+ end
53
+ end
54
+
55
+ #
56
+ # :category: Serialization
57
+ #
58
+ # Returns a serializable representation, which only contains simple data
59
+ # types.
60
+ # The hash has the keys:
61
+ # * uri:: The URI.
62
+ # * id:: The identifier.
63
+ # * name:: The name.
64
+ # * description:: The description.
65
+ #
66
+ def to_serializable
67
+ serializable = super
68
+ serializable['name'] = @name
69
+ serializable['description'] = @description
70
+ serializable
71
+ end
72
+
73
+ #
74
+ # :category: Configuration
75
+ #
76
+ # Returns 'sport'.
77
+ #
78
+ def singular_name
79
+ 'sport'
80
+ end
81
+
82
+ #
83
+ # :category: Configuration
84
+ #
85
+ # Returns 'sports'.
86
+ #
87
+ def plural_name
88
+ 'sports'
89
+ end
90
+
91
+ protected
92
+
93
+ #
94
+ # :category: Invalidation
95
+ #
96
+ # Sets the uri to the base uri and the name.
97
+ #
98
+ def invalidate_uri
99
+ unless @name.nil?
100
+ @uri = "#{resource_base_uri}#{@name}/"
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,7 @@
1
+ module CyberCoach
2
+ #
3
+ # Raised when a method is abstract and should be implemented in a subclass.
4
+ #
5
+ class SubclassResponsibilityError < RuntimeError
6
+ end
7
+ end