Skip to content

Instantly share code, notes, and snippets.

@pricees
Created February 12, 2014 20:15
Show Gist options
  • Save pricees/8963659 to your computer and use it in GitHub Desktop.
Save pricees/8963659 to your computer and use it in GitHub Desktop.
Extending an Array/ActiveRelation vs Subclassing Array
names = %w[SK Steve Pedro Anthony Milo Xiping]
Person = Struct.new :name
module Helloable
def hello_all_the_names
each { |person| puts "Helloable | Hello #{person.name}" }
end
end
# An interesting way to add functionality to an array is to extend it
ary = names.map { |name| Person.new name }
ary.extend(Helloable)
ary.hello_all_the_names
# Prints:
# Helloable | Hello SK
# Helloable | Hello Steve
# Helloable | Hello Pedro
# Helloable | Hello Anthony
# Helloable | Hello Milo
# Helloable | Hello Xiping
# My preference would be to subclass an array, it costs less
class HelloArray < Array
def hello_all_the_names
each { |person| puts "HelloArray | Hello #{person.name}" }
end
end
hello_ary = HelloArray.new(names.map { |name| Person.new name })
hello_ary.hello_all_the_names
# Prints:
# HelloArray | Hello SK
# HelloArray | Hello Steve
# HelloArray | Hello Pedro
# HelloArray | Hello Anthony
# HelloArray | Hello Milo
# HelloArray | Hello Xiping
# Whats the point?
#
# Well we have code here
#
#
#
# /app/models/transaction.rb
#
class Transaction < ActiveRecord::Base
#
# [...]
#
module Balances
def sold
map{ |tx| tx.sold_amount }.sum
end
def sold_pending
map{ |tx| tx.sold_pending_amount }.sum
end
def sold_available
map{ |tx| tx.sold_available_amount }.sum
end
def payments
map{ |tx| tx.payments_amount }.sum
end
def payments_requested
map{ |tx| tx.payments_requested_amount }.sum
end
def payments_paid
map{ |tx| tx.payments_paid }.sum
end
def balance
sold - payments_requested - payments_paid
end
def available_balance
sold_available - payments_requested - payments_paid
end
def real_time_balance(id)
select { |tx| tx.id <= id && tx.count_in_total? }.map(&:amount_with_sign).sum
end
end
end
# Which are used to extend ActiveRelations
#app/controllers/user_transactions_finder.rb: @transactions.extend Transaction::Balances
#app/models/line_item_decorator.rb: include Transaction::Balances
#app/models/payment_transaction.rb: transactions.extend Transaction::Balances
#
# Extending an instance over and over wastes clock cycles.
# If we choose to make Balances an array subclass we can used it like so:
class Transaction < ActiveRecord::Base
module Balances < Array
end
end
transactions = Transaction::Balances.new transactions
@transactions = Transaction::Balances.new @transactions
# Now we do include it in a relationship in line item decorator.
# This might justify keep the balances in module form, however I might then
# decide to make:
#
# class BalancesArray < Array
# include Transaction:Balances
# end
#
# Either way, I think extending instances probably more of an anti-pattern that
# we should avoid.
@milotodorovich
Copy link

This looks really good; I like the direction of the discussion. The use of extend or include is a subtle, hidden way of implementing inheritance, even if it's only on a single instance. Back in GOF days, the maxim of the day was "prefer composition over inheritance", as that leads to small, focused objects that perform a small subset of functionality, and can be assembled in innovative ways.

An even better argument is how cryptic the Transaction::Balances functionality has become. I had originally implemented the idea, and I don't recall why I chose extended over Balance.new(array).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment