Usando Rails serialize para guardar hash en la base de datos

135

Intento guardar un ID de mapeo hash en varios intentos en mi aplicación de rieles. Mi migración a la base de datos para acomodar esta nueva columna:

class AddMultiWrongToUser < ActiveRecord::Migration
  def self.up
    add_column :users, :multi_wrong, :string
  end

  def self.down
    remove_column :users, :multi_wrong
  end
end

En mi modelo tengo:

class User < ActiveRecord::Base 
 serialize :multi_wrong, Hash
end

Pero cuando uso la consola de rails para probar esto haciendo:

user = User.create()
user.multi_wrong = {"test"=>"123"}
user.save

La salida es falsa. ¿Qué está pasando mal aquí?

cmwright
fuente
44
¿Hay algo en user.errors después de intentar guardar el registro?
Martijn
1
En el futuro, puede usar el método de explosión (¡guardar!) Para generar una excepción y mostrar un mensaje de error.
Leishman
La mejor respuesta ahora utiliza una columna JSON stackoverflow.com/a/21397522/1536309
Blair Anderson

Respuestas:

174

El tipo de columna es incorrecto. Debería usar Texto en lugar de Cadena. Por lo tanto, su migración debería ser:

 def self.up
   add_column :users, :multi_wrong, :text
 end

Entonces Rails lo convertirá correctamente en YAML para usted (y realizará la serialización adecuada). Los campos de cadenas tienen un tamaño limitado y solo contendrán valores especialmente pequeños.

Benjamin Tan Wei Hao
fuente
1
@BenjaminTan cuál es la razón detrás de esto, por qué no puedo almacenar el hash en el tipo de datos 'string'.
Lohith MV
8
Porque en la base de datos, String tiene una longitud fija de 255 (creo). Pero si tuviera que serializar un hash de tamaño comparativo, esto fácilmente excedería la longitud. El mismo caso para las matrices. El texto permite longitudes mucho más grandes.
Benjamin Tan Wei Hao
72

ACTUALIZADO:

Aplicación exacta dependerá de su base de datos, pero ahora tiene PostgreSQL jsony jsonbcolumnas que pueden almacenar de forma nativa sus datos de hash / objeto y que permitan a consulta en la JSON con ActiveRecord !

cambia tu migración y listo.

class Migration0001
  def change
    add_column :users, :location_data, :json, default: {}
  end
end

ORIGINAL:

Para más detalles: rails docs && apidock

Asegúrese de que su columna sea :texty no:string

Migración:

$ rails g migration add_location_data_to_users location_data:text

debería crear:

class Migration0001
  def change
    add_column :users, :location_data, :text
  end
end

Tu clase se vería así:

class User < ActiveRecord::Base
  serialize :location_data
end

Acciones disponibles:

b = User.new
b.location_data = [1,2,{foot: 3, bart: "noodles"}]
b.save

¿Más impresionante?

utilizar postgresql hstore

class AddHstore < ActiveRecord::Migration  
  def up
    enable_extension :hstore
  end

  def down
    disable_extension :hstore
  end
end 

class Migration0001
  def change
    add_column :users, :location_data, :hstore
  end
end

Con hstore puede establecer atributos en el campo serializado

class User < ActiveRecord::Base  
  # setup hstore
  store_accessor :location_data, :city, :state
end
Blair Anderson
fuente
2
¡Realmente impresionante! ¡Gracias!
Alexander Gorg
18

Rails 4 tiene una nueva característica llamada Store , por lo que puede usarla fácilmente para resolver su problema. Puede definir un descriptor de acceso para él y se recomienda que declare la columna de la base de datos utilizada para la tienda serializada como un texto, por lo que hay mucho espacio. El ejemplo original:

class User < ActiveRecord::Base
  store :settings, accessors: [ :color, :homepage ], coder: JSON
end

u = User.new(color: 'black', homepage: '37signals.com')
u.color                          # Accessor stored attribute
u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor

# There is no difference between strings and symbols for accessing custom attributes
u.settings[:country]  # => 'Denmark'
u.settings['country'] # => 'Denmark'
Aboozar Rajabi
fuente