0

I am building Grape Entities inside my Rails models as described here:

https://github.com/ruby-grape/grape-entity#entity-organization

Currently I am creating default values automatically, based on the column hash of the model itself.

So I have a static get_entity method that exposes all the model's columns:

class ApplicationRecord < ActiveRecord::Base

  def self.get_entity(target)
    self.columns_hash.each do |name, column|
      target.expose name, documentation: { desc: "Col #{name} of #{self.to_s}" }
    end
  end

end

And then I have here an example Book model using it inside the declared Entity subclass (the comment also shows how I can override the documentation of one of the model's column):

class Book < ActiveRecord::Base

  class Entity < Grape::Entity
    Book::get_entity(self)
    # expose :some_column, documentation: {desc: "this is an override"}
  end

end

The downside with this approach is that I always need to copy and paste the class Entity declaration in each model I want the Entity for.

Can anybody help me out generating the class Entity for all child of ApplicationRecord automagically? Then if I need overrides I will need to have the Entity declaration in the class, otherwise if the default declaration is enough and can leave it as it is.

NOTE:

I cannot add class Entity definition straight inside ApplicationRecord because, Entity class should call get_entity and get_entity depends on column_hash of Books.

SOLUTION:

ended up doing this thanks to brainbag:

def self.inherited(subclass)
  super
  # definition of Entity
  entity = Class.new(Grape::Entity)
  entity.class_eval do
    subclass.get_entity(entity)
  end
  subclass.const_set "Entity", entity

  # definition of EntityList
  entity_list = Class.new(Grape::Entity)
  entity_list.class_eval do
    expose :items, with: subclass::Entity
    expose :meta, with: V1::Entities::Meta
  end
  subclass.const_set "EntityList", entity_list
end

def self.get_entity(entity)
  model = self
  model.columns_hash.each do |name, column|
    entity.expose name, documentation: { type: "#{V1::Base::get_grape_type(column.type)}", desc: "The column #{name} of the #{model.to_s.underscore.humanize.downcase}" }
  end
end

Thanks!

Francesco Meli
  • 2,484
  • 2
  • 21
  • 52

1 Answers1

2

I haven't used Grape so there may be some extra magic here that you need that I don't know about, but this is easy to do in Ruby/Rails. Based on your question "generating the class Entity for all child of ApplicationRecord automagically" you can do this:

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  class Entity < Grape::Entity
    # whatever shared stuff you want
  end
end

Book will then have access to the parent Entity:

> Book::Entity
=> ApplicationRecord::Entity

If you want to add extra code only to the Book::Entity, you can subclass it in Book, like this:

class Book < ApplicationRecord
  class Entity < Entity # subclasses the parent Entity, don't forget this
    # whatever Book-specific stuff you want
  end
end

Then Book::Entity will be its own class.

> Book::Entity
=> Book::Entity

To combine this with your need for get_entity to be called on an inherited class, you can use the #inherited method to automatically call get_entity any time ApplicationRecord is subclassed:

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  def self.get_entity(target)
    target.columns_hash.each do |name, column|
      target.expose name, documentation: { desc: "Col #{name} of #{self.to_s}" }
    end
  end

  def self.inherited(subclass)
    super
    get_entity(subclass)
  end

  class Entity < Grape::Entity
    # whatever shared stuff you want
  end
end
brainbag
  • 1,007
  • 9
  • 23
  • The thing is that get_entity results depends on the the actual Book model in the above example. How could I point to the callee model? If I use self, it will be pointing to ApplicationRecord and not Book. – Francesco Meli Nov 19 '17 at 00:23
  • Instead of `Book::get_entity(self)` you can use `parent::get_entity(self)`. Let me know if you have trouble with that and I'll update the answer with more detail. – brainbag Nov 19 '17 at 00:56
  • yes I definitely have trouble even with parent: undefined method `get_entity' for ApplicationRecord(abstract):Class – Francesco Meli Nov 19 '17 at 01:06
  • Ah yeah, it's a little trickier than I thought. I'll update the answer. – brainbag Nov 19 '17 at 01:13
  • 1
    The flow has to be: 1. Book get initialized 2. Book inherits Entity from ApplicationRecord 3. Entity class has an 'expose' function inherited by Grape::Entity so in case 'self' doesn't point to Entity you need to explicitly write that 4. Need to find a way to call get_entity so that columns_hash is pointing to the Book model (or whatever other model child of 2. Book inherits Entity from ApplicationRecord) – Francesco Meli Nov 19 '17 at 01:14
  • Let me know if that works, I believe that covers the use case that you outlined. – brainbag Nov 19 '17 at 01:17
  • 1
    def self.inherited(subclass) was definitely the answer – Francesco Meli Nov 19 '17 at 01:37