If you’re arriving without reading part 1 no problem, this article will focus on ActiveModel. If you want background and more detail on what the sample application is doing you can read part 1 here, its a much shorter read and worth it for the background. Source code is available on Github:

Download zip from Github
View Github project page

What is ActiveModel

So what is the alternative to Ruby objects? A good start would be to read through the Rails guide for ActiveModel. You may not know it exists as it’s a beta guide so its only linked in the guides landing page. The documentation is sparse, there’s more available in the source code, but one of the first issues you will notice is that there’s no single include which gets you everything You need to pick which modules you want or include ActiveModel which brings 4 of the other modules with it.

ActiveModel provides the hooks for your class to work with ActionPack and ActionView and mixes in Validations Conversions, Naming and Translations. The other mixins are below

Name Functionality
AttributeMethods Prefix and suffix attributes for common behavior
Callbacks Define and execute custom callbacks
Conversion Wire model into controllers and views
Dirty Tracking and querying attribute changes
Validations Validations similar to ActiveRecord
Naming Wire model into controllers and views
Serialization Define how hash json or xml should be created
Translation Create internalized human_readable_name
SecurePassword Password encryption and authentication

To demonstrate each of these, I’m going to convert the Ruby object version of my remote credit card service facade and attempt to replace the custom coding with the standard implementation of these Modules. With great invention, originality and excitement going to call the new models Wallet and Card.

Here’s a quick plan of action

  1. Copy over all tests from the original Models and Controllers
  2. Create Wallet and Card classes in app/models
  3. Scaffold WalletsController and  CardsController with the scaffold_controller generator rails g scaffold_controller [model name]
  4. Eliminate unnecessary tests, use Shoulda validation macros and other shortcuts where possible.
  5. Code until the tests pass!

If you haven’t used Shoulda before, its a two-part gem which allows tests to be set up in blocks with readable names and implements a number of macros which perform common testing tasks. If we take a look you can see that the following tests replace all of my manual validation tests with the confidence that we are using both ActiveModel functionality to maintain state, and a proven test suite which works with it. For example the following tests will replace almost all of our field validation tests.

In terms of readability, our code is significantly easier to understand and we now have a clear separation between very standard object setup and our custom business logic.

One of the wrinkles in this example is that a card can be valid without a card number, but cannot create a new card without one. This was not completely implemented with the Ruby object approach and its going to require some thought with ActiveModel.

First thing to do is to include ActiveModel::Model in your class. Although we will discuss this module later, it turns on a lot of automatic behavior which makes it easier to code model behavior from the beginning. Now we will see what functionality we get through each module.

ActiveModel Validations

The validations module adds all of the validations you have used with ActiveRecord. It comes included with ActiveModel:Model so we don’t need to explicitly include it when we already have Model mixed in.

Here is the initial implementation of validations

That code replaces all of my manual checks and error messages. You can try it out on the console, create a new Card without parameters.

As well as the class methods you will use as macros you get valid? it’s alias validate, invalid? and errors. A call to valid? or invalid? runs the validators, so you have to ensure your model exposes some method to be used when creation or update is complete enough to start validating. I use a subset of save, save!, create, create! and update as we’re all familiar with those terms from ActiveRecord.

Finally, Validations calls read_attribute_for_validation, a method initially aliased to send. This allows you to override how your attributes are read during validation. I wasn’t able to initially use this technique, but it would allow for a completely different way to store values in the object. For example, instead of setters/getters or attr_accessor for each attribute, you could store attributes in an internal hash and override. I am always amazed by the little things Rails does to allow customization like this.

Creating a custom validator

So I am going to put Validations to good use by refactoring the way expiration_date is manipulated in Card. If you remember PlainCard, we had 36 lines of award winning code to determine whether expiration_date was a date, a string or a month/year string. Rubocop hates it and I may grudgingly admit that 3 levels of nesting may be confusing. So can I break this up and do it a little differently?

I’m going to try validates_with and create a custom Validator. After all, so few people are silly enough to try and store 3 different types of data in the same field that its not a pre-build Rails validator. Here’s the steps

  1. Create a new directory app/validators
  2. Create a new class, MultiDateValidator and stick it in app/validators
  3. Change application.rb to have this directory auto-loaded
    1. config.autoload_paths += %W[“#{config.root}/app/validators/”]
  4. Profit!

The MultiDateValidator is going to check for my 3 types of date. To make it easy on myself I am going to inherit from EachValidator, and voila. My line validates :expiration_date, multi_date_type: true in Card calls this validator when ActiveModel::Validations::run_validators is executed.

A Quick aside. Where to put things? We all know where controllers and helpers go, but what about a class which implements an EachValidator? My answer, wherever makes sense for you and your team. Not every decision is a convention followed by the world. I’ve seen at least 3 different suggestions where to place validators which are, ahem, valid. So feel free to ignore my placement and use another solution. I don’t know about you, but for about 6 months after starting to use Rails, I was frightened of creating directories in my apps in case I was breaking some unwritten rule. Now I have come to realize that they are my applications and you should own and do what you wish to yours.

Rubocop still dislikes it, but less than the PlainCard implementation. However, we have split off the act of validation from the function of converting the object into a date, something I can get behind in justifying doing code for an article which isn’t completely contrived.

Validating an entire class

For a different custom validation, we need to jump quickly to the Wallet. If you remember, we need to invalidate the wallet if any card inside it is invalid. As we aren’t looking at fields but the class as a whole, I’m going to use validate and define a validating method in Wallet.

Pretty neat, although a little more verbose than the way it was coded with Ruby objects.

We’re done with validations for now, but we will return here later to discuss how we solve the card_number paradox with the aid of an additional module.

Validation Callbacks

Validating that date is just half the story, the other half is to convert the string into a real date so that it can be used. I am going to use a callback to alter the content of the field and convert to a date. For this one, I am going to use the non-advertised ActiveModel::Validations:Callbacks module. This isn’t mentioned in the guide, but they are the only automatic running callback pre-wired in ActiveModel. I am going to hook up an after_validation callback to convert the text value of expiration_date to a real date.

I understand this is not the best solution. It’s not the worst either, and I wanted to illustrate something a little more complex than blanking the card_number field after failed validation.

Note, include ActiveModel::Validations::Callbacks before ActiveModel::Model. If you don’t, then Validation callbacks won’t fire.

To create a Validation callback you add either a before_validation or after_validation callback. Documentation on how to do this with ActiveRecord is included in the callbacks guide, though as you will see later, they are not regular callbacks. To implement

  1. Use before_validation or after_validation to define which method is called after the callback is triggered.
  2. Write your method
  3. When writing before_validation, return true unless you want all other before callbacks to halt and to not continue running valid?. This may seem like a trivial thing, but if you would like your form to show all errors then you will have to return true from any before_validation
  4. If you’re writing an after_validation, if you return false then other after callbacks will not be run.

As you will have sharply observed, we have duplicated much of the code from our validation. I am leaving it for now for ease of reading, but these two methods would need refactoring, moving the common code into a module. However, before embarking on refactoring, sometimes code smells are nature’s way of telling you you’re whole approach is wrong, so we will revisit this functionality later.

The other item of interest you may have noticed is this

ActiveModel does not give you write_attribute or its syntactic sugar self[:attribute] version. You can take a look at the multiple ways to update an ActiveRecord field and throw them all out for ActiveModel. You are free to implement these as necessary, but nothing is mixed in for free. If I ever get to a refactoring article, I will show you the mixin I use when creating ActiveModels which provides this functionality and makes all of them act much more like ActiveRecord objects.

I would strongly caution you to really ask yourself what functionality you are putting into a validation callback. You are never guaranteed for them to fire, and anyone can write a callback higher in the chain which suddenly halts execution and leads to a very tricky debugging session. Although its tempting to attach unrelated functionality which is conveniently triggered by an event, I would suggest that only state or data changes relating to validation should be handled here. The use case I find myself implementing most tends to be turning off validation for optional fields in a form, or wiping out something such as CVV data if a model’s validation has failed. These guidelines are as valid for ActiveRecord as they are for ActiveModel.

ActiveModel Callbacks

Now where did I leave my wallet? We’ve been focussing on the Card for a few sections and now its time to create the Wallet object and replicate some of our hand crafted goodness in a hopefully less verbose way. This is going to be pretty similar to Card, we add the incredibly useful validations, get rid of our custom error handling and put in blank methods for save, save!, warnings and messages. With some changes to our factory names we can get all the tests to run without too many errors, but a lot of failures. Fantastic, ready to implement functionality!

If you remember, the Wallet needs to mark a warning and provide a message if one of its Cards is expired or the Wallet has no cards. For this we’re going to use a Callback.

For site level warnings such as ‘your card has expired,’ or ‘your email address has been taken over by a botnet’ it can be tempting to code everything in the view. After all, you might have an account object in your top_nav, always ready to be probed for its data, state and secrets. However, every time you add <% if account.profile.email.primary.invalid? %> a kitten cries. Well if not a kitten, definitely the future engineer who has to untangle your mess. Make models as self-sufficient as possible, including organizing their own messages, with text externalized and internationalized!

Now, if you’re expecting similar magic to the Validations module you’re in for a disappointment. Callbacks are a self-service buffet with some assembly required. Lets have a checklist of what we need to do.

  1. Define your callback type.For example :save, :create, :cartwheel, :jump
  2. Use the callback macro to link the callback to executed method. For example after_jump :extend_arms
  3. Find the methods which should fire a callback and wrap them in a callback block
  4. Implement your callback method

You can define your model_callbacks to include before, after around or to restrict to 1 or 2 of them.

So putting this to good use; after saving or creating a wallet, we want to check and if necessary populate our warning messages. If a wallet has no cards or contains an expired card we should store an appropriate warning. When writing this for the Ruby object version, I just tried to make sure the call to check_warnings was attached to the appropriate methods and hopefully got it right. This was a little haphazard and would certainly become unwieldy if implementing multiple callbacks. For Wallet, I think on save and on create should suffice.

Some notes:

If you are implementing ActiveModel and allowing it to set your attributes on new, you have to call super when you override initialize.

Avoid even thinking about triggering multiple callbacks from a method. You are defining events which trigger, those events can be captured by multiple callbacks but do not be tempted to trigger off

You can pass a class instead of a method to the callback macro. The callback will attempt to call the callback name as a class method in the other class. For example,

To halt the callback chain you must throw an exception.

Don’t forget to yield if you write an around callback or your original method will not run.

I find the implementation to be clunky, but better than hand coding method names to make sure they run. Its certainly low on the Rails elegance scale, but it does enforce good practices upon you if you use them, and it is better self-documentation than the alternatives. However, there is something about putting my method inside a block which is visually unsettling.

ActiveModel Attributes

One of the coolest, but perhaps least useful mixins is ActiveModel::Attributes. This should not be confused with attribute based initialization which is part of Model, but the ability to create custom prefixes and suffixes.This is one of the few modules well documented in the ActiveModel guide.

Maybe its me, but I just haven’t had much call for this type of functionality…..until now of course!

We’re currently showing a users account number on the wallet page. Pah, thats a potential security violation, lets add a new requirement is to mask all but the last 4 characters of account number. Quick! to the attribute-mobile Robin. I know I know, thats not exactly groundbreaking stuff, but I’m really drawing a blank on creative ways to use this feature within the application. Ironically, first_6 and last_4 would be a perfect Attribute illustration, but I am never storing the card_number in the local model and don’t have the opportunity to manipulate it.

So, I will create a suffix which will allow us to mask an attribute and unmask the last x characters. You have to pay attention to the order of your macros, make sure you define you attribute_method_suffix before you define_attribute_methods, and who could forget that with how easily it rolls off the tongue. Anyway, here’s the code and a test.

It really is an elegant mechanism, and the code is a great learning tool to improve your meta-programming. Perhaps if you have a great deal of text manipulation you want to share across attributes you will find a better use for it than I have.

And with this, we’ve enough to go ahead and start plugging our new Card and Wallet into the web application. I am going to create a scaffold with all the routes and views, then I will chip away at them until our new ActiveModel architecture is working.

I can do the show action and view, but will edit out any links until we get to Conversion and Naming. Oh..

ActiveModel Conversion and ActiveModel Naming

So now we get to the section where a number of modules are intertwined to provide their tangible benefit. Model, Naming and Conversion work together to help plug your object into the Rails framework. We’re going to take Naming and Conversion as a unit, so there will be a little bit of switching between modules in this section.

Naming is one of those modules you probably wont be using yourself unless you’re heavily into code generation, but it is used to derive introspective naming to determine how to create links from your model objects. On the command line, try this

There’s a bunch of information in there which is useful for link_to and form_for so lets try it.

Our plain wallet form was written  with a form helper

We should be able to use form_for now:

Sadly, because Wallet is a singular resource, this bug prevents it from working, so we will have to modify our form declaration to

If wallet was not a singular resource it would work, and you will see how Card works in a form a little later. So sadly, I have to use the wallet path helpers in Wallet and Card views. However, my Card paths are now automated:

This may not seem like much, but aside from the cool factor, it is a ton of work to maintain and edit path helper links views and controllers. I found it unwieldy just with 2 models, imagine your application has 20 or 30 different models which would require you to edit path helpers for every view, controller and partial.

Similarly, using form_for and its other tags is a convenience over form_tag, especially for the inevitable changes every application goes through. Managing form fields and updating forms is tedious at best, any help we can get it a bonus.

So thanks to the interplay between Naming, and Model, all of your links and forms should be seamless between persisted and non-persisted objects…..with one caveat. Here’s where the Conversion model kicks in. Have you ever wondered how you can use the same form partial for an edit and new action without writing any code? How does Rails know when to use Post and when to use Patch?

ActiveModel::Conversion and ActiveModel::Model are the answer. Conversion implements 3 methods; to_key, to_param and to_model. Rails supplies to_model as another one of those awesomely elegant and overlooked methods to wrap any object and have it be recognized as a model. If you have code which for some reason cannot act like ActiveModel, it can override to_model with a wrapper around itself. The to_key method will take whatever you stick in a field called :id and return it. The to_param method will take to_key and provided your object returns true from a persisted? method, will convert it to a string param. The method persisted? is added by including ActiveModel with a default return value of false. You can manually override persisted? to be true when appropriate. Still here? Take a deep breath and hunt around in ActionView::Helpers until you come across this line

Its used by form_for to determine the default action and method. So lets think about that for a second and bask in its elegance. If a model’s to_param is not nil, it means it has been persisted and provides a link-friendly parameter, at the same time persisted? is checked to determine the form action and method. For regular ActiveRecord objects this allows everything to work without any of us writing a line of code.

Its worth taking a deep breath and looking at that again. When a series of mechanisms and interactions is so elegant and so simple it’s a joy to read.

In the PlainCard and PlainWallet form, I had to manually set the action to be POST or PATCH by using the view itself to store the action inside edit.html.erb and new.html.erb. Thats not ideal, so I’m going to use Conversion to help out. We could cheat and mark all Wallets as persisted, but lets try and make a better logical test. If we have a token assigned by the remote service, it must be persisted. So, for Walet, wallet_token is the return from id and a test for persisted? That way, if I every have to implement creating a wallet, it will have no token and default to a post in the partial.

Now when I look at my edit form, I see the following:

Wonderful, I’ll leave it to Kierkegaard to treatise on the dichotomy of a persisted non-persistent object, while I create a similar mechanism for Cards so that we get the benefits of Model, Naming and Conversion working together on forms, views and controllers.

ActiveModel Model

After all that Model is a bit of an anti-climax. Its really just two convenience methods and the inclusion of Validations, Conversion, Naming and Translation, so when you include Model you get the others included automatically. You also get a default initializer which takes a hash of attributes and attempts to set any defined fields which match those parameters. Finally, Model gives you a default false returning persitent? method.

Before we wrap up this section, lets have a quick look at Lint tests. You can sanity check your models  with an include to your test class, and a setup statement which assigns the instance variable @model to a new instance of your model

Its probably worth using, although its hard to conceive of how one might fail to create a valid Model.

I have to hook up the Card actions and forms for CREATE, DELETE and UPDATE. I’m going to use the same changes as I made for Wallet: change to form_for and use object route creation.

In order for edit to work on a Card object, we’re going to need a key and persisted? to function correctly. If you look at PlainCard, I had a fairly clunky method of using a card’s order within its array offset + 1 as the key. This is so bad its painful to even think about, look how the Model link is so much less shameful and nauseating.

For now, I’m going to use card_type and last_4 concatenated to integer. These will be unique per wallet and will do for now, but a real application would need something more bulletproof!

One final note: singular resource with nested resources and form_for. I think this took me about half a day to figure out the first time, and I will get around to dedicating some time to nested resources in a few weeks, but for now, here’s the ‘spell’ to get this working.

Other permutations caused everything from route errors to my personal favorite: the wallet id being appended with a period to the url and being interpreted by the controller as an unknown format.

ActiveModel Serialization, or Virtual-virtual fields

With PlainCard I created a helper method to change a card’s expiration date to just the month/year for the form display. While noble in ignoring the last few years universal admonishment for even saying the word helper out loud, there are alternative methods of accomplishing this which are a little slicker.

One option would be to create a function in Card which returns mm/yy and set a text_field_tag with the content. It would be nicer if there was a way to set the read and write methods of a form helper object because one hanging text_field_tag in a form looks aesthetically wrong.

However, there is an opportunity to refactor some sludge away from the Callbacks section, provide an even slicker solution and review the Serialization functionality.

Just as you can create virtual attributes in ActiveRecord, you can do the same in ActiveModel. In fact, unless you care about serialization, all attributes are virtual. So, if your model object is transient and only lives for the life of a request, you can ignore serialization. But if you want to move your data somewhere you will eventually need a way to extract the model’s attributes and convert them to another format.

If you looked at the code for PlainWallet or PlainCard you may have noticed an attributes method. This is used to turn its data into a hash of parameters to pass onto the remote service. As a happy little accident, this is the same method you need to implement to have Rails’s Serializers spring into action.  Whatever you include in attributes is the ‘public’ data from your object, so I can exclude certain fields from being considered model attributes.

The first use case for this would be to not include first_6 and last_4. Those only make sense travelling from the remote service to the local model, and we could also omit the just implemented :short_expiration so it is never sent outside the model. I remove them from the attributes of card to leave it like this:

So now that I can safely add and use other fields at will, I can start my refactoring of dates. I will leave the validation alone, but I am removing the after_validation callback and with it, the hideous stench of code smell. I will add a setter and getter for short_expiration, add short_expiration to the whitelist of strong parameters in the controller and change the form helper to a text_field. The result, same functionality, more clearly defined and without the need for a callback to run.

Aside from writing your own attributes method, what does Serialization do for us? The guide and code comments state that your hash should use string keys, but I have not found any code to suggest that symbols will not work. After implementing attributes, Rails method serializable_hash will return your object attributes. Why? who knows.

You can also change your import to use ActiveModel::Serializers::JSON or XML where you get the additional functionality of a correct JSON/XML export, and building your object from JSON/XML should you wish. A cooler feature is the ability to create your object from json, especially handy when working with remote services as you don’t need an intermediate delegate to convert json to hash parameters.

In a world before jBuilder, or if you wish, ignoring jBuilder, Serialization allows you to construct your models representation to the world. This is useful for facaded 3rd parties; if you look at the RemoteSbbWalletDelegate you can see translation methods marshalling remote and local cards. Export of Wallet and Card are not affected by these methods, and changing to a JSON object construction would eliminate the need to marshal the remote object for simple changes such as deleting un-needed id fields. An object initialized this way will ignore data it does not care about, rather than throwing exceptions with an attribute based initializer.

ActiveModel Translation

Forza! Actung! Allez! Included with ActiveModel::Model, or available separately, translation will, well, translate. Specifically, it will change a model’s human_readble_name. This is used in label_field, or directly on the model itself.

I created a UK locale file with card number translated to Primary account number. It’s used in the Card form and Wallet show pages. You can change your locale in application.rb by uncommenting the locale line, or run the Capybara tests to see it working.

While not earth-shattering, internationalization is a very handy tool for ActiveModel, time saved rises exponentially with number of models and languages.

ActiveModel SecurePassword

I have to cheat, there’s no way to squeeze user passwords into the confines of a card and a wallet, so I will walk through this module with a Model not related to the sample application. I created a Model called Registration, it has a handful of the fields you would expect a registration object to hold and it implements SecurePassword.

I have a hard time coming up with use cases for this one, unless you are abdicating local authentication to a remote service or creating a transient session-length authentication with functionality requiring password re-entry, otherwise I don’t think you will be using a secure password without a table behind it.

The code is shared with ActiveRecord, if you’ve loved using SecurePassword before, terrific,  you will love this. However, there is a potentially awful design flaw which can lead to a gaping security hole. Try this, with help from the console. Add an attr_accessor :password to RemoteWallet. Now create an empty RemoteWallet and set the password field. Then inspect the object

Notice that virtual fields in ActiveRecord are not available through inspect, attributes or any other normal access. It would take deliberate work to get the content of a virtual attribute out of an ActiveRecord object.

Now lets try that with my ActiveModel class.

Even though I don’t have a password field in my model, it’s been mixed in by SecurePassgword and is showing up in inspect. Although I can implement Serializable to exclude the field, if I don’t, my default behavior for this object will be to broadcast a user’s password in cleartext wherever it goes. Not a good design, so handle with extreme care.

The module takes care of password and password_confirmation fields, it requires you have a field called password_digest in your object, but it would have been nice if this was included too. The reason it is not is that it would not work correctly with ActiveRecord as written, but all in all, while kudos is due for the team splitting this out from ActiveRecord, it looks poorly designed when examined in isolation of its ActiveRecord roots.

To create a model which utilizes SecurePassword:

  1. Create your model, include ActiveModel::SecurePassword
  2. create an attr_accessor for password_digest
  3. Do not create password or password_confirmation fields/accessors/setter or getter methods
  4. use the macro has_secure_password
    1. You can use one option: validations: false This will turn off password length, password_confirmation equality with password and password not nil checks from your model’s valid? test
  5. You may add additional validations to your password field

To use a SecurePassword

  1. Create a new object, either bare or with params. Do not set password_digest
  2. If password_confirmation is nil it will not be used in an equality test with password. This is fairly bizarre and can be fixed manually
  3. You can check validity of your object
  4. You can use authenticate(clear_text_password) and will receive either false or the model in its entirety
  5. Beware, password and password_confirmation are available to be read from the object
  6. There is no minimum password length
  7. Setting password_confirmation to nil at any time removes it from being validated against password

We can tidy up the confirmation validation problem somewhat by adding some additional validations:

  1. Set the minimum password size
  2. Override the password_confirmation allow_nil validation

However, I would say there’s no good use case for using SecurePassword as it is implemented for non-persistent models, the amount of work to use bcrypt yourself is minimal and can get as good results without the problem of a transparent password field.

Dirty

We’ve finally reached the last module. If you’re still here, congratulations, you’ve survived probably the longest tutorial on ActiveModel in existence.

I had high hopes for Dirty but found it lacking. If you bought a ‘Build Your Own Airplane Kit’ which comprised of three instructions; 1. Become an engineer, 2 Go work for Boeing, 3 Success! it would mirror my disappointment.

As we reach the final module, I have a test case in my Wallet/Card project which has failed and I have been unable to correct or work around with any of the previous tools. I’ve referred to this before as the Card Number Paradox. A card number is never sent from the server, but must be sent to the server to save or edit a card. Therefore it need to be a required field…except when it isn’t.

Here’s the test case

So far I have exhausted all the modules of ActiveModel and there is still no solution. It’s an unusual use case, and its only existent because of another strange requirement which requires an edited Wallet to sending a blank card_number. Dirty is the last hope before some dreadful hack.

Now Dirty is similar to Callbacks, you get some tools but need to wire up everything yourself. Here are the steps to implement:

  1. List attributes which will be tracked with define_attribute_methods.
    1. Note, this will clash with the same macro used in the Attributes and will apply prefix and suffix methods to the fields you just want to track as dirty.
  2. Invoke attr_name_will_change! prior to changing an attribute
  3. Call changes_applied on saving, or another event which you deem to have a transform the object state to clean.
  4. Contrary to the documentation, I found that setting a value to the same as an existing value still marked the object and field as changed.

Here’s an example

After implementing you get some neat functionality, lets take a look at some of the methods added to your model and fields within your model.

  1. model.changed?
  2. model.changed
  3. model.previous_changes
  4. model.changed_attributes
  5. model.field_name_changed?
  6. model.field_name_change
  7. model.field_name_was
  8. model.restore_field_name!

 

Fantastic, all sorts of ways to check and restore data which was changed, and if you note that creating a new object makes it dirty until marked as saved. There is a job of work to do in order to get Dirty implemented, but it will be possible to differentiate between an object which is saved and one which is still being edited. For our application, this will differentiate between locally edited and remotely loaded data.

After making my changes, the application is successfully differentiating between locally and remotely changed attributes, fixing my broken test cases in Card, trumpets did play and rainbows appeared. I change my validation as follows.

This will only attempt to validate card number if my card_number responds true to changed? Pretty neat, and the nagging in the back of my mind that this is a hideous solution was suppressed for a few moments!

Clearly, a single API to update or create both parent and children, combined with card_number being retrieved as blank is a terrible design. However, sometimes you need to deal with 3rd parties or legacy systems which have collected a great deal of unintentional mire. I deliberately injected some challenging requirements for this project as its all too easy to demonstrate how to do things in an article or tutorial when you can rig the functionality to only address the most basic functionality.

I still have a failing test on the PlainWallet side of things as I never found a solution which did not require a great deal of hacking, or to replicate Dirty. So score another point for ActiveModel, it managed to cleanse my dirty architecture and cut through those difficult requirements.

Wrapping Up

The Rails guide provide a small page with limited examples, but ActiveModel is a feature rich set of modules which can save you time, reduce brittleness in your code and help refactor-proof your non-persistent model requirements.

There are plenty of weaknesses in the modules, some quirks and some design flaws, but perhaps the biggest problem is that there is a missing module. If you peruse the source code you will see the following methods in both Card and Wallet.

There are some other very basic methods such as Wallet.find, Wallet.create, and save which would look almost identical if I wrote other non persistent models. It would have been nice if Rails supplied a module which mixed in an elegant solution here, but perhaps improving ActiveModel will be the subject for a future article.

ActiveModel won’t necessarily save you time or reduce your code, I believe that both the ActiveModel and Ruby object classes I produced are close enough in size for any improvements to be negligible.

I would recommend using ActiveModel just for the Model/Naming/Conversion and Validations modules alone. Coding objects in a rails way will be better for long term changes, for code readability and for consistency in the application.

As usual, comments, criticism and questions welcome, either leave a comment or email me directly.

 

 

 

 

 

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *