cloudflare 4.3.0 → 4.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,77 +1,111 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # This implements the Worker KV Store API
4
- # https://api.cloudflare.com/#workers-kv-namespace-properties
3
+ # Released under the MIT License.
4
+ # Copyright, 2019, by Rob Widmer.
5
+ # Copyright, 2019-2024, by Samuel Williams.
6
+ # Copyright, 2021, by Terry Kerr.
5
7
 
6
- require_relative '../paginate'
7
- require_relative '../representation'
8
- require_relative 'rest_wrapper'
8
+ require_relative "../paginate"
9
+ require_relative "../representation"
10
+ require_relative "wrapper"
9
11
 
10
12
  module Cloudflare
11
13
  module KV
12
14
  class Key < Representation
13
15
  def name
14
- value[:name]
16
+ result[:name]
15
17
  end
16
18
  end
17
-
19
+
20
+ class Value < Representation[Wrapper]
21
+ include Async::REST::Representation::Mutable
22
+
23
+ def put(value)
24
+ self.class.put(@resource, value) do |resource, response|
25
+ value = response.read
26
+
27
+ return value[:success]
28
+ end
29
+ end
30
+ end
31
+
18
32
  class Keys < Representation
19
33
  include Paginate
20
-
34
+
21
35
  def representation
22
36
  Key
23
37
  end
24
38
  end
25
-
39
+
26
40
  class Namespace < Representation
41
+ include Async::REST::Representation::Mutable
42
+
27
43
  def delete_value(name)
28
44
  value_representation(name).delete.success?
29
45
  end
30
-
46
+
31
47
  def id
32
- value[:id]
48
+ result[:id]
33
49
  end
34
-
50
+
35
51
  def keys
36
- self.with(Keys, path: 'keys')
52
+ self.with(Keys, path: "keys")
37
53
  end
38
-
54
+
39
55
  def read_value(name)
40
56
  value_representation(name).value
41
57
  end
42
-
58
+
43
59
  def rename(new_title)
44
- put(title: new_title)
45
- value[:title] = new_title
60
+ self.class.put(@resource, title: new_title) do |resource, response|
61
+ value = response.read
62
+
63
+ if value[:success]
64
+ result[:title] = new_title
65
+ else
66
+ raise RequestError.new(resource, value)
67
+ end
68
+ end
46
69
  end
47
-
70
+
48
71
  def title
49
- value[:title]
72
+ result[:title]
50
73
  end
51
-
74
+
52
75
  def write_value(name, value)
53
- value_representation(name).put(value).success?
76
+ value_representation(name).put(value)
54
77
  end
55
-
78
+
56
79
  private
57
-
80
+
58
81
  def value_representation(name)
59
- @representation_class ||= Representation[RESTWrapper]
60
- self.with(@representation_class, path: "values/#{name}")
82
+ self.with(Value, path: "values/#{name}/")
61
83
  end
62
84
  end
63
-
85
+
64
86
  class Namespaces < Representation
65
87
  include Paginate
66
-
88
+
67
89
  def representation
68
90
  Namespace
69
91
  end
70
-
71
- def create(title)
72
- represent_message(post(title: title))
92
+
93
+ def create(title, **options)
94
+ payload = {title: title, **options}
95
+
96
+ Namespace.post(@resource, payload) do |resource, response|
97
+ value = response.read
98
+ result = value[:result]
99
+ metadata = response.headers
100
+
101
+ if id = result[:id]
102
+ resource = resource.with(path: id)
103
+ end
104
+
105
+ Namespace.new(resource, value: value, metadata: metadata)
106
+ end
73
107
  end
74
-
108
+
75
109
  def find_by_title(title)
76
110
  each.find {|namespace| namespace.title == title }
77
111
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2021, by Terry Kerr.
5
+ # Copyright, 2024, by Samuel Williams.
6
+
7
+ require "json"
8
+
9
+ module Cloudflare
10
+ module KV
11
+ class Wrapper < Cloudflare::Wrapper
12
+ APPLICATION_OCTET_STREAM = "application/octet-stream"
13
+ def prepare_request(request, payload)
14
+ request.headers.add("accept", APPLICATION_OCTET_STREAM)
15
+
16
+ if payload
17
+ request.headers["content-type"] = APPLICATION_OCTET_STREAM
18
+
19
+ request.body = ::Protocol::HTTP::Body::Buffered.new([payload.to_s])
20
+ end
21
+ end
22
+
23
+ def parser_for(response)
24
+ if response.headers["content-type"].start_with?(APPLICATION_OCTET_STREAM)
25
+ OctetParser
26
+ else
27
+ super
28
+ end
29
+ end
30
+
31
+ class OctetParser < ::Protocol::HTTP::Body::Wrapper
32
+ def join
33
+ super.force_encoding(Encoding::BINARY)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,31 +1,16 @@
1
- # Copyright, 2019, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining a copy
4
- # of this software and associated documentation files (the "Software"), to deal
5
- # in the Software without restriction, including without limitation the rights
6
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- # copies of the Software, and to permit persons to whom the Software is
8
- # furnished to do so, subject to the following conditions:
9
- #
10
- # The above copyright notice and this permission notice shall be included in
11
- # all copies or substantial portions of the Software.
12
- #
13
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- # THE SOFTWARE.
1
+ # frozen_string_literal: true
20
2
 
21
- require_relative 'representation'
22
- require_relative 'paginate'
3
+ # Released under the MIT License.
4
+ # Copyright, 2019-2024, by Samuel Williams.
5
+
6
+ require_relative "representation"
7
+ require_relative "paginate"
23
8
 
24
9
  module Cloudflare
25
10
  module Logs
26
11
  class Entry < Representation
27
12
  def to_s
28
- "#{value[:rayid]}-#{value[:ClientRequestURI]}"
13
+ "#{result[:rayid]}-#{result[:ClientRequestURI]}"
29
14
  end
30
15
  end
31
16
 
@@ -38,4 +23,3 @@ module Cloudflare
38
23
  end
39
24
  end
40
25
  end
41
-
@@ -1,52 +1,40 @@
1
- # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining a copy
4
- # of this software and associated documentation files (the "Software"), to deal
5
- # in the Software without restriction, including without limitation the rights
6
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- # copies of the Software, and to permit persons to whom the Software is
8
- # furnished to do so, subject to the following conditions:
9
- #
10
- # The above copyright notice and this permission notice shall be included in
11
- # all copies or substantial portions of the Software.
12
- #
13
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- # THE SOFTWARE.
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2018-2024, by Samuel Williams.
5
+ # Copyright, 2019, by Rob Widmer.
20
6
 
21
7
  module Cloudflare
22
8
  module Paginate
23
9
  include Enumerable
24
-
10
+
25
11
  def each(page: 1, per_page: 50, **parameters)
26
12
  return to_enum(:each, page: page, per_page: per_page, **parameters) unless block_given?
27
-
13
+
28
14
  while true
29
- zones = @resource.get(self.class, page: page, per_page: per_page, **parameters)
30
-
31
- break if zones.empty?
32
-
33
- Array(zones.value).each do |attributes|
34
- yield represent(zones.metadata, attributes)
15
+ resource = @resource.with(parameters: {page: page, per_page: per_page, **parameters})
16
+
17
+ response = self.class.get(resource)
18
+
19
+ break if response.empty?
20
+
21
+ response.results.each do |attributes|
22
+ yield represent(response.metadata, attributes)
35
23
  end
36
-
24
+
37
25
  page += 1
38
-
26
+
39
27
  # Was this the last page?
40
- break if zones.value.size < per_page
28
+ break if response.results.size < per_page
41
29
  end
42
30
  end
43
-
31
+
44
32
  def empty?
45
33
  self.value.empty?
46
34
  end
47
-
35
+
48
36
  def find_by_id(id)
49
- representation.new(@resource.with(path: id))
37
+ representation.new(@resource.with(path: "#{id}/"))
50
38
  end
51
39
  end
52
40
  end
@@ -1,111 +1,87 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright, 2012, by Marcin Prokop.
4
- # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
5
- #
6
- # Permission is hereby granted, free of charge, to any person obtaining a copy
7
- # of this software and associated documentation files (the "Software"), to deal
8
- # in the Software without restriction, including without limitation the rights
9
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- # copies of the Software, and to permit persons to whom the Software is
11
- # furnished to do so, subject to the following conditions:
12
- #
13
- # The above copyright notice and this permission notice shall be included in
14
- # all copies or substantial portions of the Software.
15
- #
16
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
- # THE SOFTWARE.
3
+ # Released under the MIT License.
4
+ # Copyright, 2017-2024, by Samuel Williams.
5
+ # Copyright, 2018, by Leonhardt Wille.
6
+ # Copyright, 2019, by Rob Widmer.
23
7
 
24
- require 'json'
8
+ require "json"
25
9
 
26
- require 'async/rest/representation'
10
+ require "async/rest/representation"
11
+ require "async/rest/wrapper/json"
27
12
 
28
13
  module Cloudflare
29
14
  class RequestError < StandardError
30
- def initialize(resource, errors)
31
- super("#{resource}: #{errors.map{|attributes| attributes[:message]}.join(', ')}")
32
-
33
- @representation = representation
15
+ def initialize(request, value)
16
+ if error = value[:error]
17
+ super("#{request}: #{error}")
18
+ elsif errors = value[:errors]
19
+ super("#{request}: #{errors.map{|attributes| attributes[:message]}.join(', ')}")
20
+ else
21
+ super("#{request}: #{value.inspect}")
22
+ end
23
+
24
+ @value = value
34
25
  end
35
-
36
- attr_reader :representation
26
+
27
+ attr :value
37
28
  end
38
-
39
- class Message
40
- def initialize(response)
41
- @response = response
42
- @body = response.read
43
-
44
- # Some endpoints return the value instead of a message object (like KV reads)
45
- @body = { success: true, result: @body } unless @body.is_a?(Hash)
46
- end
47
-
48
- attr :response
49
- attr :body
50
-
51
- def headers
52
- @response.headers
53
- end
54
-
55
- def result
56
- @body[:result]
57
- end
58
-
59
- def read
60
- @body[:result]
61
- end
62
-
63
- def results
64
- Array(result)
65
- end
66
-
67
- def errors
68
- @body[:errors]
69
- end
70
-
71
- def messages
72
- @body[:messages]
73
- end
74
-
75
- def success?
76
- @body[:success]
29
+
30
+ class Wrapper < Async::REST::Wrapper::JSON
31
+ def process_response(request, response)
32
+ super
33
+
34
+ if response.failure?
35
+ raise RequestError.new(request, response.read)
36
+ end
77
37
  end
78
38
  end
79
-
39
+
80
40
  class Representation < Async::REST::Representation
81
- def process_response(*)
82
- message = Message.new(super)
83
-
84
- unless message.success?
85
- raise RequestError.new(@resource, message.errors)
86
- end
87
-
88
- return message
89
- end
90
-
41
+ WRAPPER = Wrapper.new
42
+
91
43
  def representation
92
44
  Representation
93
45
  end
94
-
46
+
95
47
  def represent(metadata, attributes)
96
48
  resource = @resource.with(path: attributes[:id])
97
-
98
- representation.new(resource, metadata: metadata, value: attributes)
49
+
50
+ representation.new(resource, metadata: metadata, value: {
51
+ success: true, result: attributes
52
+ })
99
53
  end
100
-
54
+
101
55
  def represent_message(message)
102
56
  represent(message.headers, message.result)
103
57
  end
104
-
58
+
59
+ def result
60
+ value[:result]
61
+ end
62
+
105
63
  def to_hash
106
- if value.is_a?(Hash)
107
- return value
108
- end
64
+ result
65
+ end
66
+
67
+ def to_id
68
+ {id: result[:id]}
69
+ end
70
+
71
+ def results
72
+ Array(result)
73
+ end
74
+
75
+ def errors
76
+ value[:errors]
77
+ end
78
+
79
+ def messages
80
+ value[:messages]
81
+ end
82
+
83
+ def success?
84
+ value[:success]
109
85
  end
110
86
  end
111
87
  end
@@ -1,36 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright, 2012, by Marcin Prokop.
4
- # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
5
- #
6
- # Permission is hereby granted, free of charge, to any person obtaining a copy
7
- # of this software and associated documentation files (the "Software"), to deal
8
- # in the Software without restriction, including without limitation the rights
9
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- # copies of the Software, and to permit persons to whom the Software is
11
- # furnished to do so, subject to the following conditions:
12
- #
13
- # The above copyright notice and this permission notice shall be included in
14
- # all copies or substantial portions of the Software.
15
- #
16
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
- # THE SOFTWARE.
3
+ # Released under the MIT License.
4
+ # Copyright, 2017-2024, by Samuel Williams.
5
+ # Copyright, 2018, by Leonhardt Wille.
23
6
 
24
- require_relative 'representation'
7
+ require_relative "representation"
25
8
 
26
9
  module Cloudflare
27
10
  class User < Representation
28
11
  def id
29
- value[:id]
12
+ result[:id]
30
13
  end
31
14
 
32
15
  def email
33
- value[:email]
16
+ result[:email]
34
17
  end
35
18
  end
36
19
  end
@@ -1,26 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright, 2012, by Marcin Prokop.
4
- # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
5
- #
6
- # Permission is hereby granted, free of charge, to any person obtaining a copy
7
- # of this software and associated documentation files (the "Software"), to deal
8
- # in the Software without restriction, including without limitation the rights
9
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- # copies of the Software, and to permit persons to whom the Software is
11
- # furnished to do so, subject to the following conditions:
12
- #
13
- # The above copyright notice and this permission notice shall be included in
14
- # all copies or substantial portions of the Software.
15
- #
16
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
- # THE SOFTWARE.
3
+ # Released under the MIT License.
4
+ # Copyright, 2014-2016, by Marcin Prokop.
5
+ # Copyright, 2014-2024, by Samuel Williams.
6
+ # Copyright, 2015, by Kyle Corbitt.
7
+ # Copyright, 2018, by Leonhardt Wille.
8
+ # Copyright, 2018, by Casey Lopez.
23
9
 
24
10
  module Cloudflare
25
- VERSION = '4.3.0'
11
+ VERSION = "4.4.0"
26
12
  end
@@ -1,65 +1,59 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright, 2012, by Marcin Prokop.
4
- # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
5
- # Copyright, 2017, by David Rosenbloom. <http://artifactory.com>
6
- #
7
- # Permission is hereby granted, free of charge, to any person obtaining a copy
8
- # of this software and associated documentation files (the "Software"), to deal
9
- # in the Software without restriction, including without limitation the rights
10
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- # copies of the Software, and to permit persons to whom the Software is
12
- # furnished to do so, subject to the following conditions:
13
- #
14
- # The above copyright notice and this permission notice shall be included in
15
- # all copies or substantial portions of the Software.
16
- #
17
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
- # THE SOFTWARE.
3
+ # Released under the MIT License.
4
+ # Copyright, 2017-2024, by Samuel Williams.
5
+ # Copyright, 2017, by Denis Sadomowski.
6
+ # Copyright, 2017, by 莫粒.
7
+ # Copyright, 2018, by Leonhardt Wille.
8
+ # Copyright, 2018, by Michael Kalygin.
9
+ # Copyright, 2018, by Sherman Koa.
10
+ # Copyright, 2018, by Kugayama Nana.
11
+ # Copyright, 2018, by Casey Lopez.
12
+ # Copyright, 2019, by Akinori Musha.
13
+ # Copyright, 2019, by Rob Widmer.
24
14
 
25
- require_relative 'representation'
26
- require_relative 'paginate'
15
+ require_relative "representation"
16
+ require_relative "paginate"
27
17
 
28
- require_relative 'custom_hostnames'
29
- require_relative 'firewall'
30
- require_relative 'dns'
31
- require_relative 'logs'
18
+ require_relative "custom_hostnames"
19
+ require_relative "firewall"
20
+ require_relative "dns"
21
+ require_relative "logs"
32
22
 
33
23
  module Cloudflare
34
24
  class Zone < Representation
25
+ include Async::REST::Representation::Mutable
26
+
35
27
  def custom_hostnames
36
- self.with(CustomHostnames, path: 'custom_hostnames')
28
+ self.with(CustomHostnames, path: "custom_hostnames")
37
29
  end
38
30
 
39
31
  def dns_records
40
- self.with(DNS::Records, path: 'dns_records')
32
+ self.with(DNS::Records, path: "dns_records")
41
33
  end
42
34
 
43
35
  def firewall_rules
44
- self.with(Firewall::Rules, path: 'firewall/access_rules/rules')
36
+ self.with(Firewall::Rules, path: "firewall/access_rules/rules")
45
37
  end
46
38
 
47
39
  def logs
48
- self.with(Logs::Received, path: 'logs/received')
40
+ self.with(Logs::Received, path: "logs/received")
49
41
  end
50
42
 
51
- DEFAULT_PURGE_CACHE_PARAMS = {
43
+ DEFAULT_PURGE_CACHE_PARAMETERS = {
52
44
  purge_everything: true
53
45
  }.freeze
54
46
 
55
- def purge_cache(parameters = DEFAULT_PURGE_CACHE_PARAMS)
56
- self.with(Zone, path: 'purge_cache').post(parameters)
47
+ def purge_cache(**options)
48
+ if options.empty?
49
+ options = DEFAULT_PURGE_CACHE_PARAMETERS
50
+ end
57
51
 
58
- return self
52
+ self.class.post(@resource.with(path: "purge_cache"), options)
59
53
  end
60
54
 
61
55
  def name
62
- value[:name]
56
+ result[:name]
63
57
  end
64
58
 
65
59
  alias to_s name
@@ -72,12 +66,24 @@ module Cloudflare
72
66
  Zone
73
67
  end
74
68
 
75
- def create(name, account, jump_start = false)
76
- represent_message(self.post(name: name, account: account.to_hash, jump_start: jump_start))
69
+ def create(name, account, jump_start: false, **options)
70
+ payload = {name: name, account: account.to_id, jump_start: jump_start, **options}
71
+
72
+ Zone.post(@resource, payload) do |resource, response|
73
+ value = response.read
74
+ result = value[:result]
75
+ metadata = response.headers
76
+
77
+ if id = result[:id]
78
+ resource = resource.with(path: id)
79
+ end
80
+
81
+ Zone.new(resource, value: value, metadata: metadata)
82
+ end
77
83
  end
78
-
84
+
79
85
  def find_by_name(name)
80
- each(name: name).first
86
+ each(name: name).find{|zone| zone.name == name}
81
87
  end
82
88
  end
83
89
  end