dcangulo
dcangulo

Reputation: 2107

Override 'all' method of a model in Ruby on Rails

I have an Account model that has columns:

id :bigint(8)
name :string

In app/models/account.rb, I have:

class Account < ApplicationRecord
  has_one_attached :logo
end

This is an API, and the controller only returns JSON. In my controller, I have:

render :json => Account.all

The code above returns:

[
  {
    "id": 1,
    "name": "Example1"
  },
  {
    "id": 2,
    "name": "Example2"
  }
]

I want to also include the url of the ActiveStorage file attached to the JSON returned.

In order to achieve that, I am doing this in the controller:

def index
  @accounts =  
    Account.all.map do |account|
      account =
        account.attributes.merge(
          :logo_url =>
            account.logo.attached? ?
              polymorphic_url(account.logo) : nil
        )
  end

  render :json => @accounts
end

This will return:

[
  {
    "id": 1,
    "name": "Example1",
    "logo_url": "/path/to/logo.jpg"
  },
  {
    "id": 2,
    "name": "Example2",
    "logo_url": "/path/to/logo.jpg"
  }
]

However, I don't want to do this whenever I call Account.all; I want to automatically add the logo_url whenever Account.all is called.

I have come up with this solution; instead of calling it in the controller, I decided to override the method in the model. In app/models/account.rb, I added:

def self.all
  super.map do |account|
    account =
      account.attributes.merge(
        :logo_url =>
          account.logo.attached? ?
            polymorphic_url(account.logo) : nil
      )
  end
end

The code above works so well. The problem is that methods find, find_by, where, and other methods stopped working. And if I remove the self.all method from account.rb, it works again.

The following codes:

Account.find_by :id => params[:id]
Account.find params[:id]

raise an error:

undefined method `where' for #<Array:0x00007fd09b68d670>

I believe this happens because self.all returns an array instead of an active record.

How can I override the all method in the model?

Upvotes: 0

Views: 1174

Answers (1)

Derek
Derek

Reputation: 1795

I don't think you want to override Account.all. Instead you want to change the default serialization of your model. Try this:

class Account < ApplicationRecord

  has_one_attached :logo

  private

  def serializable_hash(options)
    super(options).merge("logo_url" => logo_url)
  end

  def logo_url
    logo.attached? ? polymorphic_url(account.logo) : nil
  end
end

The controller ends up calling to_json on your model and the will end up using the #serializable_hash. Since to_son takes options to set a root of the json, it's best to override serializable_hash. Another approach is to create a separate serializer classes per model but this should be suitable for a simple case.

Upvotes: 1

Related Questions