{"pageProps":{"topic":{"name":"Ruby on Rails","description":null,"guide":null,"slug":"rails","path":"/dist/src/content/articles/topics/rails.json"},"thoughts":[{"title":{"html":"
On the reasoning why and how you might use a Clean Architecture approach in Rails applications. Warning: it's nuanced and full of compromise.
","plain":"On the reasoning why and how you might use a Clean Architecture approach in Rails applications. Warning: it's nuanced and full of compromise."},"slug":"why-take-a-clean-architecture-approach-to-rails","tags":["Clean Architecture","Ruby on Rails"],"publishedAt":{"pretty":"18th December 2019","iso":"2019-12-18T00:00:00.000+00:00"},"content":{"html":"On the reasoning why and how you might use a Clean Architecture approach in Rails applications. Warning: it's nuanced and full of compromise.
\nA few years ago I was introduced to the concept of Clean Architecture. When I saw it something clicked in my brain. Before Clean Architecture I chose to remove logic from controllers and models of applications I built into Plain Old Ruby Objects (POROs). While I maintained some consistency across projects, my approach to architecting and testing these POROs varied more often than not and it often felt like I was reinventing the wheel.
\nThe POROs were usually placed into a directory like app/services
and called Service Objects or just Services. The Service Object pattern involves the creation classes that represent business logic or behaviour that would otherwise sit too close to the the database in an ActiveRecord model or in the controller.
After having a little google I found references going as far back as 2012 including a blog post on "the service layer [that] lies between controllers and models" and a RailsCasts video on using Service Objects in Rails.
\nThis pattern is probably the most common Rails pattern for keeping controllers and models skinny though it remains decidedly non-standard by Rails creator DHH.
\n\n\nIt's like we have a J2EE renaissance fair going on at the moment. Digging up every discredited pattern of complexity to do a reunion tour.
\n
Haha, I laughed at that one. He was still hurting from his time as a Java developer when he tweeted that I suspect.
\n\n\nIt's given birth to some truly horrendous monstrosities of architecture. A dense jungle of service objects, command patterns, and worse.
\n
Yup, that is the author of Rails' view of service objects. He don't like 'em much does he? He also doesn't like TDD. YMMV.
\nI think it's okay for the Rails author to not support a common idiom within the community, his mileage did vary. We have to have empathy for differing views in a complex world.
\nDHH was right about one thing though: lumping the Service Object and Command pattern together. You'll find the most commonly touted approach to services in Rails is to name them as verbs and have a common public method like #run
, #call
, #execute
or #exec
. This isn't the Service Object pattern in reality – it's the Command pattern as mentioned by Martin Fowler.
Okay so how about Clean Architecture? I've certainly seen "by the book" Clean Architecture applied to Rails applications. Some teams love it! In this pure adoption you will usually find application and enterprise business rules in the lib/
directory of a Rails app rather than app/
. You will also find directories named domain/
or entities/
, interactions/
or use_cases/
, and gateways/
or adapters/
in lib/
.
I've also seen teams of Ruby on Rails developers completely reject Clean Architecture. They already have their idioms for this problem! Something didn't click in their brains like it did in mine. This was clearly a stretch too far from the Rails way.
\nAgain, empathy for all, everyone can have view that differ. For me I found the concept of breaking down business rules into entities, use cases and adapters a fairer split of labour than simply lifting and shifting controller code into a single service object. That said, in many cases where an application is made up of (mostly) simple CRUD, this extra layer of complexity wasn't needed at all.
\nThe world isn't as simple as always using one pattern or another. Sure it's easier to teach one way, rather than the nuances of many, but knowing and being able to argue the tradeoffs with yourself is the real wisdom.
\nThis world of nuance has led me to take a more compromising view on the adoption of Clean Architecture in the Rails world – when it makes sense to use Clean Architecture at all.
\nA more friendly approach for Rails developers is to use more Rails idioms. There are plenty of idioms that match up with the entity, use case and gateway classes from Clean Architecture.
\nThe Services convention in Rails clearly matches that of use case classes that represent actions a user can make on a system. This especially rings true for me when taking the Command pattern approach to Services which is the same convention I see used in Clean Architecture use cases. Just put your use cases in app/services
!
Entities are use to represent enterprise-level business rules. Code that will likely be shared across use cases and business functions. These need only introducing when you want to share behaviour between use cases, or when you want to abstract your use cases from the database and the ActiveRecord pattern. There are plenty of libraries including ActiveModel that provide helpers to make the definition of these classes easier. You may need to debate with your team whether these can sit beside database representations in app/models
or whether you split them out into app/domains
.
Adapters are already known to Rails developers as the Adapter pattern is rife there too. Plenty of applications have adapters for connecting to external APIs or databases. These can go in app/adapters
.
So you see, Clean Architecture isn't actually a million miles away from being idiomatic to Rails developers. Sure it's full of compromise, and is probably only worth it when your application is complex enough. We have to learn these nuances and know when to compromise to deliver value for our users and organisations.
","plain":"On the reasoning why and how you might use a Clean Architecture approach in Rails applications. Warning: it's nuanced and full of compromise.\nA few years ago I was introduced to the concept of Clean Architecture. When I saw it something clicked in my brain. Before Clean Architecture I chose to remove logic from controllers and models of applications I built into Plain Old Ruby Objects (POROs). While I maintained some consistency across projects, my approach to architecting and testing these POROs varied more often than not and it often felt like I was reinventing the wheel.\nBefore Clean Architecture: Services\nThe POROs were usually placed into a directory like app/services and called Service Objects or just Services. The Service Object pattern involves the creation classes that represent business logic or behaviour that would otherwise sit too close to the the database in an ActiveRecord model or in the controller.\nAfter having a little google I found references going as far back as 2012 including a blog post on "the service layer [that] lies between controllers and models" and a RailsCasts video on using Service Objects in Rails.\nThis pattern is probably the most common Rails pattern for keeping controllers and models skinny though it remains decidedly non-standard by Rails creator DHH.\n\nIt's like we have a J2EE renaissance fair going on at the moment. Digging up every discredited pattern of complexity to do a reunion tour.\n\nHaha, I laughed at that one. He was still hurting from his time as a Java developer when he tweeted that I suspect.\n\nIt's given birth to some truly horrendous monstrosities of architecture. A dense jungle of service objects, command patterns, and worse.\n\nYup, that is the author of Rails' view of service objects. He don't like 'em much does he? He also doesn't like TDD. YMMV.\nI think it's okay for the Rails author to not support a common idiom within the community, his mileage did vary. We have to have empathy for differing views in a complex world.\nDHH was right about one thing though: lumping the Service Object and Command pattern together. You'll find the most commonly touted approach to services in Rails is to name them as verbs and have a common public method like #run, #call, #execute or #exec. This isn't the Service Object pattern in reality – it's the Command pattern as mentioned by Martin Fowler.\nIntroducing Clean Architecture: Even further from pure-Rails\nOkay so how about Clean Architecture? I've certainly seen "by the book" Clean Architecture applied to Rails applications. Some teams love it! In this pure adoption you will usually find application and enterprise business rules in the lib/ directory of a Rails app rather than app/. You will also find directories named domain/ or entities/, interactions/ or use_cases/, and gateways/ or adapters/ in lib/.\nI've also seen teams of Ruby on Rails developers completely reject Clean Architecture. They already have their idioms for this problem! Something didn't click in their brains like it did in mine. This was clearly a stretch too far from the Rails way.\nAgain, empathy for all, everyone can have view that differ. For me I found the concept of breaking down business rules into entities, use cases and adapters a fairer split of labour than simply lifting and shifting controller code into a single service object. That said, in many cases where an application is made up of (mostly) simple CRUD, this extra layer of complexity wasn't needed at all.\nThe world isn't as simple as always using one pattern or another. Sure it's easier to teach one way, rather than the nuances of many, but knowing and being able to argue the tradeoffs with yourself is the real wisdom.\nIntroducing Clean Architecture: Services (and other friends)\nThis world of nuance has led me to take a more compromising view on the adoption of Clean Architecture in the Rails world – when it makes sense to use Clean Architecture at all.\nA more friendly approach for Rails developers is to use more Rails idioms. There are plenty of idioms that match up with the entity, use case and gateway classes from Clean Architecture.\nThe Services convention in Rails clearly matches that of use case classes that represent actions a user can make on a system. This especially rings true for me when taking the Command pattern approach to Services which is the same convention I see used in Clean Architecture use cases. Just put your use cases in app/services!\nEntities are use to represent enterprise-level business rules. Code that will likely be shared across use cases and business functions. These need only introducing when you want to share behaviour between use cases, or when you want to abstract your use cases from the database and the ActiveRecord pattern. There are plenty of libraries including ActiveModel that provide helpers to make the definition of these classes easier. You may need to debate with your team whether these can sit beside database representations in app/models or whether you split them out into app/domains.\nAdapters are already known to Rails developers as the Adapter pattern is rife there too. Plenty of applications have adapters for connecting to external APIs or databases. These can go in app/adapters.\nSo you see, Clean Architecture isn't actually a million miles away from being idiomatic to Rails developers. Sure it's full of compromise, and is probably only worth it when your application is complex enough. We have to learn these nuances and know when to compromise to deliver value for our users and organisations."}},{"title":{"html":"On the trouble you can encounter when trying to separate your domain logic from a framework like Rails.
","plain":"On the trouble you can encounter when trying to separate your domain logic from a framework like Rails."},"slug":"decoupling-the-delivery-mechanism","tags":["Clean Architecture","Ruby on Rails"],"publishedAt":{"pretty":"11th August 2018","iso":"2018-08-11T00:00:00.000+00:00"},"content":{"html":"On the trouble you can encounter when trying to separate your domain logic from a framework like Rails.
\nIf you work with Rails and haven't heard of Clean Architecture you may have heard of Hexagonal Architecture and most probably have heard of the Service Object pattern. These patterns seek to keep your controllers and models skinny by using Plain Old Ruby Objects (POROs) to model domain problems. If you do not know these patterns, I suggest you read up a little to understand the context in which this article is written.
\nWhen exploring concepts like Clean Architecture in a Rails context it's often tempting to cut corners. Perhaps rather than using test doubles for Rails dependencies inside your library code you decide to depend on them directly.
\n\n\nIn the example above we now have a direct dependency on the Rails model Ship
and also on the database itself. This means slower tests as they hit the DB and also means the system is harder to change as if you change your model you'll need to change this test too.
Or maybe you decided to use your model as a gateway rather than create a PORO adapter class to encapsulate the model.
\n\n\nHere we have another direct dependency on Rails and the database. Again slower tests and your library needs to change when your application changes.
\nOr you thought you could return models from your gateways and treat it like a domain object.
\n\n\nFinally in this example we return an ActiveRecord model from the gateway and therefore expose a large interface to the wider application. The problem here is that method calls to the Ship
model could trigger SQL queries meaning control of database performance is spread through the codebase rather than solely managed by gateways. This again makes the system harder to reason about and harder to change.
The problem with doing any of this is that you no longer have a library that represents your business logic independent of Rails, that is easy to test and easy to change. Instead you a left with a contrived and non-standard Rails setup that is harder to test and difficult to change. It would have been better to stay omakase.
\nIf running rspec spec/unit/lib
requires you to load rails_helper.rb
you've already fallen fowl of coupling your library to Rails. Allow me to apologise for the lack of information out there that might have helped you avoid this situation. At this point you have one of two options:
rails_helper.rb
app/
directory and keep to a more standard omakase approachThere's a more general rule here too that goes beyond Clean Architecture and Rails. Your library code should not depend on any delivery mechanism, database, or API. There should be no need to depend on database fixtures, factories or depend on framework or database classes being defined. This rule exists to make change cheap in the future.
\nOf course, you will likely have acceptance and feature tests that will depend on rails_helper.rb
and that's okay. You want to test when delivering your library via Rails that everything works in harmony. This will only be a certain percentage of your tests. Remember the testing pyramid?
As a rule the unit tests for your library, usually found in spec/unit/lib
, should not need to depend on Rails.
In example one we saw the gateway specs relying on Rails for setting up database state. We can avoid this by using RSpec's class_double
and instance_double
.
The test remains largely the same except that there is no direct dependency on Rails this time. We add another test, 'retrieves ship from model'
, to ensure that we call the mock as expected, this replaces the need to rely on the state of the database.
In example two we saw a use case using an ActiveRecord model directly as a gateway. Not only this but the spec directly depended on the model and database state via FactoryBot.
\n\n\nInstead of using ActiveRecord as the gateway we instead rely on an adapter gateway Space::Flight::ShipGateway
. We go even further by not directly depending on the gateway and instead use instance_double
to mock it out. This approach decouples the use case from ActiveRecord resulting in a spec that doesn't touch the database.
In example three Space::Flight::ShipGateway
returns an ActiveRecord model from it's #find_by_id
method. We really should have the discipline to return a domain object from the gateway instead.
Here we define Space::Flight::Ship
a domain object that exposes a limited amount a functions compared to an ActiveRecord model. Our gateway constructs this domain object and returns it.
It takes discipline as a software engineer to keep interfaces clean between the various layers of your application. This is especially true in Ruby where interfaces do not exist as part of it's OOP implementation.
\nDiscipline and experience leads to good architecture.
\n\n","plain":"On the trouble you can encounter when trying to separate your domain logic from a framework like Rails.\nIf you work with Rails and haven't heard of Clean Architecture you may have heard of Hexagonal Architecture and most probably have heard of the Service Object pattern. These patterns seek to keep your controllers and models skinny by using Plain Old Ruby Objects (POROs) to model domain problems. If you do not know these patterns, I suggest you read up a little to understand the context in which this article is written.\n\nWhen exploring concepts like Clean Architecture in a Rails context it's often tempting to cut corners. Perhaps rather than using test doubles for Rails dependencies inside your library code you decide to depend on them directly.\n\n\nIn the example above we now have a direct dependency on the Rails model Ship and also on the database itself. This means slower tests as they hit the DB and also means the system is harder to change as if you change your model you'll need to change this test too.\nOr maybe you decided to use your model as a gateway rather than create a PORO adapter class to encapsulate the model.\n\n\nHere we have another direct dependency on Rails and the database. Again slower tests and your library needs to change when your application changes.\nOr you thought you could return models from your gateways and treat it like a domain object.\n\n\nFinally in this example we return an ActiveRecord model from the gateway and therefore expose a large interface to the wider application. The problem here is that method calls to the Ship model could trigger SQL queries meaning control of database performance is spread through the codebase rather than solely managed by gateways. This again makes the system harder to reason about and harder to change.\nThe problem with doing any of this is that you no longer have a library that represents your business logic independent of Rails, that is easy to test and easy to change. Instead you a left with a contrived and non-standard Rails setup that is harder to test and difficult to change. It would have been better to stay omakase.\nYour library should not depend on Rails\nIf running rspec spec/unit/lib requires you to load rails_helper.rb you've already fallen fowl of coupling your library to Rails. Allow me to apologise for the lack of information out there that might have helped you avoid this situation. At this point you have one of two options:\n\nFind a way not to depend on rails_helper.rb\nMove your library code back into the app/ directory and keep to a more standard omakase approach\n\nThere's a more general rule here too that goes beyond Clean Architecture and Rails. Your library code should not depend on any delivery mechanism, database, or API. There should be no need to depend on database fixtures, factories or depend on framework or database classes being defined. This rule exists to make change cheap in the future.\nOf course, you will likely have acceptance and feature tests that will depend on rails_helper.rb and that's okay. You want to test when delivering your library via Rails that everything works in harmony. This will only be a certain percentage of your tests. Remember the testing pyramid?\n\nAs a rule the unit tests for your library, usually found in spec/unit/lib, should not need to depend on Rails.\nMocking out ActiveRecord in your gateway unit tests\nIn example one we saw the gateway specs relying on Rails for setting up database state. We can avoid this by using RSpec's class_double and instance_double.\n\n\nThe test remains largely the same except that there is no direct dependency on Rails this time. We add another test, 'retrieves ship from model', to ensure that we call the mock as expected, this replaces the need to rely on the state of the database.\nMocking out gateways in your use case unit tests\nIn example two we saw a use case using an ActiveRecord model directly as a gateway. Not only this but the spec directly depended on the model and database state via FactoryBot.\n\n\nInstead of using ActiveRecord as the gateway we instead rely on an adapter gateway Space::Flight::ShipGateway. We go even further by not directly depending on the gateway and instead use instance_double to mock it out. This approach decouples the use case from ActiveRecord resulting in a spec that doesn't touch the database.\nAvoid returning ActiveRecord models from your gateways\nIn example three Space::Flight::ShipGateway returns an ActiveRecord model from it's #find_by_id method. We really should have the discipline to return a domain object from the gateway instead.\n\n\nHere we define Space::Flight::Ship a domain object that exposes a limited amount a functions compared to an ActiveRecord model. Our gateway constructs this domain object and returns it.\nDiscipline as a software engineer\nIt takes discipline as a software engineer to keep interfaces clean between the various layers of your application. This is especially true in Ruby where interfaces do not exist as part of it's OOP implementation.\nDiscipline and experience leads to good architecture.\n\nGood architecture makes the system easy to understand, easy to develop, easy to maintain, and easy to deploy.\nClean Architecture by Robert C. Martin\n"}},{"title":{"html":"Good architecture makes the system easy to understand, easy to develop, easy to maintain, and easy to deploy.\nClean Architecture by Robert C. Martin
\n
On structuring Rails apps for growth. Often a tricky area this article will walk you through a refactor and hopefully you'll walk away with a few more ideas for structuring your business logic.
","plain":"On structuring Rails apps for growth. Often a tricky area this article will walk you through a refactor and hopefully you'll walk away with a few more ideas for structuring your business logic."},"slug":"business-logic-in-rails","tags":["Ruby on Rails"],"publishedAt":{"pretty":"24th September 2016","iso":"2016-09-24T00:00:00.000+00:00"},"content":{"html":"On structuring Rails apps for growth. Often a tricky area this article will walk you through a refactor and hopefully you'll walk away with a few more ideas for structuring your business logic.
\nI read and loved Tom Dalling's post about Isolating Side Effects in Ruby today and agree with a lot of his sentiments with regards to functional core, imperative shell. I want to expand on the testing of business logic (also known as domain logic) in Rails with his examples and continue on to explain how we can evolve our applications as we add more features to them. I'll be referring to the post quite a bit so it is probably best you read that first.
\nTom discusses moving the business logic into what he calls functionally pure methods within a static/singleton class. His use of the phrase "functionally pure" is quite the liberty as he admits in his own article.
\n\n\nRevisiting the Billing
module we can observe a few things:
.billable_accounts
performs an SQL query using an ActiveRecord object.monthly_bill
returns an initialised ActiveRecord object.discounts
is functionally pure business logicThe first two methods aren't really functionally pure but how does this affect their testability? We can jump straight into testing .billable_accounts
.
Unfortunately this test hit the DB. The fact the method hits the DB will to some mean the method isn't functionally pure or business logic at all. The method isn't functionally pure because even though you do not pass any parameters, the values it change can vary depending on what is in the DB. Pure functions should return the same results every time they are called with the same arguments. It's not business logic either as it deals with implmentation specific details such as it is use of ActiveRecord methods.
\nFor now, I suppose we could do some mocking to get around this.
\n\n\nI'm not opposed to resorting to this if we need to get a method under test quickly. Luckily ruby and RSpec make this kind of thing easy. You certainly would not be able to do this in PHP or Java.
\nMoving onto .monthly_bill
we should notice it is a little easier to test.
The tests here are not too bad. .monthly_bill
is easier to test as it is mostly business logic and doesn't rely on complex external interfaces. The only external interfaces it relys on is account#plan#amount
, account#type
and Bill.new
.
If we structure our assertions into an actual RSpec suite, our tests describe our billing domain well. This isn't a bad place to be. The suite entirely avoids hitting the DB so it'll be fast.
\n\n\nOne thing to note is that we are testing .discounts
in the .monthly_bill
example as well as in it is own specific test. To me this signals that we are probably exposing functionality that does not need to be exposed. Calculating discounts is only used when we are creating a monthly bill so we can probably hide that functionality and test it indirectly with our "creating monthly bill" context.
After making .discounts
private our test suite will begin to fail with NoMethodError: private method 'discount' called for Billing:Module
. This is okay, we can now delete this failing test.
Tom goes on to talk about Skinny Models and using objects in Rails to model actions rather than things. The Billing
module is an object that performs actions rather than modelling a thing.
The Account
and Bill
ActiveRecord objects do model things, but we've kept the business logic separate by not placing that logic inside the models.
Unfortunately the way we've built Billing
module means it will only have a short shelf life. Billing is a large domain so the module will likely get bigger and bigger. Not only that but it is responsible for two separate actions: querying billing accounts and creating bills. Once we start adding more billing related actions to this module, for example refunding a bill, the test suite will grow along with the module itself which to me means it is more difficult for Engineers to quickly understand the responsibilities of the module and therefore more difficult to change it.
Luckily the piece of wisdom shared in the post provides the answer.
\n\n\nEnlightenment comes when you use objects in a server-side web application to model actions, not things.
\n– Brad Urani
\n
We need to use objects (read: plural) to model actions. We simply need to split the file down into responsibilities.
\nWhat would breaking down the billing module into individual actions look like?
\n\n\nWe can then split out the RSpec examples.
\n\n\nWe've now got two billing modules for two different topics, Billing::Accounts
and Billing::MonthlyBill
. However from the name of these modules it still feels like we've moved back to modelling things rather than actions.
In order to categorise our logic into actions we need to think about triggers the actions. What is consuming our business logic? From Tom's original example he was tying everything together in a job class.
\n\n\nAbove is the updated job to match the changes we've made in this article. From the name of the classes I'm still not getting a clear picture what is happening here. Reading the code of the job does tell us, but it is not easy to understand at a glance. What if the job called an action object?
\n\n\nI think this is a lot easier to understand. We're passing in our create_and_send_monthly_bill
object and calling #to_all_accounts
on it. From the name of the parameter and the method called we paint a clear picture of what is going on.
As you can see our MonthlyBillingJob
can now be tested without as many mocks as before.
We of course now need to create our create and send monthly bill action.
\n\n\nEssentially the code from the job class is now in this domain specific action class. The RSpec example will therefore be fairly similar to the old job spec.
\n\n\nQuite a jumble as in the original post. I think this in itself is a smell about the way our code works. We have to do a fair bit of mocking in order to test our action because our CreateAndSendMonthlyBill
action calls Billing::Accounts
and Billing::MonthlyBill
directly. We are also duplicating our testing efforts again.
One solution to this problem would be to inject Billing::Accounts
and Billing::MonthlyBill
into our action. This will allow us to create doubles in our test and pass those in. This would mean our mocking would be simplified and we will reduce the duplication of our tests.
Ha, that didn't go as well as I expected. It's actually more number of lines than our previous test. I think this is a reflection of the design of our business logic. Business logic should be easy to understand and easy to test. These properties should exist when we reach a good design. I often find gut instinct tells me if the design is good and I think this is informed by how easily my brain can understand the code.
\nWe'll need to update our implementation of CreateAndSendMonthlyBill
to satisfy this test.
We've called the variable storing the MonthlyBill
monthly_bill_initialiser
which does clearly explain what it does, but the method #to_all_accounts
is now a little harder to understand.
We should probably move the creation and sending of bills into their own actions that are composed together in order to achieve the larger create and send action. The CreateAndSendMonthlyBill
contains the word "and". This to me signals there are two separate concerns here. We could move the two concerns into their own classes and then use them within the bigger action.
Looking at CreateAndSendMonthlyBill#to_all_accounts
the code now makes more sense when you read it.
Our test can now be split up which will reduce the complexity of them and make them easy to understand too.
\n\n\nOur app is a lot easier to understand from the filesystem level too.
\n\n\nWe can at a glance of the file names know what our application does.
\nWe are almost there but we still have our Billing::Accounts
and Billing::MonthlyBill
modules that represent things rather than actions.
Billing::Accounts
is an easy win as we just change the class name to begin with a verb, "find".
Billing::MonthlyBill
is a little harder to change. It is responsible for initialising a Bill
object with a correct amount. This feels very much related to the creation of the bill to me. It's almost as if we could move all the logic into Billing::CreateMonthlyBill
.
Doing this violates one of Tom's rules about not mixing business logic with things that have side effects. However for me, at this point in time no other object needs to initialise a Bill
with the same logic so until that need arises I would in fact keep it all in this class.
You'll have probably noticed that we now inject an empty Bill
object. This is to keep things easy to test.
The tests don't look too bad at all. Our folder structure is looking really informative too.
\n\n\nCode's testability is very much affected by its design and structure. You might say that your tests inform the design of your code. I prefer to think that the design supports easier testing because it has an easy to understand structure. Code that is easily tested is typically easier to understand.
\nBusiness logic is easier to understand when expressed as actions. This allows Engineers to understand the function of your domain by simply reading file names. It also means it is easy to find relevant parts of your domain and they remain easy to test.
\nThe structure presented in this article isn't new. Uncle Bob and Gary Bernhardt along with many others have been talking about this before. Some call "actions" by their other name "use case classes".
\nHopefully with this design you can avoid fat controllers and fat models. Instead we can have skinny everything when we break down our domain into easy to understand pieces.
\nThanks for bearing with me, and feel free to tweet your feedback to me.
","plain":"On structuring Rails apps for growth. Often a tricky area this article will walk you through a refactor and hopefully you'll walk away with a few more ideas for structuring your business logic.\nI read and loved Tom Dalling's post about Isolating Side Effects in Ruby today and agree with a lot of his sentiments with regards to functional core, imperative shell. I want to expand on the testing of business logic (also known as domain logic) in Rails with his examples and continue on to explain how we can evolve our applications as we add more features to them. I'll be referring to the post quite a bit so it is probably best you read that first.\nTom discusses moving the business logic into what he calls functionally pure methods within a static/singleton class. His use of the phrase "functionally pure" is quite the liberty as he admits in his own article.\n\n\nRevisiting the Billing module we can observe a few things:\n\n.billable_accounts performs an SQL query using an ActiveRecord object\n.monthly_bill returns an initialised ActiveRecord object\n.discounts is functionally pure business logic\n\nTesting business logic\nThe first two methods aren't really functionally pure but how does this affect their testability? We can jump straight into testing .billable_accounts.\n\n\nUnfortunately this test hit the DB. The fact the method hits the DB will to some mean the method isn't functionally pure or business logic at all. The method isn't functionally pure because even though you do not pass any parameters, the values it change can vary depending on what is in the DB. Pure functions should return the same results every time they are called with the same arguments. It's not business logic either as it deals with implmentation specific details such as it is use of ActiveRecord methods.\nFor now, I suppose we could do some mocking to get around this.\n\n\nI'm not opposed to resorting to this if we need to get a method under test quickly. Luckily ruby and RSpec make this kind of thing easy. You certainly would not be able to do this in PHP or Java.\nMoving onto .monthly_bill we should notice it is a little easier to test.\n\n\nThe tests here are not too bad. .monthly_bill is easier to test as it is mostly business logic and doesn't rely on complex external interfaces. The only external interfaces it relys on is account#plan#amount, account#type and Bill.new.\nReviewing the test suite\nIf we structure our assertions into an actual RSpec suite, our tests describe our billing domain well. This isn't a bad place to be. The suite entirely avoids hitting the DB so it'll be fast.\n\n\nOne thing to note is that we are testing .discounts in the .monthly_bill example as well as in it is own specific test. To me this signals that we are probably exposing functionality that does not need to be exposed. Calculating discounts is only used when we are creating a monthly bill so we can probably hide that functionality and test it indirectly with our "creating monthly bill" context.\n\n\nAfter making .discounts private our test suite will begin to fail with NoMethodError: private method 'discount' called for Billing:Module. This is okay, we can now delete this failing test.\nGrowing the domain\nTom goes on to talk about Skinny Models and using objects in Rails to model actions rather than things. The Billing module is an object that performs actions rather than modelling a thing.\nThe Account and Bill ActiveRecord objects do model things, but we've kept the business logic separate by not placing that logic inside the models.\nUnfortunately the way we've built Billing module means it will only have a short shelf life. Billing is a large domain so the module will likely get bigger and bigger. Not only that but it is responsible for two separate actions: querying billing accounts and creating bills. Once we start adding more billing related actions to this module, for example refunding a bill, the test suite will grow along with the module itself which to me means it is more difficult for Engineers to quickly understand the responsibilities of the module and therefore more difficult to change it.\nLuckily the piece of wisdom shared in the post provides the answer.\n\nEnlightenment comes when you use objects in a server-side web application to model actions, not things.\n– Brad Urani\n\nWe need to use objects (read: plural) to model actions. We simply need to split the file down into responsibilities.\nWhat would breaking down the billing module into individual actions look like?\n\n\nWe can then split out the RSpec examples.\n\n\nModel actions not things\nWe've now got two billing modules for two different topics, Billing::Accounts and Billing::MonthlyBill. However from the name of these modules it still feels like we've moved back to modelling things rather than actions.\nIn order to categorise our logic into actions we need to think about triggers the actions. What is consuming our business logic? From Tom's original example he was tying everything together in a job class.\n\n\nAbove is the updated job to match the changes we've made in this article. From the name of the classes I'm still not getting a clear picture what is happening here. Reading the code of the job does tell us, but it is not easy to understand at a glance. What if the job called an action object?\n\n\nI think this is a lot easier to understand. We're passing in our create_and_send_monthly_bill object and calling #to_all_accounts on it. From the name of the parameter and the method called we paint a clear picture of what is going on.\n\n\nAs you can see our MonthlyBillingJob can now be tested without as many mocks as before.\nWe of course now need to create our create and send monthly bill action.\n\n\nEssentially the code from the job class is now in this domain specific action class. The RSpec example will therefore be fairly similar to the old job spec.\n\n\nQuite a jumble as in the original post. I think this in itself is a smell about the way our code works. We have to do a fair bit of mocking in order to test our action because our CreateAndSendMonthlyBill action calls Billing::Accounts and Billing::MonthlyBill directly. We are also duplicating our testing efforts again.\nOne solution to this problem would be to inject Billing::Accounts and Billing::MonthlyBill into our action. This will allow us to create doubles in our test and pass those in. This would mean our mocking would be simplified and we will reduce the duplication of our tests.\n\n\nHa, that didn't go as well as I expected. It's actually more number of lines than our previous test. I think this is a reflection of the design of our business logic. Business logic should be easy to understand and easy to test. These properties should exist when we reach a good design. I often find gut instinct tells me if the design is good and I think this is informed by how easily my brain can understand the code.\nWe'll need to update our implementation of CreateAndSendMonthlyBill to satisfy this test.\n\n\nWe've called the variable storing the MonthlyBill monthly_bill_initialiser which does clearly explain what it does, but the method #to_all_accounts is now a little harder to understand.\nComposing actions\nWe should probably move the creation and sending of bills into their own actions that are composed together in order to achieve the larger create and send action. The CreateAndSendMonthlyBill contains the word "and". This to me signals there are two separate concerns here. We could move the two concerns into their own classes and then use them within the bigger action.\n\n\nLooking at CreateAndSendMonthlyBill#to_all_accounts the code now makes more sense when you read it.\nOur test can now be split up which will reduce the complexity of them and make them easy to understand too.\n\n\nOur app is a lot easier to understand from the filesystem level too.\n\n\nWe can at a glance of the file names know what our application does.\nFinishing up\nWe are almost there but we still have our Billing::Accounts and Billing::MonthlyBill modules that represent things rather than actions.\n\n\nBilling::Accounts is an easy win as we just change the class name to begin with a verb, "find".\nBilling::MonthlyBill is a little harder to change. It is responsible for initialising a Bill object with a correct amount. This feels very much related to the creation of the bill to me. It's almost as if we could move all the logic into Billing::CreateMonthlyBill.\n\n\nDoing this violates one of Tom's rules about not mixing business logic with things that have side effects. However for me, at this point in time no other object needs to initialise a Bill with the same logic so until that need arises I would in fact keep it all in this class.\nYou'll have probably noticed that we now inject an empty Bill object. This is to keep things easy to test.\n\n\nThe tests don't look too bad at all. Our folder structure is looking really informative too.\n\n\nConclusion\nCode's testability is very much affected by its design and structure. You might say that your tests inform the design of your code. I prefer to think that the design supports easier testing because it has an easy to understand structure. Code that is easily tested is typically easier to understand.\nBusiness logic is easier to understand when expressed as actions. This allows Engineers to understand the function of your domain by simply reading file names. It also means it is easy to find relevant parts of your domain and they remain easy to test.\nThe structure presented in this article isn't new. Uncle Bob and Gary Bernhardt along with many others have been talking about this before. Some call "actions" by their other name "use case classes".\nHopefully with this design you can avoid fat controllers and fat models. Instead we can have skinny everything when we break down our domain into easy to understand pieces.\nThanks for bearing with me, and feel free to tweet your feedback to me."}},{"title":{"html":"In which I outline a strategy for Feature testing with rspec and capybara.
","plain":"In which I outline a strategy for Feature testing with rspec and capybara."},"slug":"feature-testing-in-2016","tags":["Ruby on Rails"],"publishedAt":{"pretty":"9th January 2016","iso":"2016-01-09T00:00:00.000+00:00"},"content":{"html":"In which I outline a strategy for Feature testing with rspec and capybara.
\nAt the end of last year I, along with friend, colleague and fellow islander\nDavid decided upon a set way of writing\nfeature tests across our rails projects. Based on frustrations with cucumber,\nregex and too much code sharing between scenarios the following strategy was\ndevised.
\nIt builds upon a blog post by Future Learn on\nwriting readable feature tests in rspec. Without further ado:
\n\n\nAlong with this structure there are some rules for keeping things tidy and\nmaintainable:
\n#assert_something
I wrote up more of the whys over on our\nMade Tech blog. This\nwas before some of our more stricter rules were put in place.
\nWhat do you think? Get in touch via twitter\n@LukeMorton.
","plain":"In which I outline a strategy for Feature testing with rspec and capybara.\nAt the end of last year I, along with friend, colleague and fellow islander\nDavid decided upon a set way of writing\nfeature tests across our rails projects. Based on frustrations with cucumber,\nregex and too much code sharing between scenarios the following strategy was\ndevised.\nIt builds upon a blog post by Future Learn on\nwriting readable feature tests in rspec. Without further ado:\n\n\nAlong with this structure there are some rules for keeping things tidy and\nmaintainable:\n\nOnly one "given/when/then" per scenario (never start a step with "and")\nNever reuse "given/when/then" steps between scenarios\nAlways define steps within the scope of the feature\nDefine lets after private declaration for separation\nAny shared logic between steps should be placed in private methods defined\nbelow your let statements\nComplicated or multiple assertions in your "then" steps should be placed\nin well named methods like #assert_something\nRely on lets rather than instance variables\n\nI wrote up more of the whys over on our\nMade Tech blog. This\nwas before some of our more stricter rules were put in place.\nWhat do you think? Get in touch via twitter\n@LukeMorton."}},{"title":{"html":"In which I provide a few links to help scale the M in MVC,\nthe ActiveRecord in rails.
","plain":"In which I provide a few links to help scale the M in MVC,\nthe ActiveRecord in rails."},"slug":"better-active-record-mileage","tags":["Ruby on Rails"],"publishedAt":{"pretty":"12th September 2015","iso":"2015-09-12T00:00:00.000+00:00"},"content":{"html":"In which I provide a few links to help scale the M in MVC,\nthe ActiveRecord in rails.
\nThe basis of this post comes from one tweet I read.
\nThe greatest trick the ORM ever pulled was convincing the world the DB doesn't exist... and it's a disaster for a generation of devs
— Brad Urani (@bradurani) September 6, 2015
\n\nI saw this tweet by Brad and had a response that I commonly have to positions\nor declarations in the world of software engineering.
\n\n\nThat's a bit extreme.
\n
More and more I have this view. I feel rather mellow. That said, I responded on\ntwitter almost a troll comment which immediately sinks me into having a view\nwhich again could be considered extreme.
\n@bradurani @Baranosky People are still shipping products though?
— Luke Morton (@LukeMorton) September 6, 2015
\n\nAh, isn't life full of ironic opportunities. Anyway...
\nPeople are still shipping products but I think Brad is right in a way. The more\nwe introduce engineers to the world of web development via rails the more\nabstracted away from the concepts of the database they are. In the world of\nsmall business, the one I choose to operate in, roles aren't well defined.\nFull stack is about as defined as my role can get since on any given day I can\nbe building out UI components with Sass/BEM/pieces, designing refund\nsystems for Spree applications, setting up continuous delivery practices for new\nclients, writing chef recipes, finding new hires or writing blog\nposts. I didn't even mention databases here or the scaling of your models\nwhich are yet more skills required for generalists.
\nFor small businesses and for people entering the world of rails (or whatever\nyour framework) it's easy to become a generalist and suffer the consequences of\nbecoming the master of none. We need to be mindful as our\napplications grow how to keep control of our ORMs.
\nBrad wrote a follow up post which I recommend you go read now before continuing.
\n\nThe author writes the common pitfalls of Active Record, statements of denial\nand provides some resources to how we might fix these problems. I'd like to add\nto the mix a bunch of resources I find useful for tackling these issues.
\nGreat article on how to break down models with a trip of patterns.\nAlthough this author introduces these concepts with the aid of a gem, I think\nwe can achieve these patterns without any additional dependencies.
\nhttp://victorsavkin.com/post/41016739721/building-rich-domain-models-in-rails-separating
\nPiotr wrote a great piece on the things he's learnt whilst being a rails\ndeveloper:
\nhttp://solnic.eu/2015/03/04/8-things-i-learned-during-8-years-of-ruby-and-rails.html
\nAlright, this is a plug to one of my blog posts at Made. It's on topic though\nand highlights how we might better use object oriented as well as functional\nprogramming practices to scale our models further.
\nhttps://www.madetech.com/blog/boundaries-in-object-oriented-design
\nRather than leaning on complex models we can instead lean on hashes or hash\nlike objects to transfer data around our applications.
\nI hesistated in posting this one since it's yet another list of design patterns.\nThen again, this whole blog post is about links to design patterns so it's\nincluded for completeness.
\nI'm going to write in the future on how design patterns are introducing more\nproblems to our applications through their blind use.
\nhttp://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
\nBy using Query and Command objects we can avoid the necessity for callbacks\nwhich are often a cause for confusing bugs.
\nhttp://www.mattjohnston.co/blog/2013/07/07/dumb-data-objects/
\nIt's getting easier to use straight up SQL in rails.
\nhttps://github.com/rails/rails/pull/21536
\nUse ROM.rb instead!!
\n\nUse Lotus instead!!
\n\nOkay those last two links are more inspirational than aspirational. As engineers\nwho chose shops that use rails, we won't be escaping Active Record or rails in\ngeneral any time soon. Hopefully I've provided a few more links that\ncan help you scale out your models and ORMs further.
\nI haven't however provided resources to how you can utilise the power of your\ndatabase further. Or even learn as a rubyist how to be a DBA. One thing that\ncame out of my education was database normalisation a concept some developers\nhaven't heard of. Let the conversation continue...
","plain":"In which I provide a few links to help scale the M in MVC,\nthe ActiveRecord in rails.\nThe basis of this post comes from one tweet I read.\nThe greatest trick the ORM ever pulled was convincing the world the DB doesn't exist... and it's a disaster for a generation of devs— Brad Urani (@bradurani) September 6, 2015\n\n\n\nI saw this tweet by Brad and had a response that I commonly have to positions\nor declarations in the world of software engineering.\n\nThat's a bit extreme.\n\nMore and more I have this view. I feel rather mellow. That said, I responded on\ntwitter almost a troll comment which immediately sinks me into having a view\nwhich again could be considered extreme.\n@bradurani @Baranosky People are still shipping products though?— Luke Morton (@LukeMorton) September 6, 2015\n\n\n\nAh, isn't life full of ironic opportunities. Anyway...\nPeople are still shipping products but I think Brad is right in a way. The more\nwe introduce engineers to the world of web development via rails the more\nabstracted away from the concepts of the database they are. In the world of\nsmall business, the one I choose to operate in, roles aren't well defined.\nFull stack is about as defined as my role can get since on any given day I can\nbe building out UI components with Sass/BEM/pieces, designing refund\nsystems for Spree applications, setting up continuous delivery practices for new\nclients, writing chef recipes, finding new hires or writing blog\nposts. I didn't even mention databases here or the scaling of your models\nwhich are yet more skills required for generalists.\nFor small businesses and for people entering the world of rails (or whatever\nyour framework) it's easy to become a generalist and suffer the consequences of\nbecoming the master of none. We need to be mindful as our\napplications grow how to keep control of our ORMs.\nResources for getting along with your ORM\nBrad wrote a follow up post which I recommend you go read now before continuing.\nhttps://medium.com/@bradurani/turning-the-tables-how-to-get-along-with-your-object-relational-mapper-e5d2d6a76573\nThe author writes the common pitfalls of Active Record, statements of denial\nand provides some resources to how we might fix these problems. I'd like to add\nto the mix a bunch of resources I find useful for tackling these issues.\nTackling god objects with entities, data objects and repositories\nGreat article on how to break down models with a trip of patterns.\nAlthough this author introduces these concepts with the aid of a gem, I think\nwe can achieve these patterns without any additional dependencies.\nhttp://victorsavkin.com/post/41016739721/building-rich-domain-models-in-rails-separating\nLearn from others\nPiotr wrote a great piece on the things he's learnt whilst being a rails\ndeveloper:\nhttp://solnic.eu/2015/03/04/8-things-i-learned-during-8-years-of-ruby-and-rails.html\nUse OO boundaries more efficiently\nAlright, this is a plug to one of my blog posts at Made. It's on topic though\nand highlights how we might better use object oriented as well as functional\nprogramming practices to scale our models further.\nhttps://www.madetech.com/blog/boundaries-in-object-oriented-design\nUse data objects\nRather than leaning on complex models we can instead lean on hashes or hash\nlike objects to transfer data around our applications.\n\nhttp://brewhouse.io/2015/07/31/be-nice-to-others-and-your-future-self-use-data-objects.html\n/thoughts/2013-09-23-hashes-for-data\n\nMoar patterns\nI hesistated in posting this one since it's yet another list of design patterns.\nThen again, this whole blog post is about links to design patterns so it's\nincluded for completeness.\nI'm going to write in the future on how design patterns are introducing more\nproblems to our applications through their blind use.\nhttp://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/\nAvoid callbacks\nBy using Query and Command objects we can avoid the necessity for callbacks\nwhich are often a cause for confusing bugs.\nhttp://www.mattjohnston.co/blog/2013/07/07/dumb-data-objects/\nUse SQL prepared statements in rails 5\nIt's getting easier to use straight up SQL in rails.\nhttps://github.com/rails/rails/pull/21536\nAvoid ActiveRecord\nUse ROM.rb instead!!\nhttp://rom-rb.org/\nAvoid rails\nUse Lotus instead!!\nhttp://lotusrb.org/\nConclusion\nOkay those last two links are more inspirational than aspirational. As engineers\nwho chose shops that use rails, we won't be escaping Active Record or rails in\ngeneral any time soon. Hopefully I've provided a few more links that\ncan help you scale out your models and ORMs further.\nI haven't however provided resources to how you can utilise the power of your\ndatabase further. Or even learn as a rubyist how to be a DBA. One thing that\ncame out of my education was database normalisation a concept some developers\nhaven't heard of. Let the conversation continue..."}}]},"__N_SSG":true}