Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use the ActiveRecord json field type

Tags:

I have a Rails model which has a database column of type "json":

create_table "games", force: true do |t|   t.json     "game_board"   t.datetime "created_at", null: false   t.datetime "updated_at", null: false end 

Great! Now how do I use it? Is it really just as simple as treating the field like a Hash?

self.game_board[:player1] = 1 self.game_board[:cards] = cards.to_hash 

If I were to write that, would everything just work as expected, so in a future API call from a client I could do this?:

self.game_board[:player] # And get back the 1 that I put here before 

What about performance as well? Will the entire game_board be de-serialized every time even if that field is never read? Will the field be re-written (IOW a database write) each time I change part of the "Hash?"

like image 689
Freedom_Ben Avatar asked Jan 23 '15 03:01

Freedom_Ben


People also ask

What is ActiveRecord?

Active Record is the M in MVC - the model - which is the layer of the system responsible for representing business data and logic. Active Record facilitates the creation and use of business objects whose data requires persistent storage to a database.

What is Ruby ActiveRecord?

ActiveRecord is an ORM. It's a layer of Ruby code that runs between your database and your logic code. When you need to make changes to the database, you'll write Ruby code, and then run "migrations" which makes the actual changes to the database.

What is Jsonb?

The JSONB data type stores JSON (JavaScript Object Notation) data as a binary representation of the JSONB value, which eliminates whitespace, duplicate keys, and key ordering. JSONB supports GIN indexes.


2 Answers

Yes, ActiveRecord allows to use Postgres' json-fields simply as Hashes in their models. However, there are a couple of things to consider:

  1. Hash may be NULL on initialization
    In your create_table migration you allow the field :game_board to be NULL. Thus, on first usage the field :game_board of your model-instance will be NULL and you have to initialize the Hash first before using it. (See example below)

  2. In JSON all keys are Strings
    Thus, on save (and reload) all keys will be transformed into Strings if you have used Symbols or Numbers before. Thus, to prevent unwanted behavior it is recommended to use String-keys unless your ORM is configured to symbolize all keys.


Your examples:

self.game_board         ||= {} self.game_board[:player1] = 1 self.game_board[:cards]   = cards.to_hash  # after reload from database (access via String-key): self.game_board['player1']  # And retrieve value 1 (that we put here before) 


@ Performance:

  1. Yes, every time ActiveRecord reads an entry from the database and creates a model-instance, JSON-fields get unserialized into Hashes. But if you think that is a performance-hit to your application than you should either use a text-field and serialize/deserialize the JSON/Hashes when you need to or, even better, don't use ActiveRecord at all. By creating heaps of classes and using magic-methods, ActiveRecord creates so much overhead that you shouldn't worry about the deserialization of JSON. Convenience has its costs.

  2. Yes, every time you change a value in the Hash, the (whole) JSON-field gets replaced and updated with the new serialized version.
    Two notes on this:

    • Even in Postgres itself (not only in ActiveRecord) the possibility of performing updates on certain JSON-elements is missing until now. Compare this Stackoverflow-question
    • In general, JSON-fields should be used with a fixed structure or, at least, in manageable sizes and the field-type is not supposed to be a document-store like eg. in MongoDB. Compare the Postgres documentation
like image 166
Andreas Rayo Kniep Avatar answered Sep 20 '22 04:09

Andreas Rayo Kniep


Just to further clarify - when you're saving the JSON object to an attribute of your model instance make sure to save it as a hash.

Active Record will not complain if you forget to parse a JSON string:

  game = Game.create(game_board: '"key":"value"') 

When you retrieve a string from a json attribute, it won't complain and just return the String.

  game.game_board   => '"key":"value"' 

Thus game.game_board['key'] would lead to an error because you're trying to treat a String like a Hash.

So make sure you use JSON.parse(string) before saving.

  game = Game.create(game_board: JSON.parse('"key":"value"')) 

So now you have the expected behavior

game.game_board['key'] => 'value' 

Probably not useful for this case, but came across this issue when saving a JSON payload from an API I was integrating with. Anyway, hope this helps.

like image 24
Dylan Pierce Avatar answered Sep 19 '22 04:09

Dylan Pierce