DEV Community

NicolaiGorden
NicolaiGorden

Posted on

Custom Validations and Methods in Rails

I've been using Ruby on Rails for a few months now, and despite the fact that I have much more experience with frontend tech, doing backend logic has genuinely been a lot less stressful; mostly because Rails streamlines so much of the workload. And not only just for the backend! In the couple projects I've been able to do, it's been very noticeable how much of my (model-based) logic has moved from ReactJS to Rails' controllers and models. (mainly because the elegance of ruby's syntax makes writing logic ever-so-slightly more understandable, to me.) Let's go through some custom validations and methods to see what kind of workload we can offload to the backend.

Something important to remember is that, because we don't want to spaghettify our code on either ends, we have to write methods that we know are necessary. We and to keep our code as clean as possible so managing state in the frontend is less of a pain. Rails already gives us A LOT of flexibility, so it's important to peek at the documentation every now and then because usually, something you need to do is covered in some method or validation there.
In one of my projects, specifically an auction building site, I have three models connected through a joins table in Rails. There are 'users' which own 'bids' for 'items'. These tables all have very basic, standard data tables, name, price and such. However, for this particular project, I wanted to make sure that, for a given item, the user:

  1. Cannot place a bid worth less than the highest bid on the current object.
  2. Can very readily see the highest bid on any given component an item lives in. This means I want my item model to be have some kind of 'highest bid' field or method, which would requires custom logic that can knab data through join tables from within the item model. Thankfully, Rails can do this!
  def highest_bid
      if self.object.bids.length > 0
        self.object.bids.map{|bid| bid.amount}.max()
      else
        self.object.start_price
      end
  end
Enter fullscreen mode Exit fullscreen mode

I have this method saved in my ItemSerializer (through active_model_serializers). We map through an array of bids connected to the object (if there are any), returning the highest bid. If there are no bids, we return the starting price (remember to account for any possible state of your backend data, as to avoid errors). Not only do we now have a highest_bid key-value pair we can pass to the front end, it's also dynamic, eliminating the need to do much of that data handling on outside of our database. Avoiding repeating code is always good, especially when you can keep that frontend clear.

Of course, this all comes at the cost of having to juggle performing CRUD actions in the backend while also dynamically updating the frontend. We need to either be selective about how we build our models and how much data is stored in their tables, or we need to make use of custom rendering and associations. A lot of this is where pre-planning and pseudo code becomes useful, especially in larger-scale applications where data is more complex and specific. It can be quite easy to overcomplicate your database, and your greatest tool is preplanning; avoiding creating unnecessary code can sound daunting, but it's usually a side-effect of doing things without a solid plan before hand, trust me, I would know. Rails provides tools that expedite the completion of projects, if you use them right; this means not writing custom methods for every single little piece of logic, a lot of this can be handled on the front end. In my opinion, it becomes much more pertinent to create alternatives when your application has something specific and encompassing it requires to work, or at least, function in a satisfying way.

One last quick one, a validation this time.

    def must_contain_uppercase
        if password != nil
            unless password.match(/[[:upper:]]/)
                errors.add(:password, message: "must contain an uppercase character!")
            end
        end
    end
Enter fullscreen mode Exit fullscreen mode

A lot simpler, but just know you don't have to stick with the validations Rails provides (even though they're incredibly useful, in fact I'm sure this exists already. Seriously, people. Docs.) Here, we're just checking if the password response in a create(or update) method contains an uppercase character. Then, we pass a custom error message to the frontend, for us to save in state for later.

At the end of the day, it's still Ruby, and you can quite easily write your own logic. Rails makes things easy, but you can make them even easier, just make sure to write your backend logic where it's absolutely necessary

Top comments (0)