output_attributes 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +28 -0
- data/README.md +214 -0
- data/Rakefile +10 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/lib/output_attributes/version.rb +3 -0
- data/lib/output_attributes.rb +89 -0
- data/output_attributes.gemspec +37 -0
- metadata +114 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5853821c644c05df3134cba84d1fb800540eafe828ee11cbb6e17f4bb1d80fcd
|
4
|
+
data.tar.gz: 5b9acc26991eb9f52a1e93633badb9761564b9393cfa1a5a713f453f15c45e30
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0cd70015e649d210c073ee061b6691823642c6ac5dcb7e03ed84ca5089759f56d9b67ac690223d5e48b94089ffcb5fb9f81daffa8d0c920e25606c0d2105bd2e
|
7
|
+
data.tar.gz: 30cc7fd8b45ea679001cf3e32ad977222ce0643109643b0c021ef4a19b1f5ed577bfbe3a01d84f49d34d5ae0ef66a9e6c7a7a2808b11a1b88a6fd12779b9756a
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
output_attributes (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
coderay (1.1.2)
|
10
|
+
method_source (0.9.2)
|
11
|
+
minitest (5.13.0)
|
12
|
+
pry (0.12.2)
|
13
|
+
coderay (~> 1.1.0)
|
14
|
+
method_source (~> 0.9.0)
|
15
|
+
rake (10.5.0)
|
16
|
+
|
17
|
+
PLATFORMS
|
18
|
+
ruby
|
19
|
+
|
20
|
+
DEPENDENCIES
|
21
|
+
bundler (~> 2.0)
|
22
|
+
minitest (~> 5.0)
|
23
|
+
output_attributes!
|
24
|
+
pry
|
25
|
+
rake (~> 10.0)
|
26
|
+
|
27
|
+
BUNDLED WITH
|
28
|
+
2.0.2
|
data/README.md
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
# OutputAttributes
|
2
|
+
|
3
|
+
This gem provides a class macro that adds `output` helpers when defining your class. I find it jarring to keep `#to_hash` up to date on classes that have many data attributes, and a few helper methods. I often wish to just mark a method as "This method describes my data and should be part of `#to_hash`".
|
4
|
+
|
5
|
+
Behold:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
class Item
|
9
|
+
include OutputAttributes
|
10
|
+
|
11
|
+
def name
|
12
|
+
"The Name"
|
13
|
+
end
|
14
|
+
output :name
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
Item.new.output_attributes
|
19
|
+
# => {:name=>"The Name"}
|
20
|
+
```
|
21
|
+
|
22
|
+
The `output` declaration can come before, during, or after a method definition.
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
class Item
|
26
|
+
include OutputAttributes
|
27
|
+
|
28
|
+
# Before:
|
29
|
+
output :first_name
|
30
|
+
|
31
|
+
def first_name
|
32
|
+
"First Name"
|
33
|
+
end
|
34
|
+
|
35
|
+
# During -- this is my favorite. It leverages the fact that the `def meth` expression returns a symbol... Clever!
|
36
|
+
output def middle_name
|
37
|
+
"Middle Name"
|
38
|
+
end
|
39
|
+
|
40
|
+
# After
|
41
|
+
def last_name
|
42
|
+
"Last Name"
|
43
|
+
end
|
44
|
+
|
45
|
+
output :last_name
|
46
|
+
end
|
47
|
+
|
48
|
+
Item.new.output_attributes
|
49
|
+
# => {:first_name=>"First Name",
|
50
|
+
# :middle_name=>"Middle Name",
|
51
|
+
# :last_name=>"Last Name"}
|
52
|
+
```
|
53
|
+
|
54
|
+
Whatever style works best for you. Stack a bunch on top like typical `attr_readers`. Stash them on the bottom. Decorate them. It's all good.
|
55
|
+
|
56
|
+
Sometimes the method name is not what you want as your output key. `output` takes an optional `from: ` keyword argument. If `from` is a `Symbol`, it will call that method instead:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
class Item
|
60
|
+
include OutputAttributes
|
61
|
+
def name
|
62
|
+
"An Item"
|
63
|
+
end
|
64
|
+
|
65
|
+
output :description, from: :name
|
66
|
+
end
|
67
|
+
|
68
|
+
Item.new.output_attributes
|
69
|
+
# => {:description=>'An Item'}
|
70
|
+
```
|
71
|
+
|
72
|
+
You can also pass a proc or lambda in. The first argument provided to the proc is the instance of the object. This can be helpful if you need just a little extra massaging.
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
class Item
|
76
|
+
include OutputAttributes
|
77
|
+
def name
|
78
|
+
"An Item"
|
79
|
+
end
|
80
|
+
|
81
|
+
def color
|
82
|
+
"Red"
|
83
|
+
end
|
84
|
+
|
85
|
+
output :description, from: ->(item) { [item.name, item.color].join(', ') }
|
86
|
+
end
|
87
|
+
|
88
|
+
Item.new.output_attributes
|
89
|
+
# => {:description=>"An Item, Red"}
|
90
|
+
|
91
|
+
```
|
92
|
+
|
93
|
+
You can of course just ignore it as well.
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
class Item
|
97
|
+
include OutputAttributes
|
98
|
+
|
99
|
+
output :extracted_at, from: ->(_) { Time.now }
|
100
|
+
end
|
101
|
+
|
102
|
+
Item.new.output_attributes
|
103
|
+
# => {:extracted_at=>2019-11-26 16:12:01 -0600}
|
104
|
+
|
105
|
+
```
|
106
|
+
|
107
|
+
I don't overwrite `#to_hash` or `#to_h` because I think those methods are kind of special. However, it's incredibly easy to do it yourself!
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
class Item
|
111
|
+
include OutputAttributes
|
112
|
+
output def name
|
113
|
+
"An Item"
|
114
|
+
end
|
115
|
+
|
116
|
+
alias to_h output_attributes
|
117
|
+
|
118
|
+
# or
|
119
|
+
|
120
|
+
def to_hash
|
121
|
+
output_attributes.merge(
|
122
|
+
with: :more,
|
123
|
+
customization: :perhaps
|
124
|
+
)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
item = Item.new
|
129
|
+
# => #<Item:0x000055e0ae92d0a8>
|
130
|
+
item.output_attributes
|
131
|
+
# => {:name=>"An Item"}
|
132
|
+
item.to_h
|
133
|
+
# => {:name=>"An Item"}
|
134
|
+
item.to_hash
|
135
|
+
# => {:name=>"An Item", :with=>:more, :customization=>:perhaps}
|
136
|
+
```
|
137
|
+
|
138
|
+
I find this style particularly useful when working with Page Objects for data extraction:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
class Page < SimpleDelegator
|
142
|
+
include OutputAttributes
|
143
|
+
|
144
|
+
output def name
|
145
|
+
at_css('#title').text
|
146
|
+
end
|
147
|
+
|
148
|
+
output def price
|
149
|
+
at_css('.price').text
|
150
|
+
end
|
151
|
+
|
152
|
+
output def color
|
153
|
+
labels(:color)
|
154
|
+
end
|
155
|
+
|
156
|
+
output def size
|
157
|
+
labels(:size)
|
158
|
+
end
|
159
|
+
|
160
|
+
output :extracted_at, from: ->(_){ Time.now }
|
161
|
+
|
162
|
+
alias to_hash output_attributes
|
163
|
+
|
164
|
+
private
|
165
|
+
def labels(key)
|
166
|
+
at_css("li:contains('#{key}')").text
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
|
171
|
+
Page.new(nokogirilike).to_hash
|
172
|
+
```
|
173
|
+
|
174
|
+
Usually when I'm writing a method for a page object, I'm thinking "Is this part of my data output, or is this just a helper method?". I've often forgotten to update `#to_hash` when it lives further away from the method itself. I've also tried other styles that involved packaging my data methods into a module, and then doing something like `Attributes.public_instance_methods.reduce({})...` but I wanted to give this style a spin.
|
175
|
+
|
176
|
+
|
177
|
+
# Fun Fact
|
178
|
+
|
179
|
+
`def method; ...; end` returns a symbol. I saw a recent post on Reddit comparing Python's method decorators. This led to some example code using the Memoist gem that looked like this:
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
memoize def my_method
|
183
|
+
...
|
184
|
+
end
|
185
|
+
```
|
186
|
+
|
187
|
+
I think this is pretty cool, and is exactly the type of syntax I wanted when creating data objects.
|
188
|
+
|
189
|
+
|
190
|
+
## Installation
|
191
|
+
|
192
|
+
Add this line to your application's Gemfile:
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
gem 'output_attributes'
|
196
|
+
```
|
197
|
+
|
198
|
+
And then execute:
|
199
|
+
|
200
|
+
$ bundle
|
201
|
+
|
202
|
+
Or install it yourself as:
|
203
|
+
|
204
|
+
$ gem install output_attributes
|
205
|
+
|
206
|
+
## Development
|
207
|
+
|
208
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
209
|
+
|
210
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
211
|
+
|
212
|
+
## Contributing
|
213
|
+
|
214
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/output_attributes.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "output_attributes"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
require "pry"
|
11
|
+
Pry.start
|
data/bin/setup
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require "output_attributes/version"
|
2
|
+
|
3
|
+
# This module creates a class helper method `output` that can be used to create an output configuration
|
4
|
+
# The goal is to help assemble a class's hash representation.
|
5
|
+
#
|
6
|
+
# Each time you call `output` in the class definition, you register a key => proc pair.
|
7
|
+
# You can then call `#output_attributes` to get the hash of key => values
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
#
|
11
|
+
# class Item
|
12
|
+
# include OutputAttributes
|
13
|
+
#
|
14
|
+
# # You can register outputs similar to attr_accessors
|
15
|
+
# output :name
|
16
|
+
#
|
17
|
+
# def name
|
18
|
+
# "A Thing"
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# # Since the `def meth` expression returns a symbol, you can also register it like a decorator.
|
22
|
+
# # It returns the symbol so you could keep chaining with other similar tools like memoize
|
23
|
+
# output def price
|
24
|
+
# "free"
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# # You can rename a method/key:
|
28
|
+
# output :cost, from: :price
|
29
|
+
#
|
30
|
+
# # You can also define a custom proc if the key doesn't match a method name.
|
31
|
+
# # The argument to your proc is the instance itself so you have easy access to it's methods
|
32
|
+
# output :description, from: ->(item){ [item.name, item.price].join(': ') }
|
33
|
+
#
|
34
|
+
# # You can also call whatever you want:
|
35
|
+
# output :extracted_at, from: ->(_){ Time.now }
|
36
|
+
#
|
37
|
+
# def a_helper_method
|
38
|
+
# "Ignore this"
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# # It does not override `#to_h/ash`, but this is easy enough if you wish!
|
42
|
+
# def to_h
|
43
|
+
# output_attributes
|
44
|
+
# end
|
45
|
+
# alias to_hash output_attributes
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# item = Item.new
|
49
|
+
# item.output_attributes || item.to_h || item.to_hash
|
50
|
+
# # =>
|
51
|
+
# {
|
52
|
+
# name: "A Thing",
|
53
|
+
# price: "Free",
|
54
|
+
# description: "A Thing: Free",
|
55
|
+
# extracted_at: 2019-11-26 14:33:00.000
|
56
|
+
# }
|
57
|
+
|
58
|
+
module OutputAttributes
|
59
|
+
# Register this class's catalog of outputs
|
60
|
+
def self.included(base)
|
61
|
+
base.class_eval do
|
62
|
+
@registered_output_attributes = {}
|
63
|
+
|
64
|
+
def self.output(key, from: nil)
|
65
|
+
@registered_output_attributes[key] = from || key
|
66
|
+
key
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.registered_output_attributes
|
70
|
+
@registered_output_attributes
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Return a hash representing your outputs
|
76
|
+
def output_attributes
|
77
|
+
self.class.registered_output_attributes.map do |key, meth|
|
78
|
+
value = case meth
|
79
|
+
when Symbol, String
|
80
|
+
self.send(meth.to_sym)
|
81
|
+
when Proc
|
82
|
+
meth.call(self)
|
83
|
+
else
|
84
|
+
raise ArgumentError, "Could not determine how to output #{meth} for #{key}."
|
85
|
+
end
|
86
|
+
[key, value]
|
87
|
+
end.to_h
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
lib = File.expand_path("lib", __dir__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "output_attributes/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "output_attributes"
|
7
|
+
spec.version = OutputAttributes::VERSION
|
8
|
+
spec.authors = ["Tim Tilberg"]
|
9
|
+
spec.email = ["ttilberg@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = %q{Easily declare a hash to represent your object using `output` attributes}
|
12
|
+
spec.description = <<~DESC
|
13
|
+
Sometimes defining #to_hash is a drag because the source location of your methods
|
14
|
+
is far away from `def to_hash`. This gem gives you a declarative way to build up
|
15
|
+
a hash representation of your object as you define your methods.
|
16
|
+
DESC
|
17
|
+
spec.homepage = "https://www.github.com/ttilberg/output_attributes"
|
18
|
+
|
19
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
20
|
+
|
21
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
22
|
+
spec.metadata["source_code_uri"] = "https://www.github.com/ttilberg/output_attributes"
|
23
|
+
|
24
|
+
# Specify which files should be added to the gem when it is released.
|
25
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
26
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
27
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
28
|
+
end
|
29
|
+
spec.bindir = "exe"
|
30
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ["lib"]
|
32
|
+
|
33
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
34
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
35
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
36
|
+
spec.add_development_dependency "pry"
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: output_attributes
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tim Tilberg
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-11-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: |
|
70
|
+
Sometimes defining #to_hash is a drag because the source location of your methods
|
71
|
+
is far away from `def to_hash`. This gem gives you a declarative way to build up
|
72
|
+
a hash representation of your object as you define your methods.
|
73
|
+
email:
|
74
|
+
- ttilberg@gmail.com
|
75
|
+
executables: []
|
76
|
+
extensions: []
|
77
|
+
extra_rdoc_files: []
|
78
|
+
files:
|
79
|
+
- ".gitignore"
|
80
|
+
- Gemfile
|
81
|
+
- Gemfile.lock
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- bin/console
|
85
|
+
- bin/setup
|
86
|
+
- lib/output_attributes.rb
|
87
|
+
- lib/output_attributes/version.rb
|
88
|
+
- output_attributes.gemspec
|
89
|
+
homepage: https://www.github.com/ttilberg/output_attributes
|
90
|
+
licenses: []
|
91
|
+
metadata:
|
92
|
+
allowed_push_host: https://rubygems.org
|
93
|
+
homepage_uri: https://www.github.com/ttilberg/output_attributes
|
94
|
+
source_code_uri: https://www.github.com/ttilberg/output_attributes
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
requirements: []
|
110
|
+
rubygems_version: 3.0.4
|
111
|
+
signing_key:
|
112
|
+
specification_version: 4
|
113
|
+
summary: Easily declare a hash to represent your object using `output` attributes
|
114
|
+
test_files: []
|