flox 0.1.2 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7994c4b7087c3bbdd2202eb9078e9a3d718059a1
4
- data.tar.gz: 84ca494ed9b601e7a4b5287b9075a6af6ef452cf
3
+ metadata.gz: 8c588236a3e6206950c5d693305514fe02335a0e
4
+ data.tar.gz: 9344c387fa0f1ff1a6060b8bff84cb1fdcdc485d
5
5
  SHA512:
6
- metadata.gz: 866c2b3e215e19adf4751313b126e792efdb747b93fc76bbe6c809373682a000e3be344152587ffee7ba966a03253d70dac67eca4264b88491698232cf1444d2
7
- data.tar.gz: 0f9e469c3487c31b559675d24a893c34260742d24f265733e926f9c4fd91c714560807740209221bd0f4d55174a05d23027c10faf244a5d99511105713cb8674
6
+ metadata.gz: ade435b67774267ec23f818078a756525a542ae1bc7ca7205d4a80e22909eda3e1d45121935a7ee430bdf91dbf4ecb5495e2f5d4f0238de7fd747febe2ae3af8
7
+ data.tar.gz: cd5754f39054ebf0b8d9cbce960b3c5c6850ee931ff92b3ddc35d58e94d83d2af3a9e38d0b8b98332ff5dbec9c66542691e4edd29e5421b19b03fa0ecafed720
data/README.md CHANGED
@@ -53,7 +53,7 @@ We're done with the preparations! Now let's look at some of the things you can d
53
53
  entity = flox.load_entity('entity-type', 'entity-id') # => Flox::Entity
54
54
 
55
55
  # modify it via the []-operator
56
- entity['myProperty'] = 'something'
56
+ entity[:myProperty] = 'something'
57
57
 
58
58
  # save or delete at will
59
59
  flox.save_entity(entity)
data/bin/flox CHANGED
@@ -32,7 +32,7 @@ class Worker
32
32
  FileUtils.mkdir_p(destination)
33
33
 
34
34
  print "Loading log IDs (this could take a moment) ... "
35
- log_ids = flox.load_log_ids(query, limit)
35
+ log_ids = flox.find_log_ids(query, limit)
36
36
  puts "found #{log_ids.length} logs."
37
37
  log_ids.each do |log_id|
38
38
  remote_path = "logs/#{log_id}"
@@ -8,9 +8,6 @@
8
8
  # of all players.
9
9
  class Flox
10
10
 
11
- # The main Error class, all Exception classes inherit from this class.
12
- class Error < StandardError; end
13
-
14
11
  # The URL where the Flox servers are found.
15
12
  DEFAULT_URL = "https://www.flox.cc"
16
13
 
@@ -49,7 +46,7 @@ class Flox
49
46
  # @private
50
47
  def login(auth_type, auth_id=nil, auth_token=nil)
51
48
  data = service.login(auth_type, auth_id, auth_token)
52
- @current_player = Player.new(data['id'], data['entity'])
49
+ @current_player = Player.new(data[:id], data[:entity])
53
50
  end
54
51
 
55
52
  # Loads an entity with a certain type and id from the server.
@@ -58,11 +55,7 @@ class Flox
58
55
  def load_entity(type, id)
59
56
  path = entity_path(type, id)
60
57
  data = service.get(path)
61
- if (type == '.player')
62
- Player.new(id, data)
63
- else
64
- Entity.new(type, id, data)
65
- end
58
+ create_entity(type, id, data)
66
59
  end
67
60
 
68
61
  # Loads an entity with type '.player'.
@@ -75,8 +68,8 @@ class Flox
75
68
  # @return [Flox::Entity]
76
69
  def save_entity(entity)
77
70
  result = service.put(entity.path, entity)
78
- entity['updatedAt'] = result['updatedAt']
79
- entity['createdAt'] = result['createdAt']
71
+ entity[:updatedAt] = result[:updatedAt]
72
+ entity[:createdAt] = result[:createdAt]
80
73
  entity
81
74
  end
82
75
 
@@ -112,9 +105,9 @@ class Flox
112
105
  args = {}
113
106
 
114
107
  if scope.is_a?(Array)
115
- args['p'] = scope
108
+ args[:p] = scope
116
109
  else
117
- args['t'] = scope.to_s.to_camelcase
110
+ args[:t] = scope.to_s.to_camelcase
118
111
  end
119
112
 
120
113
  raw_scores = service.get(path, args)
@@ -128,15 +121,26 @@ class Flox
128
121
  service.get(path, args)
129
122
  end
130
123
 
124
+ # Loads a bulk of resources from a certain path, optionally feeding them
125
+ # through a block.
126
+ # @yield [id, resource] called on every resource; the return-value is feeded
127
+ # into the result array.
128
+ # @return [Array]
129
+ def load_resources(path, ids)
130
+ ids.map do |id|
131
+ resource = load_resource "#{path}/#{id}"
132
+ resource = yield id, resource if block_given?
133
+ resource
134
+ end
135
+ end
136
+
131
137
  # Loads a log with a certain ID. A log is a Hash instance.
132
138
  # @return [Hash]
133
139
  def load_log(log_id)
134
- log = service.get log_path(log_id)
135
- log['id'] = log_id unless log['id']
136
- log
140
+ service.get log_path(log_id)
137
141
  end
138
142
 
139
- # Loads logs defined by a certain query.
143
+ # Finds logs defined by a certain query.
140
144
  # Here are some sample queries:
141
145
  #
142
146
  # * `day:2014-02-20` → all logs of a certain day
@@ -144,32 +148,59 @@ class Flox
144
148
  # * `severity:error` → all logs of type error
145
149
  # * `day:2014-02-20 severity:error` → all error logs from February 20th.
146
150
  #
147
- # @return [Flox::ResourceEnumerator<Hash>]
148
- def load_logs(query=nil, limit=nil)
149
- log_ids = load_log_ids(query, limit)
150
- paths = log_ids.map { |log_id| log_path(log_id) }
151
- ResourceEnumerator.new(service, paths)
151
+ # @return [Array<Hash>]
152
+ def find_logs(query=nil, limit=nil)
153
+ log_ids = find_log_ids(query, limit)
154
+ load_resources('logs', log_ids)
152
155
  end
153
156
 
154
- # Loads just the IDs of the logs, defined by a certain query.
157
+ # Finds just the IDs of the logs, defined by a certain query.
158
+ # @see {#find_logs}
155
159
  # @return [Array<String>]
156
- def load_log_ids(query=nil, limit=nil)
160
+ def find_log_ids(query=nil, limit=nil)
157
161
  log_ids = []
158
162
  cursor = nil
159
163
  begin
160
164
  args = {}
161
- args['q'] = query if query
162
- args['l'] = limit if limit
163
- args['c'] = cursor if cursor
165
+ args[:q] = query if query
166
+ args[:l] = limit if limit
167
+ args[:c] = cursor if cursor
164
168
 
165
169
  result = service.get "logs", args
166
- cursor = result["cursor"]
167
- log_ids += result["ids"]
170
+ cursor = result[:cursor]
171
+ log_ids += result[:ids]
168
172
  limit -= log_ids.length if limit
169
173
  end while !cursor.nil? and (limit.nil? or limit > 0)
170
174
  log_ids
171
175
  end
172
176
 
177
+ # Executes a query over Entities, using a simple SQL-like syntax.
178
+ # @see Flox::Query
179
+ #
180
+ # @overload find_entities(query)
181
+ # @param query [Flox::Query] the query to execute
182
+ # @overload find_entities(entity_type, constraints=nil, *args)
183
+ # @param type [String, Symbol, Class] the type of the entity
184
+ # @param query [String] the query string with optional '?' placeholders
185
+ def find_entities(*query)
186
+ query = create_query(*query)
187
+ ids = find_entity_ids(query)
188
+ load_resources "entities/#{query.type}", ids do |id, data|
189
+ create_entity(query.type, id, data)
190
+ end
191
+ end
192
+
193
+ # Executes a query over Entities, using a simple SQL-like syntax.
194
+ # @return [Array<String>]
195
+ # @see #find_entities
196
+ # @see Flox::Query
197
+ def find_entity_ids(*query)
198
+ query = create_query(*query)
199
+ path = "entities/#{query.type}"
200
+ data = { where: query.constraints, offset: query.offset, limit: query.limit }
201
+ service.post(path, data).map {|e| e[:id]}
202
+ end
203
+
173
204
  # Loads the status of the Flox service.
174
205
  # @return [Hash] with the keys 'status' and 'version'.
175
206
  def status
@@ -213,12 +244,27 @@ class Flox
213
244
  "logs/#{log_id}"
214
245
  end
215
246
 
247
+ def create_entity(type, id, data)
248
+ if (type == '.player' or type == Flox::Player)
249
+ Player.new(id, data)
250
+ else
251
+ Entity.new(type, id, data)
252
+ end
253
+ end
254
+
255
+ def create_query(*query)
256
+ if query[0].kind_of? Flox::Query then query[0]
257
+ else Flox::Query.new(query[0], query[1], *query[2..-1])
258
+ end
259
+ end
260
+
216
261
  end
217
262
 
218
- require 'flox/version'
219
- require 'flox/utils'
220
263
  require 'flox/rest_service'
264
+ require 'flox/version'
265
+ require 'flox/errors'
221
266
  require 'flox/entity'
222
267
  require 'flox/player'
223
268
  require 'flox/score'
224
- require 'flox/resource_enumerator'
269
+ require 'flox/query'
270
+ require 'flox/utils'
@@ -10,7 +10,7 @@ require 'time'
10
10
  # The class extends `Hash`. Thus, all properties of the Entity can be accessed
11
11
  # as keys of the Entity instance.
12
12
  #
13
- # entity['name'] = 'Donald Duck'
13
+ # entity[:name] = 'Donald Duck'
14
14
  #
15
15
  # For convenience, the standard entity properties (e.g. `created_at` and
16
16
  # `updated_at`)can be accessed via Ruby attributes.
@@ -19,7 +19,7 @@ require 'time'
19
19
  #
20
20
  # To load and save an entity, use the respective methods on the Flox class.
21
21
  #
22
- # my_entity = flox.load_entity('SaveGame', '12345') # => Flox::Entity
22
+ # my_entity = flox.load_entity(:SaveGame, '12345') # => Flox::Entity
23
23
  # flox.save_entity(my_entity)
24
24
  #
25
25
  class Flox::Entity < Hash
@@ -36,33 +36,36 @@ class Flox::Entity < Hash
36
36
  def initialize(type, id=nil, data=nil)
37
37
  @type = type
38
38
  @id = id ? id : String.random_uid
39
- self['createdAt'] = self['updatedAt'] = Time.now.utc.to_xs_datetime
40
- self.public_access = ''
41
- self.merge!(data) if data
39
+ self[:createdAt] = self[:updatedAt] = Time.now.utc.to_xs_datetime
40
+ self[:publicAccess] = ''
41
+ if (data)
42
+ data_sym = Hash[data.map{|k, v| [k.to_sym, v]}]
43
+ self.merge!(data_sym)
44
+ end
42
45
  end
43
46
 
44
47
  def created_at
45
- Time.parse self['createdAt']
48
+ Time.parse self[:createdAt]
46
49
  end
47
50
 
48
51
  def updated_at
49
- Time.parse self['updatedAt']
52
+ Time.parse self[:updatedAt]
50
53
  end
51
54
 
52
55
  def public_access
53
- self["publicAccess"]
56
+ self[:publicAccess]
54
57
  end
55
58
 
56
59
  def public_access=(access)
57
- self["publicAccess"] = access.to_s
60
+ self[:publicAccess] = access.to_s
58
61
  end
59
62
 
60
63
  def owner_id
61
- self["ownerId"]
64
+ self[:ownerId]
62
65
  end
63
66
 
64
67
  def owner_id=(value)
65
- self["ownerId"] = value.to_s
68
+ self[:ownerId] = value.to_s
66
69
  end
67
70
 
68
71
  def path
@@ -78,6 +81,16 @@ class Flox::Entity < Hash
78
81
  description += "]"
79
82
  end
80
83
 
84
+ # Accesses a property of the entity; both symbols and strings work.
85
+ def [](key)
86
+ super(key.to_sym)
87
+ end
88
+
89
+ # (see #[])
90
+ def []=(key, value)
91
+ super(key.to_sym, value)
92
+ end
93
+
81
94
  #
82
95
  # documentation hints
83
96
  #
@@ -0,0 +1,20 @@
1
+ ## Author: Daniel Sperl
2
+ ## Copyright: Copyright 2014 Gamua
3
+ ## License: Simplified BSD
4
+
5
+ # The main Error class for Flox errors
6
+ class Flox::Error < StandardError; end
7
+
8
+ # Raised when the REST service encounters an error
9
+ # (e.g. the server returns a HTTP code >= 400).
10
+ class Flox::ServiceError < Flox::Error
11
+
12
+ # @return [Net::HTTPResponse] the complete server response
13
+ attr_reader :response
14
+
15
+ # @param code [String] the http status code
16
+ def initialize(response)
17
+ @response = response
18
+ end
19
+
20
+ end
@@ -23,14 +23,14 @@ class Flox::Player < Flox::Entity
23
23
  # is always `.player` in Flox.
24
24
  def initialize(id=nil, data=nil)
25
25
  data ||= {}
26
- data["authType"] ||= "guest"
27
- data["publicAccess"] ||= "r"
26
+ data[:authType] ||= "guest"
27
+ data[:publicAccess] ||= "r"
28
28
  super(".player", id, data)
29
29
  self.owner_id ||= self.id
30
30
  end
31
31
 
32
32
  def auth_type
33
- self["authType"].to_sym
33
+ self[:authType].to_sym
34
34
  end
35
35
 
36
36
  #
@@ -0,0 +1,84 @@
1
+ ## Author: Daniel Sperl
2
+ ## Copyright: Copyright 2014 Gamua
3
+ ## License: Simplified BSD
4
+
5
+ # The Query class allows you to retrieve entities from the server by narrowing
6
+ # down the results with certain constraints. The system works similar to SQL
7
+ # "select" statements.
8
+ #
9
+ # Before you can make a query, you have to create indices that match the query.
10
+ # You can do that in the Flox online interface. An index has to contain all the
11
+ # properties that are referenced in the constraints.
12
+ #
13
+ # Here is an example of how you can execute a Query with Flox. This query
14
+ # requires an index containing both "level" and "score" properties.
15
+ #
16
+ # query = Query.new(:Player)
17
+ # query.where("level == ? AND score > ?", "tutorial", 500)
18
+ # query.limit = 1
19
+ # results = flox.find_entities(query)
20
+ #
21
+ # Alternatively, you can execute the query in one single line:
22
+ #
23
+ # results = flox.find_entities(:Player, "score > ?", 500)
24
+ # puts "Found #{results.length} players"
25
+ #
26
+ class Flox::Query
27
+
28
+ # @return [String] the constraints that will be used as WHERE-clause.
29
+ # It is recommended to use the {#where} method to construct them.
30
+ attr_accessor :constraints
31
+
32
+ # @return [Fixnum] the offset of the results returned by the query.
33
+ attr_accessor :offset
34
+
35
+ # @return [Fixnum] the maximum number of returned entities.
36
+ attr_accessor :limit
37
+
38
+ # @return [String] the entity type that is searched.
39
+ attr_reader :type
40
+
41
+ # Create a new query that will search within the given Entity type.
42
+ # Optionally, pass the constraints in the same way as in the {#where} method.
43
+ # @param entity_type [String, Symbol, Class] the type of the entity
44
+ def initialize(entity_type, constraints=nil, *args)
45
+ if entity_type == Flox::Player || entity_type.to_s == "Player"
46
+ entity_type = '.player'
47
+ end
48
+
49
+ @type = entity_type
50
+ @offset = 0
51
+ @limit = 50
52
+ where(constraints, *args) if constraints
53
+ end
54
+
55
+ # You can narrow down the results of the query with an SQL like where-clause.
56
+ # The constraints string supports the following comparison operators:
57
+ # `==, >, >=, <, <=, !=`. You can combine constraints using `AND` and `OR`;
58
+ # construct logical groups with round brackets.
59
+ #
60
+ # To simplify creation of the constraints string, you can use questions
61
+ # marks as placeholders. They will be replaced one by one with the additional
62
+ # parameters you pass to the method, while making sure their format is correct
63
+ # (e.g. it surrounds Strings with quotations marks). Here is an example:
64
+ #
65
+ # query.where("name == ? AND score > ?", "thomas", 500);
66
+ # # -> name == "thomas" AND score > 500
67
+ #
68
+ # Use the 'IN'-operator to check for inclusion within a list of possible values:
69
+ #
70
+ # query.where("name IN ?", ["alfa", "bravo", "charlie"]);
71
+ # # -> name IN ["alfa", "bravo", "charlie"]
72
+ #
73
+ # Note that subsequent calls to this method will replace preceding constraints.
74
+ # @return String the final constraints string
75
+ def where(constraints, *args)
76
+ @constraints = constraints.gsub(/\?/) do
77
+ raise ArgumentError, "incorrect placeholder count" unless args.length > 0
78
+ arg = args.shift
79
+ arg = arg.to_xs_datetime if arg.kind_of?(Time)
80
+ arg.to_json
81
+ end
82
+ end
83
+
84
+ end
@@ -30,33 +30,90 @@ class Flox::RestService
30
30
  # and added to the path.
31
31
  # @return the server response.
32
32
  def get(path, data=nil)
33
- path = full_path(path)
34
- path += "?" + URI.encode_www_form(data) if data
35
- request = Net::HTTP::Get.new(path)
36
- execute(request)
33
+ request(:get, path, data)
37
34
  end
38
35
 
39
36
  # Makes a `DELETE` request at the server.
40
37
  # @return the server response.
41
38
  def delete(path)
42
- request = Net::HTTP::Delete.new(full_path(path))
43
- execute(request)
39
+ request(:delete, path)
44
40
  end
45
41
 
46
42
  # Makes a `POST` request at the server. The given data-Hash is transferred
47
43
  # in the body of the request.
48
44
  # @return the server response.
49
45
  def post(path, data=nil)
50
- request = Net::HTTP::Post.new(full_path(path))
51
- execute(request, data)
46
+ request(:post, path, data)
52
47
  end
53
48
 
54
49
  # Makes a `PUT` request at the server. The given data-Hash is transferred
55
50
  # in the body of the request.
56
51
  # @return the server response.
57
52
  def put(path, data=nil)
58
- request = Net::HTTP::Put.new(full_path(path))
59
- execute(request, data)
53
+ request(:put, path, data)
54
+ end
55
+
56
+ # Makes a request on the Flox server. When called without a block, the
57
+ # method will raise a {Flox::ServiceError} if the server returns an HTTP
58
+ # error code; when called with a block, it will always succeed.
59
+ #
60
+ # @yield [body, response] The decoded body and the raw http response
61
+ # @param method [Symbol] one of `:get, :delete, :put, :post`
62
+ # @param path [String] the path relative to the game, e.g. "entities/product"
63
+ # @param data [Hash] the body of the request.
64
+ # @return the body of the server response
65
+ def request(method, path, data=nil)
66
+ request_class =
67
+ case method
68
+ when :get
69
+ if data
70
+ path += "?" + URI.encode_www_form(data)
71
+ data = nil
72
+ end
73
+ Net::HTTP::Get
74
+ when :delete then Net::HTTP::Delete
75
+ when :post then Net::HTTP::Post
76
+ when :put then Net::HTTP::Put
77
+ end
78
+
79
+ flox_header = {
80
+ :sdk => { :type => "ruby", :version => Flox::VERSION },
81
+ :gameKey => @game_key,
82
+ :dispatchTime => Time.now.utc.to_xs_datetime,
83
+ :bodyCompression => "zlib",
84
+ :player => @authentication
85
+ }
86
+
87
+ request = request_class.new(full_path(path))
88
+ request["Content-Type"] = "application/json"
89
+ request["X-Flox"] = flox_header.to_json
90
+ request.body = encode(data.to_json) if data
91
+
92
+ uri = URI.parse(@base_url)
93
+ http = Net::HTTP::new(uri.host, uri.port)
94
+
95
+ if uri.scheme == "https" # enable SSL/TLS
96
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
97
+ http.use_ssl = true
98
+ end
99
+
100
+ http.start do |session|
101
+ response = session.request(request)
102
+ body = body_from_response response
103
+
104
+ if block_given?
105
+ # if a block was passed to the method, no error is thrown
106
+ yield body, response
107
+ return body
108
+ else
109
+ # without a block, we raise an error if the request was not a success
110
+ if (response.is_a? Net::HTTPSuccess)
111
+ return body
112
+ else
113
+ raise Flox::ServiceError.new(response), (body[:message] rescue body)
114
+ end
115
+ end
116
+ end
60
117
  end
61
118
 
62
119
  # Makes a login on the server with the given authentication data.
@@ -76,7 +133,7 @@ class Flox::RestService
76
133
  auth_data[:id] = @authentication[:id]
77
134
  end
78
135
  response = post("authenticate", auth_data)
79
- auth_data[:id] = response["id"]
136
+ auth_data[:id] = response[:id]
80
137
  end
81
138
 
82
139
  @authentication = auth_data
@@ -90,45 +147,6 @@ class Flox::RestService
90
147
 
91
148
  private
92
149
 
93
- def execute(request, data=nil)
94
- flox_header = {
95
- "sdk" => { "type" => "ruby", "version" => Flox::VERSION },
96
- "gameKey" => @game_key,
97
- "dispatchTime" => Time.now.utc.to_xs_datetime,
98
- "bodyCompression" => "zlib",
99
- "player" => @authentication
100
- }
101
-
102
- request["Content-Type"] = "application/json"
103
- request["X-Flox"] = flox_header.to_json
104
- request.body = encode(data.to_json) if data
105
-
106
- uri = URI.parse(@base_url)
107
- http = Net::HTTP::new(uri.host, uri.port)
108
-
109
- if uri.scheme == "https" # enable SSL/TLS
110
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
111
- http.use_ssl = true
112
- end
113
-
114
- http.start do |session|
115
- response = session.request(request)
116
- body = response.body
117
- body = decode(body) if response['x-content-encoding'] == 'zlib'
118
-
119
- if (response.is_a? Net::HTTPSuccess)
120
- return JSON.parse(body || '{}')
121
- else
122
- message = begin
123
- JSON.parse(body)['message']
124
- rescue
125
- body
126
- end
127
- raise Flox::Error, message
128
- end
129
- end
130
- end
131
-
132
150
  def decode(string)
133
151
  return nil if string.nil? or string.empty?
134
152
  Zlib::Inflate.inflate(Base64.decode64(string))
@@ -139,6 +157,18 @@ class Flox::RestService
139
157
  Base64.encode64(Zlib::Deflate.deflate(string))
140
158
  end
141
159
 
160
+ def body_from_response(response)
161
+ body = response.body
162
+
163
+ begin
164
+ body = decode(response.body) if response['x-content-encoding'] == 'zlib'
165
+ JSON.parse(body || '{}', {symbolize_names: true})
166
+ rescue
167
+ html_matches = /\<h1\>(.*)\<\/h1\>/.match(body)
168
+ { message: html_matches ? html_matches[1] : body }
169
+ end
170
+ end
171
+
142
172
  def full_path(path)
143
173
  "/api/games/#{@game_id}/#{path}"
144
174
  end
@@ -24,11 +24,11 @@ class Flox::Score
24
24
 
25
25
  # @param data [Hash] the contents of the score as given by the Flox server.
26
26
  def initialize(data)
27
- @player_id = data['playerId'].to_s
28
- @player_name = data['playerName'].to_s
29
- @value = data['value'].to_i
30
- @country = data['country'].to_s
31
- @created_at = Time.parse(data['createdAt'].to_s)
27
+ @player_id = data[:playerId].to_s
28
+ @player_name = data[:playerName].to_s
29
+ @value = data[:value].to_i
30
+ @country = data[:country].to_s
31
+ @created_at = Time.parse(data[:createdAt].to_s)
32
32
  end
33
33
 
34
34
  end
@@ -9,6 +9,6 @@
9
9
  class Flox
10
10
 
11
11
  # The current version of the Flox SDK.
12
- VERSION = '0.1.2'
12
+ VERSION = '0.2.0'
13
13
 
14
14
  end
@@ -30,7 +30,7 @@ class EntityTest < Test::Unit::TestCase
30
30
  created_at = Time.parse("2014-02-20T11:00:00Z")
31
31
  created_at_string = created_at.to_xs_datetime
32
32
  entity = Flox::Entity.new('type', 'id')
33
- entity['createdAt'] = created_at_string
33
+ entity[:createdAt] = created_at_string
34
34
  assert_equal(created_at, entity.created_at)
35
35
  end
36
36
 
@@ -38,7 +38,7 @@ class EntityTest < Test::Unit::TestCase
38
38
  updated_at = Time.parse("2014-02-20T11:00:00Z")
39
39
  updated_at_string = updated_at.to_xs_datetime
40
40
  entity = Flox::Entity.new('type', 'id')
41
- entity['updatedAt'] = updated_at_string
41
+ entity[:updatedAt] = updated_at_string
42
42
  assert_equal(updated_at, entity.updated_at)
43
43
  end
44
44
 
@@ -49,4 +49,15 @@ class EntityTest < Test::Unit::TestCase
49
49
  assert_equal('rw', entity.public_access)
50
50
  end
51
51
 
52
+ def test_accepts_string_or_symbol
53
+ first_name = 'donald'
54
+ last_name = 'duck'
55
+ entity = Flox::Entity.new('type', 'id',
56
+ { first_name: first_name, "last_name" => last_name })
57
+ assert_equal(first_name, entity[:first_name])
58
+ assert_equal(first_name, entity['first_name'])
59
+ assert_equal(last_name, entity[:last_name])
60
+ assert_equal(last_name, entity['last_name'])
61
+ end
62
+
52
63
  end
@@ -36,37 +36,37 @@ class FloxTest < Test::Unit::TestCase
36
36
  leaderboard_id = "dummy"
37
37
  path = "leaderboards/#{leaderboard_id}"
38
38
  raw_score = {
39
- 'value' => 20,
40
- 'playerName' => 'hugo',
41
- 'playerId' => '123',
42
- 'country' => 'at',
43
- 'createdAt' => '2014-02-24T20:15:00.123Z'
39
+ :value => 20,
40
+ :playerName => 'hugo',
41
+ :playerId => '123',
42
+ :country => 'at',
43
+ :createdAt => '2014-02-24T20:15:00.123Z'
44
44
  }
45
45
 
46
46
  # using time scope (t)
47
- flox.service.expects(:get).once.with(path, has_key('t')).returns([])
47
+ flox.service.expects(:get).once.with(path, has_key(:t)).returns([])
48
48
  scores = flox.load_scores(leaderboard_id, :today)
49
49
  assert_kind_of(Array, scores)
50
50
  assert_equal(0, scores.length)
51
51
 
52
52
  # using player scope (p)
53
- flox.service.expects(:get).once.with(path, has_key('p')).returns([raw_score])
53
+ flox.service.expects(:get).once.with(path, has_key(:p)).returns([raw_score])
54
54
  scores = flox.load_scores(leaderboard_id, %w(1, 2, 3))
55
55
  assert_kind_of(Array, scores)
56
56
  assert_equal(1, scores.length)
57
57
 
58
58
  highscore = scores.first
59
59
  assert_kind_of(Flox::Score, highscore)
60
- assert_equal(raw_score['value'], highscore.value)
61
- assert_equal(raw_score['playerName'], highscore.player_name)
62
- assert_equal(raw_score['playerId'], highscore.player_id)
63
- assert_equal(raw_score['country'], highscore.country)
64
- assert_equal(raw_score['createdAt'], highscore.created_at.to_xs_datetime)
60
+ assert_equal(raw_score[:value], highscore.value)
61
+ assert_equal(raw_score[:playerName], highscore.player_name)
62
+ assert_equal(raw_score[:playerId], highscore.player_id)
63
+ assert_equal(raw_score[:country], highscore.country)
64
+ assert_equal(raw_score[:createdAt], highscore.created_at.to_xs_datetime)
65
65
  end
66
66
 
67
67
  def test_login_with_key
68
68
  key = "key"
69
- result = { 'id' => '123', 'entity' => { 'authType' => 'key' } }
69
+ result = { :id => '123', :entity => { :authType => 'key' } }
70
70
  flox.service.expects(:login).with(:key, key, nil).once.returns(result)
71
71
  player = flox.login_with_key(key)
72
72
  assert_not_nil(player)
@@ -86,7 +86,7 @@ class FloxTest < Test::Unit::TestCase
86
86
  type = "type"
87
87
  id = "id"
88
88
  path = "entities/#{type}/#{id}"
89
- data = { "name" => "Jean-Luc" }
89
+ data = { :name => "Jean-Luc" }
90
90
 
91
91
  flox.service.expects(:get).with(path).once.returns(data)
92
92
  entity = flox.load_entity(type, id)
@@ -95,32 +95,32 @@ class FloxTest < Test::Unit::TestCase
95
95
  assert_equal(id, entity.id)
96
96
  assert_equal(type, entity.type)
97
97
  assert_equal(path, entity.path)
98
- assert_equal(data["name"], entity["name"])
98
+ assert_equal(data[:name], entity[:name])
99
99
  end
100
100
 
101
101
  def test_load_player
102
102
  id = "id"
103
- data = { "name" => "Jean-Luc" }
103
+ data = { :name => "Jean-Luc" }
104
104
  flox.service.expects(:get).once.returns(data)
105
105
  player = flox.load_player(id)
106
106
  assert_kind_of(Flox::Player, player)
107
- assert_equal(data["name"], player["name"])
107
+ assert_equal(data[:name], player[:name])
108
108
  assert_equal(id, player.id)
109
109
  assert_equal('.player', player.type)
110
110
  end
111
111
 
112
112
  def test_save_entity
113
- data = { "name" => "Jean-Luc" }
113
+ data = { :name => "Jean-Luc" }
114
114
  entity = Flox::Entity.new("type", "id", data)
115
115
  path = "entities/#{entity.type}/#{entity.id}"
116
- result = { "createdAt" => "2014-01-01T12:00:00.000Z",
117
- "updatedAt" => "2014-02-01T12:00:00.000Z" }
116
+ result = { :createdAt => "2014-01-01T12:00:00.000Z",
117
+ :updatedAt => "2014-02-01T12:00:00.000Z" }
118
118
 
119
119
  flox.service.expects(:put).with(path, entity).once.returns(result)
120
120
  flox.save_entity(entity)
121
121
 
122
- assert_equal(result["createdAt"], entity.created_at.to_xs_datetime)
123
- assert_equal(result["updatedAt"], entity.updated_at.to_xs_datetime)
122
+ assert_equal(result[:createdAt], entity.created_at.to_xs_datetime)
123
+ assert_equal(result[:updatedAt], entity.updated_at.to_xs_datetime)
124
124
  end
125
125
 
126
126
  def test_delete_entity
@@ -132,12 +132,12 @@ class FloxTest < Test::Unit::TestCase
132
132
 
133
133
  def test_find_logs
134
134
  log_ids = %w{ 0 1 2 }
135
- result = { 'ids' => log_ids, 'cursor' => nil }
136
- flox.service.expects(:get).at_most_once.returns(result)
137
- logs = flox.load_logs(':warning', 50)
138
- assert_kind_of(Flox::ResourceEnumerator, logs)
135
+ result = { :ids => log_ids, :cursor => nil }
136
+
137
+ flox.service.expects(:get).times(4).returns(result).then.returns({})
138
+ logs = flox.find_logs('severity:warning', 50)
139
+ assert_kind_of(Array, logs)
139
140
  assert_equal(log_ids.length, logs.length)
140
- flox.service.expects(:get).times(log_ids.length).returns({})
141
141
  logs.each { |log| assert_kind_of(Hash, log) }
142
142
  end
143
143
 
@@ -145,19 +145,19 @@ class FloxTest < Test::Unit::TestCase
145
145
  log_ids = %w{ 0 1 2 3 4 5 6 7 8 9 }
146
146
  log_ids_a = log_ids.slice 0, 5
147
147
  log_ids_b = log_ids.slice 5, 5
148
- result_a = { 'ids' => log_ids_a, 'cursor' => 'a' }
149
- result_b = { 'ids' => log_ids_b, 'cursor' => nil }
148
+ result_a = { :ids => log_ids_a, :cursor => 'a' }
149
+ result_b = { :ids => log_ids_b, :cursor => nil }
150
150
 
151
151
  # without limit
152
152
  flox.service.expects(:get).twice.returns(result_a, result_b)
153
- out_log_ids = flox.load_log_ids
153
+ out_log_ids = flox.find_log_ids
154
154
  assert_equal(log_ids.length, out_log_ids.length)
155
155
 
156
156
  # with limit
157
157
  limit = 7
158
- result_b['ids'] = %w{ 5 6 }
158
+ result_b[:ids] = %w{ 5 6 }
159
159
  flox.service.expects(:get).twice.returns(result_a, result_b)
160
- out_log_ids = flox.load_log_ids(nil, limit)
160
+ out_log_ids = flox.find_log_ids(nil, limit)
161
161
  assert_equal(limit, out_log_ids.length)
162
162
  end
163
163
 
@@ -0,0 +1,70 @@
1
+ ## Author: Daniel Sperl
2
+ ## Copyright: Copyright 2014 Gamua
3
+ ## License: Simplified BSD
4
+
5
+ require 'flox'
6
+ require 'test/unit'
7
+ require 'mocha/setup'
8
+
9
+ class FloxTest < Test::Unit::TestCase
10
+
11
+ def test_constraints
12
+ time_xs = "2014-03-10T14:00:00.124Z"
13
+ time = Time.parse(time_xs)
14
+ query = Flox::Query.new("Type")
15
+ query.where "count >= ? AND (name == ? OR date <= ?)", 10, "hugo", time
16
+ assert_equal("count >= 10 AND (name == \"hugo\" OR date <= \"#{time_xs}\")",
17
+ query.constraints, "wrong placeholder replacement")
18
+ end
19
+
20
+ def test_constraints_with_array
21
+ query = Flox::Query.new("Type")
22
+ query.where "name IN ?", %w{ alpha bravo charlie }
23
+ assert_equal('name IN ["alpha","bravo","charlie"]', query.constraints)
24
+ end
25
+
26
+ def test_offset
27
+ query = Flox::Query.new("Type")
28
+ query.offset = 15
29
+ assert_equal(15, query.offset)
30
+ end
31
+
32
+ def test_limit
33
+ query = Flox::Query.new("Type")
34
+ query.limit = 5
35
+ assert_equal(5, query.limit)
36
+ end
37
+
38
+ def test_constraints_checks_argument_count
39
+ query = Flox::Query.new("Type")
40
+ assert_raise ArgumentError do
41
+ query.where "a == ? AND b == ?", 10
42
+ end
43
+ end
44
+
45
+ def test_run_query
46
+ flox = Flox.new("game_id", "game_key")
47
+ query = Flox::Query.new("Type", "score > ?", 100)
48
+ flox.service.expects(:request).times(3).returns([{id: 1}, {id: 2}])
49
+ .then.returns({score: 101}, {score: 102})
50
+ results = flox.find_entities query
51
+ assert_equal 2, results.length
52
+
53
+ result_0 = results[0]
54
+ assert_kind_of(Flox::Entity, result_0)
55
+ assert_equal(101, result_0[:score])
56
+
57
+ result_1 = results[1]
58
+ assert_kind_of(Flox::Entity, result_1)
59
+ assert_equal(102, result_1[:score])
60
+ end
61
+
62
+ def test_run_query_direct
63
+ flox = Flox.new("game_id", "game_key")
64
+ flox.service.expects(:request).times(3).returns([{id: 1}, {id: 2}])
65
+ .then.returns({}, {})
66
+ results = flox.find_entities "Type", "score > ?", 100
67
+ assert_equal 2, results.length
68
+ end
69
+
70
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flox
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Sperl
@@ -97,10 +97,12 @@ files:
97
97
  - bin/flox
98
98
  - test/test_entity.rb
99
99
  - test/test_flox.rb
100
+ - test/test_query.rb
100
101
  - test/test_utils.rb
101
102
  - lib/flox/entity.rb
103
+ - lib/flox/errors.rb
102
104
  - lib/flox/player.rb
103
- - lib/flox/resource_enumerator.rb
105
+ - lib/flox/query.rb
104
106
  - lib/flox/rest_service.rb
105
107
  - lib/flox/score.rb
106
108
  - lib/flox/utils.rb
@@ -1,43 +0,0 @@
1
- ## Author: Daniel Sperl
2
- ## Copyright: Copyright 2014 Gamua
3
- ## License: Simplified BSD
4
-
5
- require 'json'
6
-
7
- # A helper class that stores the paths to a number of REST resources and
8
- # supports iterating over those resources, downloading them lazily from the
9
- # server.
10
- class Flox::ResourceEnumerator
11
-
12
- include Enumerable
13
-
14
- # @param rest_service [RestService]
15
- # the service instance used to download the resources.
16
- # @param paths [Array<String>]
17
- # the URLs to the resources that need to be accessed, relative to
18
- # the game's root.
19
- def initialize(rest_service, paths)
20
- @service = rest_service
21
- @paths = paths
22
- end
23
-
24
- # Iterates over the resources provided on intialization, loading them
25
- # from the server one by one.
26
- def each
27
- @paths.each do |path|
28
- yield @service.get(path)
29
- end
30
- end
31
-
32
- def length
33
- @paths.length
34
- end
35
-
36
- #
37
- # documentation hints
38
- #
39
-
40
- # @!attribute length
41
- # @return [Fixnum] the total number of objects being enumerated.
42
-
43
- end