Design patterns in large Ruby on Rails web applications: constructing a Query Object class that is responsible for elegantly querying a database. Read our blog post to find out how to make a simple and easy to test Query Object implementation within a Rails application.
Query Objects are classes specifically responsible for handling complex SQL queries, usually with data aggregation and filtering methods, that can be applied to your database. We use this design pattern in large Ruby on Rails web applications, to improve an app’s maintainability and scalability - which makes programmers’ lives a bit easier along the way and allows for faster development times.
Oftentimes, the code responsible for querying the database is placed within scopes in models, or gets mixed in with other logic. It is usually the same code in many places throughout the app, which goes against the principles of DRY programming (Don’t Repeat Yourself!). This leads to a lengthy development process and makes the code more prone to errors - causing a lot of irritation, especially when there is a need to refactor code or make changes to the database itself during any stage of the query.
Example of a poorly designed query function
So what does a poorly designed query function look like and what troubles can it cause? Have a look at the example below - which is a combination of a few actions, including queries to the database - and check for yourself how hard it is to test it without hitting the database with the real records:
A best programming practices solution for the above? We can simply extract our database queries in Rails in the above code to create Policy Objects and Query Objects. This will make each section of code more isolated - and thus far easier to test.
Construction of a Query Object class
There is no significant difference between a standard class and a Query Object class. Still, like any other design pattern, this one also has its own specific set of rules.
In terms of initializing the class, it is always clever to pass the scope. If the scope is not given, then we use the default one. Thanks to this approach, we can always pass a scope and use pre-filtered results, or create more complex queries by composing multiple query objects. Here is a simple example:
Other benefits of this design pattern
When creating a Query Object class, we make our models slimmer and our logic more decoupled. This means we can create more meaningful and faster tests that focus only on that specific part of the code.
Let’s get back again to the `popular_sport_posts` method presented above and refactor it using a Query Object.
To test this class we are not forced to create real records in the database in order to check behavior. Tests are much faster and the code itself is far more readable. Thanks to the implemented design pattern, we can effortlessly separate database query logic and then just stub the Query Object, testing database communication in an abstracted test class.
See below for yourself:
Query Object and Builder pattern - the perfect pairing
It is worth mentioning that the Query Object pattern works fantastically with the Builder pattern, especially in cases where a scope object is too complicated to just pass it to the initializer. In such an instance it is better to create a base class, which will expose our code for queries.
Usually we keep our Query Objects under `/lib/query_objects/products/sport_query.rb`, where `products` is the name of the database table and the file name, `sport_query.rb`, is a combination of the query suffix and the type of data we are fetching when using this class.
Intrigued by our solution to construct easy-to-test Query Object classes responsible for elegant communication with your database? Want to check out our IT skills and knowledge in practice? Make sure to get in contact with us at iRonin - we can help you with your Ruby on Rails web applications.