gcloud 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +8 -8
  2. data/CHANGELOG.md +8 -0
  3. data/lib/gcloud.rb +48 -30
  4. data/lib/gcloud/bigquery.rb +4 -6
  5. data/lib/gcloud/bigquery/connection.rb +2 -14
  6. data/lib/gcloud/bigquery/dataset.rb +41 -42
  7. data/lib/gcloud/bigquery/project.rb +50 -46
  8. data/lib/gcloud/bigquery/query_job.rb +7 -8
  9. data/lib/gcloud/bigquery/table.rb +54 -55
  10. data/lib/gcloud/bigquery/table/schema.rb +30 -40
  11. data/lib/gcloud/bigquery/view.rb +10 -11
  12. data/lib/gcloud/credentials.rb +19 -25
  13. data/lib/gcloud/datastore.rb +4 -6
  14. data/lib/gcloud/datastore/dataset.rb +3 -5
  15. data/lib/gcloud/dns.rb +4 -6
  16. data/lib/gcloud/dns/connection.rb +17 -16
  17. data/lib/gcloud/dns/importer.rb +5 -11
  18. data/lib/gcloud/dns/project.rb +11 -12
  19. data/lib/gcloud/dns/zone.rb +52 -92
  20. data/lib/gcloud/dns/zone/transaction.rb +2 -2
  21. data/lib/gcloud/pubsub.rb +4 -6
  22. data/lib/gcloud/pubsub/connection.rb +1 -12
  23. data/lib/gcloud/pubsub/project.rb +30 -36
  24. data/lib/gcloud/pubsub/subscription.rb +18 -26
  25. data/lib/gcloud/pubsub/topic.rb +16 -26
  26. data/lib/gcloud/resource_manager.rb +5 -6
  27. data/lib/gcloud/resource_manager/connection.rb +4 -4
  28. data/lib/gcloud/resource_manager/manager.rb +10 -14
  29. data/lib/gcloud/resource_manager/project.rb +3 -5
  30. data/lib/gcloud/search.rb +295 -0
  31. data/lib/gcloud/search/api_client.rb +144 -0
  32. data/lib/gcloud/search/connection.rb +146 -0
  33. data/lib/gcloud/search/credentials.rb +30 -0
  34. data/lib/gcloud/search/document.rb +301 -0
  35. data/lib/gcloud/search/document/list.rb +85 -0
  36. data/lib/gcloud/search/errors.rb +67 -0
  37. data/lib/gcloud/search/field_value.rb +164 -0
  38. data/lib/gcloud/search/field_values.rb +263 -0
  39. data/lib/gcloud/search/fields.rb +267 -0
  40. data/lib/gcloud/search/index.rb +613 -0
  41. data/lib/gcloud/search/index/list.rb +90 -0
  42. data/lib/gcloud/search/project.rb +197 -0
  43. data/lib/gcloud/search/result.rb +169 -0
  44. data/lib/gcloud/search/result/list.rb +95 -0
  45. data/lib/gcloud/storage.rb +4 -6
  46. data/lib/gcloud/storage/bucket.rb +55 -43
  47. data/lib/gcloud/storage/bucket/cors.rb +5 -7
  48. data/lib/gcloud/storage/file.rb +35 -30
  49. data/lib/gcloud/storage/file/acl.rb +12 -16
  50. data/lib/gcloud/storage/project.rb +56 -22
  51. data/lib/gcloud/version.rb +1 -1
  52. metadata +20 -3
@@ -0,0 +1,144 @@
1
+ #--
2
+ # Copyright 2015 Google Inc. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require "gcloud/version"
17
+ require "google/api_client"
18
+
19
+ module Gcloud
20
+ module Search
21
+ ##
22
+ # Temporary substitute for Google::APIClient. Once the Search API is
23
+ # discoverable, initialization of this class in Connection should be
24
+ # replaced with the Google API Client.
25
+ class APIClient #:nodoc:
26
+ attr_accessor :authorization, :connection
27
+
28
+ ##
29
+ # Creates a new APIClient instance.
30
+ def initialize _options
31
+ @connection = Faraday.new request: {
32
+ params_encoder: Faraday::FlatParamsEncoder }
33
+ end
34
+
35
+ def discovered_api name, version
36
+ DiscoveredApi.new name, version
37
+ end
38
+
39
+ def execute options
40
+ api_method = options[:api_method]
41
+ uri = generate_search_uri api_method[:uri], options
42
+ run api_method[:method], uri, options
43
+ end
44
+
45
+ def inspect
46
+ "#{self.class}(#{@project})"
47
+ end
48
+
49
+ protected
50
+
51
+ ##
52
+ # Return type for APIClient#discovered_api
53
+ class DiscoveredApi #:nodoc:
54
+ def initialize name, version
55
+ @name = name
56
+ @version = version
57
+ end
58
+
59
+ def indexes
60
+ IndexResourcePath.new @name, @version, "indexes", "indexId"
61
+ end
62
+
63
+ def documents
64
+ ResourcePath.new @name,
65
+ @version,
66
+ "indexes/{indexId}/documents",
67
+ "docId"
68
+ end
69
+ end
70
+
71
+ ##
72
+ # Return type for DiscoveredApi http verb methods
73
+ class ResourcePath #:nodoc:
74
+ def initialize api_name, api_version, resource_root, resource_id_param
75
+ @root = "https://#{api_name}.googleapis.com/#{api_version}" \
76
+ "/projects/{projectId}/#{resource_root}"
77
+ @resource_id_param = resource_id_param
78
+ end
79
+
80
+ def create
81
+ api_method :post
82
+ end
83
+
84
+ def delete
85
+ api_method :delete, "/{docId}"
86
+ end
87
+
88
+ def get
89
+ api_method :get, "/{docId}"
90
+ end
91
+
92
+ def list
93
+ api_method :get
94
+ end
95
+
96
+ def api_method method, path = nil
97
+ { method: method, uri: "#{@root}#{path}" }
98
+ end
99
+ end
100
+
101
+ ##
102
+ # Special-case return type for DiscoveredApi http search verb method
103
+ class IndexResourcePath < ResourcePath #:nodoc:
104
+ def search
105
+ api_method :get, "/{indexId}/search"
106
+ end
107
+ end
108
+
109
+ def run method, uri, options = {}
110
+ fix_serialization! options
111
+ if authorization.nil?
112
+ @connection.send method do |req|
113
+ req.url uri
114
+ req.params = options[:parameters] if options[:parameters]
115
+ req.body = options[:body] if options[:body]
116
+ end
117
+ else
118
+ options[:method] = method
119
+ options[:uri] = uri
120
+ options[:connection] = @connection
121
+ authorization.fetch_protected_resource options
122
+ end
123
+ end
124
+
125
+ def generate_search_uri uri, options = {}
126
+ params = options.delete :parameters
127
+ [:projectId, :indexId, :docId].each do |param|
128
+ uri.gsub! "{#{param}}", params.delete(param) if params[param]
129
+ end
130
+ uri = URI uri
131
+ unless params.empty?
132
+ uri.query = Faraday::FlatParamsEncoder.encode params
133
+ end
134
+ uri.to_s
135
+ end
136
+
137
+ def fix_serialization! options
138
+ return unless options[:body_object]
139
+ options[:headers] = { "Content-Type" => "application/json" }
140
+ options[:body] = options.delete(:body_object).to_json
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,146 @@
1
+ #--
2
+ # Copyright 2015 Google Inc. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require "gcloud/version"
17
+ require "gcloud/search/api_client"
18
+
19
+ module Gcloud
20
+ module Search
21
+ ##
22
+ # Represents the connection to Search,
23
+ # as well as expose the API calls.
24
+ class Connection #:nodoc:
25
+ API_VERSION = "v1"
26
+
27
+ attr_accessor :project
28
+ attr_accessor :credentials #:nodoc:
29
+ attr_accessor :client #:nodoc:
30
+ attr_accessor :connection #:nodoc:
31
+
32
+ ##
33
+ # Creates a new Connection instance.
34
+ def initialize project, credentials #:nodoc:
35
+ @project = project
36
+ @credentials = credentials
37
+ client_config = {
38
+ application_name: "gcloud-ruby",
39
+ application_version: Gcloud::VERSION
40
+ }
41
+ @client = Gcloud::Search::APIClient.new client_config
42
+ @client.authorization = @credentials.client
43
+ @search = @client.discovered_api "cloudsearch", API_VERSION
44
+ end
45
+
46
+ def list_indexes options = {}
47
+ params = { projectId: @project,
48
+ indexNamePrefix: options[:prefix],
49
+ view: (options[:view] || "FULL"),
50
+ pageSize: options[:max],
51
+ pageToken: options[:token]
52
+ }.delete_if { |_, v| v.nil? }
53
+
54
+ @client.execute(
55
+ api_method: @search.indexes.list,
56
+ parameters: params
57
+ )
58
+ end
59
+
60
+ def delete_index index_id
61
+ @client.execute(
62
+ api_method: @search.indexes.delete,
63
+ parameters: { projectId: @project,
64
+ indexId: index_id }
65
+ )
66
+ end
67
+
68
+ def get_doc index_id, doc_id
69
+ @client.execute(
70
+ api_method: @search.documents.get,
71
+ parameters: { projectId: @project,
72
+ indexId: index_id,
73
+ docId: doc_id }
74
+ )
75
+ end
76
+
77
+ def list_docs index_id, options = {}
78
+ params = { projectId: @project,
79
+ indexId: index_id,
80
+ view: (options[:view] || "FULL"),
81
+ pageSize: options[:max],
82
+ pageToken: options[:token]
83
+ }.delete_if { |_, v| v.nil? }
84
+
85
+ @client.execute(
86
+ api_method: @search.documents.list,
87
+ parameters: params
88
+ )
89
+ end
90
+
91
+ def create_doc index_id, document_hash
92
+ @client.execute(
93
+ api_method: @search.documents.create,
94
+ parameters: { projectId: @project,
95
+ indexId: index_id },
96
+ body_object: document_hash
97
+ )
98
+ end
99
+
100
+ def delete_doc index_id, doc_id
101
+ @client.execute(
102
+ api_method: @search.documents.delete,
103
+ parameters: { projectId: @project,
104
+ indexId: index_id,
105
+ docId: doc_id }
106
+ )
107
+ end
108
+
109
+ def search index_id, query, options = {}
110
+ # Always encode expression hashes as JSON strings
111
+ if options[:expressions]
112
+ # Force to an array of hashes, this works with an array or a hash
113
+ tmp = [options[:expressions]].flatten.map { |ex| JSON.dump ex }
114
+ options[:expressions] = tmp
115
+ end
116
+
117
+ @client.execute(
118
+ api_method: @search.indexes.search,
119
+ parameters: search_request(index_id, query, options)
120
+ )
121
+ end
122
+
123
+ def inspect #:nodoc:
124
+ "#{self.class}(#{@project})"
125
+ end
126
+
127
+ protected
128
+
129
+ def search_request index_id, query, options = {}
130
+ { projectId: @project,
131
+ indexId: index_id,
132
+ query: query,
133
+ fieldExpressions: options[:expressions],
134
+ matchedCountAccuracy: options[:matched_count_accuracy],
135
+ offset: options[:offset],
136
+ orderBy: options[:order],
137
+ pageSize: options[:max],
138
+ pageToken: options[:token],
139
+ returnFields: options[:fields],
140
+ scorerSize: options[:scorer_size],
141
+ scorer: options[:scorer]
142
+ }.delete_if { |_, v| v.nil? }
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,30 @@
1
+ #--
2
+ # Copyright 2015 Google Inc. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require "gcloud/credentials"
17
+
18
+ module Gcloud
19
+ module Search
20
+ ##
21
+ # Represents the Oauth2 signing logic for Search.
22
+ class Credentials < Gcloud::Credentials #:nodoc:
23
+ SCOPE = ["https://www.googleapis.com/auth/cloudsearch",
24
+ "https://www.googleapis.com/auth/userinfo.email"]
25
+ PATH_ENV_VARS = %w(SEARCH_KEYFILE GCLOUD_KEYFILE GOOGLE_CLOUD_KEYFILE)
26
+ JSON_ENV_VARS = %w(SEARCH_KEYFILE_JSON GCLOUD_KEYFILE_JSON
27
+ GOOGLE_CLOUD_KEYFILE_JSON)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,301 @@
1
+ #--
2
+ # Copyright 2015 Google Inc. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require "gcloud/search/document/list"
17
+ require "gcloud/search/connection"
18
+ require "gcloud/search/fields"
19
+
20
+ module Gcloud
21
+ module Search
22
+ ##
23
+ # = Document
24
+ #
25
+ # A document is an object that stores data that can be searched. Each
26
+ # document has a #doc_id that is
27
+ # unique within its index, a #rank, and a list of #fields that contain typed
28
+ # data. Its field values can be accessed through hash-like methods such as
29
+ # #[] and #each.
30
+ #
31
+ # require "gcloud"
32
+ #
33
+ # gcloud = Gcloud.new
34
+ # search = gcloud.search
35
+ # index = search.index "products"
36
+ #
37
+ # document = index.document "product-sku-000001"
38
+ # document.add "price", 24.95
39
+ # index.save document
40
+ # document.rank #=> 1443648166
41
+ # document["price"] #=> 24.95
42
+ #
43
+ # For more information, see {Documents and
44
+ # Indexes}[https://cloud.google.com/search/documents_indexes].
45
+ #
46
+ class Document
47
+ ##
48
+ # Creates a new Document instance.
49
+ #
50
+ def initialize #:nodoc:
51
+ @fields = Fields.new
52
+ @raw = {}
53
+ end
54
+
55
+ ##
56
+ # The unique identifier for the document. Can be set explicitly when the
57
+ # document is saved. (See Index#document and #doc_id= .) If missing, it is
58
+ # automatically assigned to the document when saved.
59
+ def doc_id
60
+ @raw["docId"]
61
+ end
62
+
63
+ ##
64
+ # Sets the unique identifier for the document.
65
+ #
66
+ # Must contain only visible, printable ASCII characters (ASCII codes 33
67
+ # through 126 inclusive) and be no longer than 500 characters. It cannot
68
+ # begin with an exclamation point (<code>!</code>), and it cannot begin
69
+ # and end with double underscores (<code>__</code>).
70
+ def doc_id= new_doc_id
71
+ @raw["docId"] = new_doc_id
72
+ end
73
+
74
+ ##
75
+ # A positive integer which determines the default ordering of documents
76
+ # returned from a search. The rank can be set explicitly when the document
77
+ # is saved. (See Index#document and #rank= .) If missing, it is
78
+ # automatically assigned to the document when saved.
79
+ def rank
80
+ @raw["rank"]
81
+ end
82
+
83
+ ##
84
+ # Sets the rank of the document.
85
+ #
86
+ # The same rank should not be assigned to many documents, and should
87
+ # never be assigned to more than 10,000 documents. By default (when it is
88
+ # not specified or set to 0), it is set at the time the document is
89
+ # created to the number of seconds since January 1, 2011. The rank can be
90
+ # used in Index#search options +expressions+, +order+, and
91
+ # +fields+, where it is referenced as +rank+.
92
+ def rank= new_rank
93
+ @raw["rank"] = new_rank
94
+ end
95
+
96
+ ##
97
+ # Retrieve the field values associated to a field name.
98
+ #
99
+ # === Parameters
100
+ #
101
+ # +name+::
102
+ # The name of the field. New values will be configured with this name.
103
+ # (+String+)
104
+ #
105
+ # === Returns
106
+ #
107
+ # FieldValues
108
+ #
109
+ # === Example
110
+ #
111
+ # require "gcloud"
112
+ #
113
+ # gcloud = Gcloud.new
114
+ # search = gcloud.search
115
+ # index = search.index "products"
116
+ #
117
+ # document = index.document "product-sku-000001"
118
+ # puts "The document description is:"
119
+ # document["description"].each do |value|
120
+ # puts "* #{value} (#{value.type}) [#{value.lang}]"
121
+ # end
122
+ #
123
+ def [] name
124
+ @fields[name]
125
+ end
126
+
127
+ # rubocop:disable Style/TrivialAccessors
128
+ # Disable rubocop because we want .fields to be listed with the other
129
+ # methods on the class.
130
+
131
+ ##
132
+ # The fields in the document. Each field has a name (String) and a list of
133
+ # values (FieldValues). (See Fields)
134
+ def fields
135
+ @fields
136
+ end
137
+
138
+ # rubocop:enable Style/TrivialAccessors
139
+
140
+ # rubocop:disable Metrics/LineLength
141
+ # Disabled because there are links in the docs that are long.
142
+
143
+ ##
144
+ # Add a new value. If the field name does not exist it will be added. If
145
+ # the field value is a DateTime or Numeric, or the type is set to
146
+ # +:datetime+ or +:number+, then the added value will replace any existing
147
+ # values of the same type (since there can be only one). (See Fields#add)
148
+ #
149
+ # === Parameters
150
+ #
151
+ # +name+::
152
+ # The name of the field. (+String+)
153
+ # +value+::
154
+ # The value to add to the field. (+String+ or +Datetime+ or +Float+)
155
+ # +type+::
156
+ # The type of the field value. An attempt is made to set the correct
157
+ # type when this option is missing, although it must be provided for
158
+ # +:geo+ values. A field can have multiple values with same or different
159
+ # types; however, it cannot have multiple +:datetime+ or +:number+
160
+ # values. (+Symbol+)
161
+ #
162
+ # The following values are supported:
163
+ # * +:default+ - The value is a string. The format will be automatically
164
+ # detected. This is the default value for strings.
165
+ # * +:text+ - The value is a string with maximum length 1024**2
166
+ # characters.
167
+ # * +:html+ - The value is an HTML-formatted string with maximum length
168
+ # 1024**2 characters.
169
+ # * +:atom+ - The value is a string with maximum length 500 characters.
170
+ # * +:geo+ - The value is a point on earth described by latitude and
171
+ # longitude coordinates, represented in string with any of the listed
172
+ # {ways of writing
173
+ # coordinates}[http://en.wikipedia.org/wiki/Geographic_coordinate_conversion].
174
+ # * +:datetime+ - The value is a +DateTime+.
175
+ # * +:number+ - The value is a +Numeric+ between -2,147,483,647 and
176
+ # 2,147,483,647. The value will be stored as a double precision
177
+ # floating point value in Cloud Search.
178
+ # +lang+::
179
+ # The language of a string value. Must be a valid {ISO 639-1
180
+ # code}[https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes].
181
+ # (+String+)
182
+ #
183
+ # === Example
184
+ #
185
+ # require "gcloud"
186
+ #
187
+ # gcloud = Gcloud.new
188
+ # search = gcloud.search
189
+ # index = search.index "products"
190
+ #
191
+ # document = index.document "product-sku-000001"
192
+ # document.add "sku", "product-sku-000001", type: :atom
193
+ # document.add "description", "The best T-shirt ever.",
194
+ # type: :text, lang: "en"
195
+ # document.add "description", "<p>The best T-shirt ever.</p>",
196
+ # type: :html, lang: "en"
197
+ # document.add "price", 24.95
198
+ #
199
+ def add name, value, type: nil, lang: nil
200
+ @fields[name].add value, type: type, lang: lang
201
+ end
202
+
203
+ # rubocop:enable Metrics/LineLength
204
+
205
+ ##
206
+ # Deletes a field and all values. (See Fields#delete)
207
+ #
208
+ # === Parameters
209
+ #
210
+ # +name+::
211
+ # The name of the field. (+String+)
212
+ #
213
+ # === Example
214
+ #
215
+ # require "gcloud"
216
+ #
217
+ # gcloud = Gcloud.new
218
+ # search = gcloud.search
219
+ # index = search.index "products"
220
+ #
221
+ # document = index.document "product-sku-000001"
222
+ # document.delete "description"
223
+ #
224
+ def delete name, &block
225
+ @fields.delete name, &block
226
+ end
227
+
228
+ ##
229
+ # Calls block once for each field, passing the field name and values pair
230
+ # as parameters. If no block is given an enumerator is returned instead.
231
+ # (See Fields#each)
232
+ #
233
+ # === Example
234
+ #
235
+ # require "gcloud"
236
+ #
237
+ # gcloud = Gcloud.new
238
+ # search = gcloud.search
239
+ # index = search.index "products"
240
+ #
241
+ # document = index.document "product-sku-000001"
242
+ # puts "The document #{document.doc_id} has the following fields:"
243
+ # document.each do |name, values|
244
+ # puts "* #{name}:"
245
+ # values.each do |value|
246
+ # puts " * #{value} (#{value.type})"
247
+ # end
248
+ # end
249
+ #
250
+ def each &block
251
+ @fields.each(&block)
252
+ end
253
+
254
+ ##
255
+ # Returns a new array populated with all the field names.
256
+ # (See Fields#names)
257
+ #
258
+ # require "gcloud"
259
+ #
260
+ # gcloud = Gcloud.new
261
+ # search = gcloud.search
262
+ # index = search.index "products"
263
+ #
264
+ # document = index.document "product-sku-000001"
265
+ # puts "The document #{document.doc_id} has the following fields:"
266
+ # document.names.each do |name|
267
+ # puts "* #{name}:"
268
+ # end
269
+ #
270
+ def names
271
+ @fields.names
272
+ end
273
+
274
+ ##
275
+ # Override to keep working in interactive shells manageable.
276
+ def inspect #:nodoc:
277
+ insp_rank = ""
278
+ insp_rank = ", rank: #{rank}" if rank
279
+ insp_fields = ", fields: (#{fields.names.map(&:inspect).join ', '})"
280
+ "#{self.class}(doc_id: #{doc_id.inspect}#{insp_rank}#{insp_fields})"
281
+ end
282
+
283
+ ##
284
+ # New Document from a raw data object.
285
+ def self.from_hash hash #:nodoc:
286
+ doc = new
287
+ doc.instance_variable_set "@raw", hash
288
+ doc.instance_variable_set "@fields", Fields.from_raw(hash["fields"])
289
+ doc
290
+ end
291
+
292
+ ##
293
+ # Returns the Document data as a hash
294
+ def to_hash #:nodoc:
295
+ hash = @raw.dup
296
+ hash["fields"] = @fields.to_raw
297
+ hash
298
+ end
299
+ end
300
+ end
301
+ end