简体   繁体   中英

How to add an element into an array of a Serialize Field using Ruby on Rails

I have a field call query_data defined as text in my MySQL database.

In my model I defined this field as serialize :query_data, JSON .

The JSON format I would like to save and retrieve look like that:

{:items => [
  {:id => 1},
  {:id => 2},
  {:id => 3}
]}

I have a collection (in that case, called items ) that contain an array of objects.

I was wondering, what's the best way to add or delete an Item.

Ex: remove {:id => 2} from my items list and add `{:id => 4} to it

Ruby on Rails has some nice methods to move seamlessly between JSON and Ruby.

thing = {:items => [
  {:id => 1},
  {:id => 2},
  {:id => 3}
]}
thing.to_json # "{\"items\":[{\"id\":1},{\"id\":2},{\"id\":3}]}"

thing.to_json is essentially what's happening in the serializer. If you want them back to Ruby, you can just do:

@items = @thing.query_data
JSON.parse(@items) # "items"=>[{"id"=>1}, {"id"=>2}, {"id"=>3}]}

Now that we can easily move between the two, lets just use Ruby syntax to deal with adding and deleting keys.

thing = {:items => [
  {:id => 1},
  {:id => 2},
  {:id => 3}
]}
thing[:items] = thing[:items].append({:id => 4})   # adding a new item
thing[:items] = thing[:items].select { |item| item[:id] != 2 } # removing an item

First: The second argument to serialize should be the class of object you're storing in the field. You should have serialize :query_data, Hash instead.

Besides that, there aren't really any established best practices for working with serialized data. It really just depends too much on the structure of your data. You might as well ask, "what's the best way to add or delete an item from a hash?"

But since this is a hash you should make sure to keep dirty attributes in mind. If you were to do something like:

items = my_model.query_data[:items]
items.reject! {|item| item[:id] == 2}
items += {id: 4}

then the model wouldn't know that query_data changed and should be updated on save.

my_model.changed?
# => false
my_model.save
# Won't actually save changes to db.

To avoid this, you can:

A) Make sure you only ever set my_model.query_data directly

B) Explicitly call my_model.query_data_will_change! after changing that field so that it will be properly updated on save.

Base on @veridian-dynamics (thanks for your help!) Here what I did.

Model:

class MyModel < ApplicationRecord
  serialize :item_data, JSON
end

Controller:

class ItemController  < ApplicationController
  before_action :authenticate_user!

  def add_item
    begin

      mymodel = MyModel.find_or_create_by(id: param[:model_id])

      if mymodel .item_data.blank?
        item = {:items => []}
      else
        item = mymodel.item_data.deep_symbolize_keys
      end

      bookmark_exist = item[:items].any? {|i| i[:id] == params[:id]}
      if !bookmark_exist
        item[:items] = item[:items ].append({id: params[:id]})   # adding a new item
      end

      mymodel.item_data = item
      mymodel.save

      return render :json => item, :status=> 200    

    rescue Exception => e
      return render :json =>{:errors=>e.message}, :status=> 400
      puts "ERROR: #{e.message}"
    end
  end

  def delete_item
    begin

      mymodel = MyModel.find_by(id: params[:model_id])
      if mymodel.present? && mymodel.item_data.present?
        item = mymodel.item_data.deep_symbolize_keys

        item[:items] = (item[:items].select { |itm| itm[:id] != params[:id] })   # remove an item
        mymodel.item_data =  item    
        mymodel.save

        return render :json => item, :status=> 200    
      end
    rescue Exception => e
      return render :json =>{:errors=>e.message}, :status=> 400
      puts "ERROR: #{e.message}"
    end
  end

end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM