README.md in rails-patterns-0.4.1 vs README.md in rails-patterns-0.5.0
- old
+ new
@@ -4,10 +4,11 @@
- [Query - complex querying on active record relation](#query)
- [Service - useful for handling processes involving multiple steps](#service)
- [Collection - when in need to add a method that relates to the collection as whole](#collection)
- [Form - when you need a place for callbacks, want to replace strong parameters or handle virtual/composite resources](#form)
+- [Calculation - when you need a place for calculating a simple value (numeric, array, hash) and/or cache it](#calculation)
## Installation
```ruby
# Gemfile
@@ -270,9 +271,78 @@
form = UserForm.new(User.new, params[:person]).as(current_user)
form.save!
ReportConfigurationForm.new
ReportConfigurationForm.new({ include_extra_data: true, dump_as_csv: true })
+```
+
+## Calculation
+
+### When to use it
+
+Calculation objects provide a place to calculate simple values (i.e. numeric, arrays, hashes), especially when calculations require interacting with multiple classes, and thus do not fit into any particular one.
+Calculation objects also provide simple abstraction for caching their results.
+
+### Assumptions and rules
+
+* Calculations have to implement `#result` method that returns any value (result of calculation).
+* Calculations do provide `.set_cache_expiry_every` method, that allows defining caching period.
+* When `.set_cache_expiry_every` is not used, result is not being cached.
+* Calculations return result by calling any of following methods: `.calculate`, `.result_for` or `.result`.
+* First argument passed to calculation is accessible by `#subject` private method.
+* Arguments hash passed to calculation is accessible by `#options` private method.
+* Caching takes into account arguments passed when building cache key.
+* To build cache key, `#cache_key` of each argument value is used if possible.
+* By default `Rails.cache` is used as cache store.
+
+### Examples
+
+#### Declaration
+
+```ruby
+class AverageHotelDailyRevenue < Patterns::Calculation
+ set_cache_expiry_every 1.day
+
+ private
+
+ def result
+ reservations.sum(:price) / days_in_year
+ end
+
+ def reservations
+ Reservation.where(
+ date: (beginning_of_year..end_of_year),
+ hotel_id: subject.id
+ )
+ end
+
+ def days_in_year
+ end_of_year.yday
+ end
+
+ def year
+ options.fetch(:year, Date.current.year)
+ end
+
+ def beginning_of_year
+ Date.new(year).beginning_of_year
+ end
+
+ def end_of_year
+ Date.new(year).end_of_year
+ end
+end
+```
+
+#### Usage
+
+```ruby
+hotel = Hotel.find(123)
+AverageHotelDailyRevenue.result_for(hotel)
+AverageHotelDailyRevenue.result_for(hotel, year: 2015)
+
+TotalCurrentRevenue.calculate
+AverageDailyRevenue.result
```
## Further reading
* [7 ways to decompose fat active record models](http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/)