0

I have a Model Bot and I would like to ensure that there is only one Bot object in my database. I also need to make sure it is persisted and not tampered with.

My original thought was to do this in a migration, one that would follow the :bots table migration. It would include a line that is something like:

Bot.all.size == 0 ? Bot.create! : nil

Maybe this would prevent the AR object from being messed with in future migrations?


BONUS: Would be awesome to be able to have instant and global access to this class object. I was thinking using a singleton module in my Bot class that way I can always reference Bot.instance and have access to that specific object.


USE CASE:

I have 4 types of users in my DB and this bot will be the facilitator to delivery role-specific messages to them through our in-app messaging feature.

The Class Bot will have a has_many association with BotMessage/bot_messages. On the bot_messages table will be an enum field for user_role.

Messages will be created by company admins and stored in these tables because we want them to be viewable at any time by looking at the "conversation" thread between the User and the Bot.

When it comes to only having 1 bot, it's just that. I have no need for an additional Bot object. Additionally, since there is only one object it would be nice to be able to have a way of explicitly targeting that object without having to run a query to find it.

For example, unlike User where there could be 1000 records and in order to find the specific one you would do something like @user = User.find_by_email('foo@bar.com'), doing something like that for the bot would be unnecessary since there is only one record to find. That is what lead me to believe having a singleton object may be worthwhile here, so whenever I need to pull up a message for a specific role, I could run Bot.instance.bot_messages.where(user_role: 1) or something similar

vin_Bin87
  • 318
  • 8
  • 18
  • It might be helpful to understand what it is you're actually trying to do. For instance, have you considered creating a plain old ruby class with constants instead of a DB record? – jvillian Mar 02 '18 at 18:05
  • I updated the post to include a Use Case scenario and explanation of my needs. – vin_Bin87 Mar 02 '18 at 18:12
  • It sounds like you need to save the *internals* of your Bot in a table, one row per setting for example, and have the Bot itself as some kind of singleton wrapper for that. – tadman Mar 02 '18 at 18:27
  • Did you end up somewhere with this? – jvillian Mar 05 '18 at 20:04
  • Hey, went with a completely different approach. No need for the singleton since I just went with a simple 1 model approach of `bot_messages` alone. – vin_Bin87 Mar 08 '18 at 14:26

2 Answers2

1

Based on your Use Case, I see no reason for Bot to be a model.

Let's say you have a role called cool_user and you want to get all the bot_messages for that role, you might do something like:

class Bot

  class << self

    def bot_messages(user_role)
      BotMessage.send(user_role)
    end

  end

end

As a very thoughtful but potentially anonymous super code monkey notes in the comments, you could also do:

class Bot

  def self.bot_messages(user_role)
    BotMessage.send(user_role)
  end

end

Which some folks might find more readable. IMO, it is a bit of an issue of personal preference.

In either case, you should be able to do

Bot.bot_messages(:cool_user)

Since, as stated in the docs,

Scopes based on the allowed values of the enum field will be provided as well.

So, I believe BotMessage, with the properly set enum, should respond to cool_user and return all the bot_messages for that role.

You may need to check the docs to get the syntax exactly right.

I believe this should also satisfy your BONUS requirement.

jvillian
  • 19,953
  • 5
  • 31
  • 44
  • I haven't looked at the question closely, but defining the instance method `bot_messages` on `Bot`'s singleton class is equivalent to defining `bot_messages` as a class method on `Bot`: `def self.bot_messages...`.The latter may be clearer to some readers. – Cary Swoveland Mar 02 '18 at 21:17
  • Quick search yielded [this](https://stackoverflow.com/questions/10964081/class-self-vs-self-method-with-ruby-whats-better) question and answers. It's not clear to me that there is a definitive answer. I consider it a matter of personal preference. However, I may be mistaken. – jvillian Mar 02 '18 at 21:36
  • Remediated the attribution. Mmm. *Syntactic Sugar...* And, an upvote from you... *cough, cough* I'm packing it in for the day. ;-) Seriously, thank you. – jvillian Mar 02 '18 at 22:35
  • The answers to your linked question should be required reading. There really is no such thing as a "class method" in Ruby. All methods are just, well, methods defined on a single object or on all instances of a class. `def self.method` is merely a form of *syntactic sugar*. For that reason I think there is an argument for teaching Ruby newbies to initially use `class << self` for defining methods on a class. Later they could switch to `def self.method` if they want. (Readers, jvillian, in his comment above, evidently anticipated this comment.) – Cary Swoveland Mar 03 '18 at 07:41
0

A proven solution would be to use an STI on User (with a user_type column)

class User < ApplicationRecord
  ...
end

class Bot < User
  has_many :bot_messages, foreign_key: :user_id
end

Is it what you're looking for ?

rubycademy.com
  • 509
  • 5
  • 16
  • Sandi Metz does a great talk that you can see [here](https://www.youtube.com/watch?v=OMPfEXIlTVE). At 25:38, she notes that "Inheritance is for specialization". It seems to me that the approach suggested here is a bit of an abuse (I don't mean to say that in a rude way) of the idea of inheritance. IMO, `Bot` is *not* a specialized form of `User`. `Bot` is an entirely different concept from `User`. So, while this might *work* (I'm not sure if it does or doesn't), it seems to me to be down the wrong path. FWIW. – jvillian Mar 02 '18 at 23:29
  • Of course, inheritance is for specialization.. It's a basic knowledge.. :) My approach is to say that a `Bot` is a kind of `User` (as a facilitator) with specifics (bot messages, name, ACLs, etc..). This is a specialization if we talk from an OOP aspect (Which is the purpose of Ruby). And this is an STI with a 1-to-N relation between `Bot` & `BotMessage` if we talk from a model-oriented aspect (which is the purpose of ActiveRecord). `Bot` can also be an independent model. In my opinion, the fact of making it out the DB break the principle of EDM because `Bot`, as it's descibed, is an entity.. – rubycademy.com Mar 03 '18 at 09:47
  • 1
    Thank you for the reply! I don't know what EDM is (other than Electronic Dance Music - which I like quite a lot). Or ACL, either (other than anterior cruciate ligament, which is best left intact). In your approach, how do you enforce the "only 1 `Bot`" requirement? If `User` has fields that are validated both by ActiveRecord and by the database (let's say, for example, gender, given name, family name, email address), then what values do you assign to the 1 `Bot`? – jvillian Mar 03 '18 at 15:47
  • 1
    Oh, and BTW, I agree that `Bot`, as it is described by the OP, is a database entity. As best as I can see, however, there is nothing in the OP's use case that *requires* that `Bot` be a database entity. So, perhaps the OP has an [XY Problem](http://xyproblem.info/) on their hands. – jvillian Mar 03 '18 at 15:56
  • Thanks for the reply too. I like discussion between 2 passionates developers. Actually. EDM stands for Entity Data Model. You can easily ensure that there is only one `Bot` using a simple `before_create` callback.. I agree with you about the STI and the other field validations. So that's why I told you that `Bot` can be an independent model. – rubycademy.com Mar 03 '18 at 16:40
  • and ACL stands for Access Control List. – rubycademy.com Mar 03 '18 at 16:58