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 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
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in output_attributes.gemspec
4
+ gemspec
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
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
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,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,3 @@
1
+ module OutputAttributes
2
+ VERSION = "0.1.0"
3
+ end
@@ -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: []