- Notifications
You must be signed in to change notification settings - Fork1
Alternative ActiveRecord composition
License
cedarcode/composition
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Alternative composition support forrails
applications, for whenActiveRecord'scomposed_of
is not enough. This gem adds some behaviorinto composed objects and ways to interact and send messages between boththe one composing and the one being composed.
Add this line to your application's Gemfile:
gem'composition'
And then execute:
$ bundle
Or install it yourself as:
$ gem install composition
Composition will enable a new way of defining composed objects into anActiveRecord class. You should have available acompose
macro for youruse in your application models.
classUser <ActiveRecord::Basecompose:credit_card,mapping:{credit_card_name::name,credit_card_brand::brand,credit_card_expiration::expiration}end
TheUser
class has now available the following methods to manipulatethecredit_card
object:
User#credit_card
User#credit_card=(credit_card)
These methods will operate with a credit_card value object like the onedescribed below:
classCreditCard <Composition::Basecomposed_from:userdefexpired?Date.today >expirationendend
Notice thatCreditCard
inherits fromComposition::Base
and that thecomposed_from
macro is set to:user
. This is necessary in order to gainfull access to theuser
object from thecredit_card
.
With the previous setup in place, now it should be possible to access attributes fromthe database through the value objects instead. You can think of theCreditCard
as a normalActiveModel::Model
class with the attributes that you alreadyspecified in themapping
option.
You would interact with the credit_card object like the following:
user.credit_card_name='Jon Snow'# Set the ActiveRecord attributeuser.credit_card_brand='Visa'# Set the ActiveRecord attributeuser.credit_card_expiration=Date.yesterday# Set the ActiveRecord attributeuser.credit_card# => CreditCard.new(name: 'Jon Snow', brand: 'Visa', expiration: Thu, 11 May 2017)user.credit_card.name# => 'Jon Snow'user.credit_card.brand# => 'Visa'user.credit_card.expiration# => Thu, 11 May 2017user.credit_card.user ==user# => trueuser.credit_card.attributes# => { name: 'Jon Snow', brand: 'Visa', expiration: Thu, 11 May 2017 }user.credit_card.expired?# => true
Modifying the credit_card attributes:
user.credit_card.name# => 'Jon Snow'user.credit_card.name='Arya Stark'# => 'Arya Stark'user.credit_card_name# => 'Arya Stark'user.save# => true
The value object can be set by either setting attributes individually, byassigning a new value object, or by usingassign_attributes
on the parent.
user.credit_card.name='Jon Snow'user.credit_card.brand='Visa'user.credit_card.expiration=Date.todayuser.credit_card# => CreditCard.new(name: 'Jon Snow', brand: 'Visa', expiration: Thu, 12 May 2017)user.credit_card=CreditCard.new(name:'Jon Snow',brand:'Visa',expiration:Date.today)user.credit_card# => CreditCard.new(name: 'Jon Snow', brand: 'Visa', expiration: Thu, 12 May 2017)user.assign_attributes(credit_card:{name:'Jon Snow',brand:'Visa',expiration:Date.today})user.credit_card# => CreditCard.new(name: 'Jon Snow', brand: 'Visa', expiration: Thu, 12 May 2017)user.update_attributes(credit_card:{name:'Jon Snow',brand:'Visa',expiration:Date.today})user.credit_card# => CreditCard.new(name: 'Jon Snow', brand: 'Visa', expiration: Thu, 12 May 2017)
If you need to add validations to your value object that should just work.
classCreditCard <Composition::Basecomposed_from:uservalidates:expiration,presence:truedefexpired?Date.today >expirationendenduser.credit_card=CreditCard.new(name:'Jon Snow',brand:'Visa',expiration:nil)user.credit_card.valid?# => false
Composition will assume some things and use some defaults based on namingconventions for when you definecompose
andcomposed_from
macros. However,there will be cases where you will have to override the naming convention withsomething custom. Following you will find the complete reference for the providedmacros.
Thecompose
method will accept the following options:
This is required. It will accept a hash of mappings between the attributesin the parent object and their mapping to the new value object being defined.
classUser <ActiveRecord::Basecompose:credit_card,mapping:{credit_card_name::name,credit_card_brand::brand,credit_card_expiration::expiration}end
Optional. If the name of the value object cannot be derived from the compositionname, you can use the:class_name
option to supply the class name. If auser
hasacredit_card
but the name of the class is something likeCCard
, then you can use:
classUser <ActiveRecord::Basecompose:credit_card,mapping:{credit_card_name::name,credit_card_brand::brand,credit_card_expiration::expiration},class_name:'CCard'end
Thecomposed_from
method will accept the following options:
Optional. If the name of the value object cannot be derived from the compositionname, you can use the:class_name
option to supply the class name. If auser
hasacredit_card
but the name of the user class is something likeAdminUser
, thenyou can use:
classCreditCard <Composition::Basecompose_from:user,class_name:'AdminUser'end
- Fork it (https://github.com/cedarcode/composition/ )
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request
See theRunning Tests guide for details on how to run the test suite.
This project is licensed under the MIT License - see theLICENSE file for details
About
Alternative ActiveRecord composition