puppet_forge 1.0.6 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +23 -0
- data/MAINTAINERS +13 -0
- data/README.md +48 -6
- data/lib/puppet_forge.rb +4 -0
- data/lib/puppet_forge/connection.rb +81 -0
- data/lib/puppet_forge/connection/connection_failure.rb +26 -0
- data/lib/puppet_forge/error.rb +34 -0
- data/lib/{her → puppet_forge}/lazy_accessors.rb +20 -27
- data/lib/{her → puppet_forge}/lazy_relations.rb +28 -9
- data/lib/puppet_forge/middleware/symbolify_json.rb +72 -0
- data/lib/puppet_forge/tar.rb +10 -0
- data/lib/puppet_forge/tar/mini.rb +81 -0
- data/lib/puppet_forge/unpacker.rb +68 -0
- data/lib/puppet_forge/v3.rb +11 -0
- data/lib/puppet_forge/v3/base.rb +106 -73
- data/lib/puppet_forge/v3/base/paginated_collection.rb +23 -14
- data/lib/puppet_forge/v3/metadata.rb +197 -0
- data/lib/puppet_forge/v3/module.rb +2 -1
- data/lib/puppet_forge/v3/release.rb +33 -8
- data/lib/puppet_forge/v3/user.rb +2 -0
- data/lib/puppet_forge/version.rb +1 -1
- data/puppet_forge.gemspec +6 -3
- data/spec/fixtures/v3/modules/puppetlabs-apache.json +21 -1
- data/spec/fixtures/v3/releases/puppetlabs-apache-0.0.1.json +4 -1
- data/spec/integration/forge/v3/module_spec.rb +79 -0
- data/spec/integration/forge/v3/release_spec.rb +75 -0
- data/spec/integration/forge/v3/user_spec.rb +70 -0
- data/spec/spec_helper.rb +15 -8
- data/spec/unit/forge/connection/connection_failure_spec.rb +30 -0
- data/spec/unit/forge/connection_spec.rb +53 -0
- data/spec/unit/{her → forge}/lazy_accessors_spec.rb +20 -13
- data/spec/unit/{her → forge}/lazy_relations_spec.rb +60 -46
- data/spec/unit/forge/middleware/symbolify_json_spec.rb +63 -0
- data/spec/unit/forge/tar/mini_spec.rb +85 -0
- data/spec/unit/forge/tar_spec.rb +9 -0
- data/spec/unit/forge/unpacker_spec.rb +58 -0
- data/spec/unit/forge/v3/base/paginated_collection_spec.rb +68 -46
- data/spec/unit/forge/v3/base_spec.rb +1 -1
- data/spec/unit/forge/v3/metadata_spec.rb +300 -0
- data/spec/unit/forge/v3/module_spec.rb +14 -36
- data/spec/unit/forge/v3/release_spec.rb +9 -30
- data/spec/unit/forge/v3/user_spec.rb +7 -7
- metadata +127 -41
- checksums.yaml +0 -7
- data/lib/puppet_forge/middleware/json_for_her.rb +0 -37
data/CHANGELOG.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Change Log
|
2
|
+
|
3
|
+
Starting with v2.0.0, all notable changes to this project will be documented in this file.
|
4
|
+
This project adheres to [Semantic Versioning](http://semver.org/).i
|
5
|
+
|
6
|
+
## v2.0.0 - 2015-08-13
|
7
|
+
|
8
|
+
### Added
|
9
|
+
|
10
|
+
* PuppetForge::V3::Release can now verify the md5, unpack, and install a release tarball.
|
11
|
+
* PuppetForge::Middleware::SymbolifyJson to change Faraday response hash keys into symbols.
|
12
|
+
* PuppetForge::V3::Metadata to represent a release's metadata as an object.
|
13
|
+
* PuppetForge::Connection to provide Faraday connections.
|
14
|
+
|
15
|
+
### Changed
|
16
|
+
|
17
|
+
* Failed API requests, such as those for a module that doesn't exist, throw a Faraday::ResourceNotFound error.
|
18
|
+
* API requests are sent through Faraday directly rather than through Her.
|
19
|
+
* PuppetForge::V3::Base#where and PuppetForge::V3::Base#all now send an API request immediately and return a paginated collection.
|
20
|
+
|
21
|
+
### Removed
|
22
|
+
|
23
|
+
* Depency on Her (also removes dependency on ActiveSupport).
|
data/MAINTAINERS
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
If you'd like to make a change to this library, you should contact at least one
|
2
|
+
of the maintainers listed below. They will be able to discuss the change with
|
3
|
+
you and decide the next best step. The maintainers will also be notified of pull
|
4
|
+
requests as described in the README. This maintainers file should be kept up to
|
5
|
+
date with the current maintainers of the project.
|
6
|
+
|
7
|
+
If you need to contact the maintainers of this project you should email
|
8
|
+
info@puppetlabs.com.
|
9
|
+
|
10
|
+
Alex Dreyer, Puppet Labs
|
11
|
+
Anderson Mills, Puppet Labs
|
12
|
+
Jesse Scott, Puppet Labs
|
13
|
+
|
data/README.md
CHANGED
@@ -23,8 +23,8 @@ Or install it yourself as:
|
|
23
23
|
|
24
24
|
##Dependencies
|
25
25
|
|
26
|
-
* [
|
27
|
-
* [Typhoeus](https://github.com/typhoeus/typhoeus) ~> 0.
|
26
|
+
* [Faraday]() ~> 0.9.0
|
27
|
+
* [Typhoeus](https://github.com/typhoeus/typhoeus) ~> 0.7.0 (optional)
|
28
28
|
|
29
29
|
Typhoeus will be used as the HTTP adapter if it is available, otherwise
|
30
30
|
Net::HTTP will be used. We recommend using Typhoeus for production-level
|
@@ -78,12 +78,13 @@ resource model are documented on the [Resource Reference][resource_ref] page.
|
|
78
78
|
|
79
79
|
###Basic Interface
|
80
80
|
|
81
|
-
Each of the models uses
|
82
|
-
|
83
|
-
interactions function as intended.
|
81
|
+
Each of the models uses ActiveRecord-like REST functionality to map over the Forge API endpoints.
|
82
|
+
Most simple ActiveRecord-style interactions function as intended.
|
84
83
|
|
85
84
|
Currently, only unauthenticated read-only actions are supported.
|
86
85
|
|
86
|
+
The methods find, where, and all immediately make one API request.
|
87
|
+
|
87
88
|
``` ruby
|
88
89
|
# Find a Resource by Slug
|
89
90
|
PuppetForge::User.find('puppetlabs') # => #<Forge::V3::User(/v3/users/puppetlabs)>
|
@@ -92,10 +93,50 @@ PuppetForge::User.find('puppetlabs') # => #<Forge::V3::User(/v3/users/puppetlabs
|
|
92
93
|
PuppetForge::Module.all # See "Paginated Collections" below for important info about enumerating resource sets.
|
93
94
|
|
94
95
|
# Find Resources with Conditions
|
95
|
-
PuppetForge::Module.where(query: 'apache')
|
96
|
+
PuppetForge::Module.where(query: 'apache') # See "Paginated Collections" below for important info about enumerating resource sets.
|
96
97
|
PuppetForge::Module.where(query: 'apache').first # => #<Forge::V3::Module(/v3/modules/puppetlabs-apache)>
|
97
98
|
```
|
98
99
|
|
100
|
+
For compatibility with older versions of the puppet_forge gem, the following two methods are functionally equivalent.
|
101
|
+
|
102
|
+
``` ruby
|
103
|
+
PuppetForge::Module.where(query: 'apache')
|
104
|
+
PuppetForge::Module.where(query: 'apache').all # This method is deprecated and not recommended
|
105
|
+
```
|
106
|
+
|
107
|
+
####Errors
|
108
|
+
|
109
|
+
All API Requests (whether via find, where, or all) will raise a Faraday::ResourceNotFound error if the request fails.
|
110
|
+
|
111
|
+
|
112
|
+
###Installing a Release
|
113
|
+
|
114
|
+
A release tarball can be downloaded and installed by following the steps below.
|
115
|
+
|
116
|
+
``` ruby
|
117
|
+
release_slug = "puppetlabs-apache-1.6.0"
|
118
|
+
release_tarball = release_slug + ".tar.gz"
|
119
|
+
dest_dir = "/path/to/install/directory"
|
120
|
+
tmp_dir = "/path/to/tmpdir"
|
121
|
+
|
122
|
+
# Fetch Release information from API
|
123
|
+
# @raise Faraday::ResourceNotFound error if the given release does not exist
|
124
|
+
release = PuppetForge::Release.find release_slug
|
125
|
+
|
126
|
+
# Download the Release tarball
|
127
|
+
# @raise PuppetForge::ReleaseNotFound error if the given release does not exist
|
128
|
+
release.download(Pathname(release_tarball))
|
129
|
+
|
130
|
+
# Verify the MD5
|
131
|
+
# @raise PuppetForge::V3::Release::ChecksumMismatch error if the file's md5 does not match the API information
|
132
|
+
release.verify(Pathname(release_tarball))
|
133
|
+
|
134
|
+
# Unpack the files to a given directory
|
135
|
+
# @raise RuntimeError if it fails to extract the contents of the release tarball
|
136
|
+
PuppetForge::Unpacker.unpack(release_tarball, dest_dir, tmp_dir)
|
137
|
+
```
|
138
|
+
|
139
|
+
|
99
140
|
###Paginated Collections
|
100
141
|
|
101
142
|
The Forge API only returns paginated collections as of v3.
|
@@ -170,3 +211,4 @@ to create a free account to add new tickets.
|
|
170
211
|
|
171
212
|
* Pieter van de Bruggen, Puppet Labs
|
172
213
|
* Jesse Scott, Puppet Labs
|
214
|
+
* Austin Blatt, Puppet Labs
|
data/lib/puppet_forge.rb
CHANGED
@@ -8,8 +8,12 @@ module PuppetForge
|
|
8
8
|
|
9
9
|
self.host = 'https://forgeapi.puppetlabs.com'
|
10
10
|
|
11
|
+
require 'puppet_forge/tar'
|
12
|
+
require 'puppet_forge/unpacker'
|
11
13
|
require 'puppet_forge/v3'
|
12
14
|
|
15
|
+
const_set :Metadata, PuppetForge::V3::Metadata
|
16
|
+
|
13
17
|
const_set :User, PuppetForge::V3::User
|
14
18
|
const_set :Module, PuppetForge::V3::Module
|
15
19
|
const_set :Release, PuppetForge::V3::Release
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'puppet_forge/connection/connection_failure'
|
2
|
+
|
3
|
+
require 'faraday'
|
4
|
+
require 'faraday_middleware'
|
5
|
+
require 'puppet_forge/middleware/symbolify_json'
|
6
|
+
|
7
|
+
module PuppetForge
|
8
|
+
# Provide a common mixin for adding a HTTP connection to classes.
|
9
|
+
#
|
10
|
+
# This module provides a common method for creating HTTP connections as well
|
11
|
+
# as reusing a single connection object between multiple classes. Including
|
12
|
+
# classes can invoke #conn to get a reasonably configured HTTP connection.
|
13
|
+
# Connection objects can be passed with the #conn= method.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# class HTTPThing
|
17
|
+
# include PuppetForge::Connection
|
18
|
+
# end
|
19
|
+
# thing = HTTPThing.new
|
20
|
+
# thing.conn = thing.make_connection('https://non-standard-forge.site')
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
module Connection
|
24
|
+
|
25
|
+
attr_writer :conn
|
26
|
+
|
27
|
+
USER_AGENT = "#{PuppetForge.user_agent} PuppetForge.gem/#{PuppetForge::VERSION} Faraday/#{Faraday::VERSION} Ruby/#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_PLATFORM})".strip
|
28
|
+
|
29
|
+
def self.authorization=(token)
|
30
|
+
@authorization = token
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.authorization
|
34
|
+
@authorization
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [Faraday::Connection] An existing Faraday connection if one was
|
38
|
+
# already set, otherwise a new Faraday connection.
|
39
|
+
def conn
|
40
|
+
@conn ||= default_connection
|
41
|
+
end
|
42
|
+
|
43
|
+
def default_connection
|
44
|
+
|
45
|
+
begin
|
46
|
+
# Use Typhoeus if available.
|
47
|
+
Gem::Specification.find_by_name('typhoeus', '~> 0.6')
|
48
|
+
require 'typhoeus/adapters/faraday'
|
49
|
+
adapter = :typhoeus
|
50
|
+
rescue Gem::LoadError
|
51
|
+
adapter = Faraday.default_adapter
|
52
|
+
end
|
53
|
+
|
54
|
+
make_connection(PuppetForge.host, [adapter])
|
55
|
+
end
|
56
|
+
module_function :default_connection
|
57
|
+
|
58
|
+
# Generate a new Faraday connection for the given URL.
|
59
|
+
#
|
60
|
+
# @param url [String] the base URL for this connection
|
61
|
+
# @return [Faraday::Connection]
|
62
|
+
def make_connection(url, adapter_args = nil, opts = {})
|
63
|
+
adapter_args ||= [Faraday.default_adapter]
|
64
|
+
options = { :headers => { :user_agent => USER_AGENT } }.merge(opts)
|
65
|
+
|
66
|
+
if token = PuppetForge::Connection.authorization
|
67
|
+
options[:headers][:authorization] = token
|
68
|
+
end
|
69
|
+
|
70
|
+
Faraday.new(url, options) do |builder|
|
71
|
+
builder.use PuppetForge::Middleware::SymbolifyJson
|
72
|
+
builder.response(:json, :content_type => /\bjson$/)
|
73
|
+
builder.response(:raise_error)
|
74
|
+
builder.use(:connection_failure)
|
75
|
+
|
76
|
+
builder.adapter(*adapter_args)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
module_function :make_connection
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
module PuppetForge
|
4
|
+
module Connection
|
5
|
+
# Wrap Faraday connection failures to include the host and optional proxy
|
6
|
+
# in use for the failed connection.
|
7
|
+
class ConnectionFailure < Faraday::Middleware
|
8
|
+
def call(env)
|
9
|
+
@app.call(env)
|
10
|
+
rescue Faraday::ConnectionFailed => e
|
11
|
+
baseurl = env[:url].dup
|
12
|
+
baseurl.path = ''
|
13
|
+
errmsg = "Unable to connect to #{baseurl.to_s}"
|
14
|
+
if proxy = env[:request][:proxy]
|
15
|
+
errmsg << " (using proxy #{proxy.uri.to_s})"
|
16
|
+
end
|
17
|
+
errmsg << ": #{e.message}"
|
18
|
+
m = Faraday::ConnectionFailed.new(errmsg)
|
19
|
+
m.set_backtrace(e.backtrace)
|
20
|
+
raise m
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
Faraday::Middleware.register_middleware(:connection_failure => lambda { PuppetForge::Connection::ConnectionFailure })
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module PuppetForge
|
2
|
+
class Error < RuntimeError
|
3
|
+
attr_accessor :original
|
4
|
+
def initialize(message, original=nil)
|
5
|
+
super(message)
|
6
|
+
@original = original
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class ExecutionFailure < PuppetForge::Error
|
11
|
+
end
|
12
|
+
|
13
|
+
class InvalidPathInPackageError < PuppetForge::Error
|
14
|
+
def initialize(options)
|
15
|
+
@entry_path = options[:entry_path]
|
16
|
+
@directory = options[:directory]
|
17
|
+
super "Attempt to install file into #{@entry_path.inspect} under #{@directory.inspect}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def multiline
|
21
|
+
<<-MSG.strip
|
22
|
+
Could not install package
|
23
|
+
Package attempted to install file into
|
24
|
+
#{@entry_path.inspect} under #{@directory.inspect}.
|
25
|
+
MSG
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class ModuleNotFound < PuppetForge::Error
|
30
|
+
end
|
31
|
+
|
32
|
+
class ReleaseNotFound < PuppetForge::Error
|
33
|
+
end
|
34
|
+
end
|
@@ -1,18 +1,12 @@
|
|
1
|
-
|
2
|
-
# @see http://her-rb.org/
|
3
|
-
module Her
|
4
|
-
|
5
|
-
# ActiveRecord-like interface for RESTful models.
|
6
|
-
# @see http://her-rb.org/#usage/activerecord-like-methods
|
7
|
-
module Model; end
|
1
|
+
module PuppetForge
|
8
2
|
|
9
3
|
# When dealing with a remote service, it's reasonably common to receive only
|
10
4
|
# a partial representation of the underlying object, with additional data
|
11
|
-
# available upon request.
|
5
|
+
# available upon request. PuppetForge, by default, provides a convenient interface
|
12
6
|
# for accessing whatever local data is available, but lacks good support for
|
13
7
|
# fleshing out partial representations. In order to build a seamless
|
14
8
|
# interface for both local and remote attriibutes, this module replaces the
|
15
|
-
# default behavior
|
9
|
+
# default behavior with an "updatable" interface.
|
16
10
|
module LazyAccessors
|
17
11
|
|
18
12
|
# Callback for module inclusion.
|
@@ -28,19 +22,25 @@ module Her
|
|
28
22
|
end
|
29
23
|
end
|
30
24
|
|
31
|
-
#
|
25
|
+
# Provide class name for object
|
26
|
+
#
|
27
|
+
def class_name
|
28
|
+
self.class.name.split("::").last.downcase
|
29
|
+
end
|
30
|
+
|
31
|
+
# Override the default #inspect behavior.
|
32
32
|
#
|
33
33
|
# The original behavior actually invokes each attribute accessor, which can
|
34
34
|
# be somewhat problematic when the accessors have been overridden. This
|
35
35
|
# implementation simply reports the contents of the attributes hash.
|
36
36
|
def inspect
|
37
37
|
attrs = attributes.map do |x, y|
|
38
|
-
[ x,
|
38
|
+
[ x, y ].join('=')
|
39
39
|
end
|
40
|
-
"#<#{self.class}(#{
|
40
|
+
"#<#{self.class}(#{uri}) #{attrs.join(' ')}>"
|
41
41
|
end
|
42
42
|
|
43
|
-
# Override the default
|
43
|
+
# Override the default #method_misssing behavior.
|
44
44
|
#
|
45
45
|
# When we receive a {#method_missing} call, one of three things is true:
|
46
46
|
# - the caller is looking up a piece of local data without an accessor
|
@@ -81,22 +81,15 @@ module Her
|
|
81
81
|
return self if @_fetch
|
82
82
|
|
83
83
|
klass = self.class
|
84
|
-
params = { :_method => klass.method_for(:find), :_path => self.request_path }
|
85
84
|
|
86
|
-
klass.request(
|
87
|
-
|
88
|
-
|
89
|
-
parsed.merge!(:_metadata => data[:metadata], :_errors => data[:errors])
|
90
|
-
|
91
|
-
self.send(:initialize, parsed)
|
92
|
-
self.run_callbacks(:find)
|
93
|
-
end
|
85
|
+
response = klass.request("#{self.class_name}s/#{self.slug}")
|
86
|
+
if @_fetch = response.success?
|
87
|
+
self.send(:initialize, response.body)
|
94
88
|
end
|
95
89
|
|
96
90
|
return self
|
97
91
|
end
|
98
92
|
|
99
|
-
|
100
93
|
# A Module subclass for attribute accessors.
|
101
94
|
class AccessorContainer < Module
|
102
95
|
|
@@ -117,19 +110,19 @@ module Her
|
|
117
110
|
# @return [void]
|
118
111
|
def add_attributes(keys)
|
119
112
|
keys.each do |key|
|
120
|
-
next if methods.include?(name =
|
113
|
+
next if methods.include?(name = key)
|
121
114
|
|
122
|
-
define_method(name) do
|
115
|
+
define_method("#{name}") do
|
123
116
|
fetch unless has_attribute?(name)
|
124
117
|
attribute(name)
|
125
118
|
end
|
126
119
|
|
127
|
-
define_method(
|
120
|
+
define_method("#{name}?") do
|
128
121
|
fetch unless has_attribute?(name)
|
129
122
|
has_attribute?(name)
|
130
123
|
end
|
131
124
|
|
132
|
-
define_method(
|
125
|
+
define_method("#{name}=") do |value|
|
133
126
|
fetch unless has_attribute?(name)
|
134
127
|
attributes[name] = value
|
135
128
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
module
|
1
|
+
module PuppetForge
|
2
2
|
|
3
3
|
# This module provides convenience accessors for related resources. Related
|
4
4
|
# classes will include {LazyAccessors}, allowing them to transparently fetch
|
@@ -11,9 +11,24 @@ module Her
|
|
11
11
|
# @private
|
12
12
|
def self.included(base)
|
13
13
|
base.extend(self)
|
14
|
-
base.after_initialize { @_lazy = {} }
|
15
14
|
end
|
16
15
|
|
16
|
+
def parent
|
17
|
+
if self.is_a? Class
|
18
|
+
class_name = self.name
|
19
|
+
else
|
20
|
+
class_name = self.class.name
|
21
|
+
end
|
22
|
+
|
23
|
+
# Get the name of the version module
|
24
|
+
version = class_name.split("::")[-2]
|
25
|
+
|
26
|
+
if version.nil?
|
27
|
+
raise RuntimeError, "Unable to determine the parent PuppetForge version module"
|
28
|
+
end
|
29
|
+
|
30
|
+
PuppetForge.const_get(version)
|
31
|
+
end
|
17
32
|
# @!macro [attach] lazy
|
18
33
|
# @!method $1
|
19
34
|
# Returns a lazily-loaded $1 proxy. To eagerly load this $1, call
|
@@ -32,16 +47,19 @@ module Her
|
|
32
47
|
# @param name [Symbol] the name of the lazy attribute
|
33
48
|
# @param class_name [#to_s] the lazy relation's class name
|
34
49
|
def lazy(name, class_name = name)
|
35
|
-
parent = self.parent
|
36
50
|
klass = (class_name.is_a?(Class) ? class_name : nil)
|
37
|
-
class_name = "#{class_name}"
|
51
|
+
class_name = "#{class_name}"
|
38
52
|
|
39
53
|
define_method(name) do
|
54
|
+
@_lazy ||= {}
|
55
|
+
|
40
56
|
@_lazy[name] ||= begin
|
57
|
+
|
41
58
|
klass ||= parent.const_get(class_name)
|
42
|
-
|
59
|
+
|
60
|
+
klass.send(:include, PuppetForge::LazyAccessors)
|
43
61
|
fetch unless has_attribute?(name)
|
44
|
-
value =
|
62
|
+
value = attributes[name]
|
45
63
|
klass.new(value) if value
|
46
64
|
end
|
47
65
|
end
|
@@ -69,14 +87,15 @@ module Her
|
|
69
87
|
# @param name [Symbol] the name of the lazy collection attribute
|
70
88
|
# @param class_name [#to_s] the lazy relation's class name
|
71
89
|
def lazy_collection(name, class_name = name)
|
72
|
-
parent = self.parent
|
73
90
|
klass = (class_name.is_a?(Class) ? class_name : nil)
|
74
|
-
class_name = "#{class_name}"
|
91
|
+
class_name = "#{class_name}"
|
75
92
|
|
76
93
|
define_method(name) do
|
94
|
+
@_lazy ||= {}
|
95
|
+
|
77
96
|
@_lazy[name] ||= begin
|
78
97
|
klass ||= parent.const_get(class_name)
|
79
|
-
klass.send(:include,
|
98
|
+
klass.send(:include, PuppetForge::LazyAccessors)
|
80
99
|
fetch unless has_attribute?(name)
|
81
100
|
(attribute(name) || []).map { |x| klass.new(x) }
|
82
101
|
end
|