Skip to content

Gem to add a declarative syntax for building a hash representation of your object using `output` attributes

License

Notifications You must be signed in to change notification settings

ttilberg/output_attributes

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OutputAttributes

This gem helps you serialize your data object by providing an output class macro for defining your class. You can call #output_attributes to get a hash representing your object from the output helpers.

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".

Installation

Add this line to your application's Gemfile:

gem 'output_attributes'

And then execute:

$ bundle

Or install it yourself as:

$ gem install output_attributes

Usage

Behold:

require 'output_attributes'

class Item
  include OutputAttributes

  def name
    "The Name"
  end
  output :name

end

Item.new.output_attributes
# => {:name=>"The Name"}

The output declaration can come before, during, or after a method definition.

class Item
  include OutputAttributes

  # Before:
  output :first_name

  def first_name
    "First Name"
  end

  # During -- this is my favorite. It leverages the fact that the `def meth` expression returns a symbol... Clever!
  output def middle_name
    "Middle Name"
  end

  # After
  def last_name
    "Last Name"
  end

  output :last_name
end

Item.new.output_attributes
# => {:first_name=>"First Name",
#  :middle_name=>"Middle Name",
#  :last_name=>"Last Name"}

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.

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:

class Item
  include OutputAttributes
  def name
    "An Item"
  end

  output :description, from: :name
end

Item.new.output_attributes
# => {:description=>'An Item'}

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.

class Item
  include OutputAttributes
  def name
    "An Item"
  end

  def color
    "Red"
  end

  output :description, from: ->(item) { [item.name, item.color].join(', ') }
end

Item.new.output_attributes
# => {:description=>"An Item, Red"}

You can of course just ignore it as well.

class Item
  include OutputAttributes

  output :extracted_at, from: ->(_) { Time.now }
end

Item.new.output_attributes
# => {:extracted_at=>2019-11-26 16:12:01 -0600}

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!

class Item
  include OutputAttributes
  output def name
    "An Item"
  end

  alias to_h output_attributes

  # or

  def to_hash
    output_attributes.merge(
      with: :more,
      customization: :perhaps
    )
  end
end

item = Item.new
# => #<Item:0x000055e0ae92d0a8>
item.output_attributes
# => {:name=>"An Item"}
item.to_h
# => {:name=>"An Item"}
item.to_hash
# => {:name=>"An Item", :with=>:more, :customization=>:perhaps}

I find this style particularly useful when working with Page Objects for data extraction:

class Page < SimpleDelegator
  include OutputAttributes

  output def name
    at_css('#title').text
  end

  output def price
    at_css('.price').text
  end

  output def color
    labels(:color)
  end

  output def size
    labels(:size)
  end

  output def description
    "#{name} #{size} #{color}"
  end

  def to_hash
    output_attributes.merge(
      extracted_at: Time.now,
      object: self.class
    )
  end

  private
  def labels(key)
    at_css("li:contains('#{key}')").text
  end

end

Page.new(nokogirilike).to_hash

Usually when I'm writing a method for a page object, I'm already 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 far 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. For now, I like it well enough.

Fun Fact

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:

memoize def my_method
  ...
end

I think this is pretty cool. It's exactly the type of syntax I usually wished I had when creating data objects.

Development

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.

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.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/ttilberg/output_attributes.

About

Gem to add a declarative syntax for building a hash representation of your object using `output` attributes

Resources

License

Stars

Watchers

Forks

Packages

No packages published