flox 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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