README.md in rails-patterns-0.3.0 vs README.md in rails-patterns-0.4.0

- old
+ new

@@ -1,9 +1,14 @@ # Pattern A collection of lightweight, standardized, rails-oriented patterns. +- [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 a whole](#collection) +- [Form - when you need a place for callbacks, want to replace strong parameters or handle virtual/composite resources](#form) + ## Installation ```ruby # Gemfile @@ -164,11 +169,105 @@ #### Usage ```ruby ColorsCollection.new -CustomerEventsCollection.for(customer) -CustomerEventsCollection.for(customer, label_method: "name") +CustomerEventsByTypeCollection.for(customer) +CustomerEventsByTypeCollection.for(customer, label_method: "name") +``` + +## Form + +### When to use it + +Form objects, just like service objects, are commonly used to mitigate problems with model callbacks that interact with external classes ([read more...](http://samuelmullen.com/2013/05/the-problem-with-rails-callbacks/)). +Form objects can also be used as replacement for `ActionController::StrongParameters` strategy, as all writable attributes are re-defined within each form. +Finally form objects can be used as wrappers for virtual (with no model representation) or composite (saving multiple models at once) resources. +In the latter case this may act as replacement for `ActiveRecord::NestedAttributes`. + +### Assumptions and rules + +* Forms include `ActiveModel::Validations` to support validation. +* Forms include `Virtus.model` to support `attribute` static method with all [corresponding capabilities](https://github.com/solnic/virtus). +* Forms can be initialized using `.new`. +* Forms accept optional resource object as first constructor argument. +* Forms accept optional attributes hash as latter constructor argument. +* forms have to implement `#persist` method that returns falsey (if failed) or truthy (if succeeded) value. +* Forms provide access to first constructor argument using `#resource`. +* Forms are saved using their `#save` or `#save!` methods. +* Forms will attempt to pre-populate their fields using `resource#attributes` and public getters for `resource` +* Form's fields are populated with passed-in attributes hash reverse-merged with pre-populated attributes if possible. +* Forms provide `#as` builder method that populates internal `@form_owner` variable (can be used to store current user). +* Forms allow defining/overriding their `#param_key` method result by using `.param_key` static method. This defaults to `#resource#model_name#param_key`. +* Forms delegate `#persisted?` method to `#resource` if possible. +* Forms do handle `ActionController::Parameters` as attributes hash (using `to_unsafe_h`) +* It is recommended to wrap `#persist` method in transaction if possible and if multiple model are affected. + +### Examples + +#### Declaration + +```ruby +class UserForm < Patterns::Form + param_key "person" + + attribute :first_name, String + attribute :last_name, String + attribute :age, Integer + attribute :full_address, String + attribute :skip_notification, Boolean + + validate :first_name, :last_name, presence: true + + private + + def persist + update_user and + update_address and + deliver_notification + end + + def update_user + resource.update_attributes(attributes.except(:full_address, :skip_notification)) + end + + def update_address + resource.address.update_attributes(full_address: full_address) + end + + def deliver_notification + skip_notification || UserNotifier.user_update_notification(user, form_owner).deliver + end +end + +class ReportConfigurationForm < Patterns::Form + param_key "report" + + attribute :include_extra_data, Boolean + attribute :dump_as_csv, Boolean + attribute :comma_separated_column_names, String + attribute :date_start, Date + attribute :date_end, Date + + private + + def persist + SendReport.call(attributes) + end +end +``` + +#### Usage + +```ruby +form = UserForm.new(User.find(1), params[:person]) +form.save + +form = UserForm.new(User.new, params[:person]).as(current_user) +form.save! + +ReportConfigurationForm.new +ReportConfigurationForm.new({ include_extra_data: true, dump_as_csv: true }) ``` ## 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/)