2011-07-06 860 views
25

Estoy construyendo una aplicación simple y quiero poder almacenar cadenas json en una base de datos. Tengo una interfaz de tabla con una columna json, y quiero que mi modelo de rieles valide el valor de la cadena. Así que algo así como:Cómo validar si una cadena está json en un modelo de Rails

class Interface < ActiveRecord::Base 
    attr_accessible :name, :json 

    validates :name, :presence => true, 
        :length => { :minimum => 3, 
            :maximum => 40 }, 
        :uniqueness => true 

    validates :json, :presence => true, 
        :type => json #SOMETHING LIKE THIS 
        :contains => json #OR THIS  
end 

¿Cómo puedo hacer eso?

Respuesta

33

Supongo que podría analizar el campo en cuestión y ver si arroja un error. He aquí un ejemplo simplificado (es posible que desee dejar caer el doble de la explosión para algo un poco más claro):

require 'json' 

class String 
    def is_json? 
    begin 
     !!JSON.parse(self) 
    rescue 
     false 
    end 
    end 
end 

Posteriormente, se podría utilizar esta extensión de cadena en un validador personalizado.

validate :json_format 

protected 

    def json_format 
    errors[:base] << "not in json format" unless json.is_json? 
    end 
+0

¡Y todo se volvió verde, gracias! Gran tiempo de respuesta también :) He estado tratando de hacer algo como esto, pero mis habilidades de Rails están un poco polvorientas. –

+0

Hmm, esto es extraño. Tengo pruebas rspec, una de las cuales requiere una cadena json válida como el valor de json, y todas estas son verdes. Pero mis pruebas de pepino fallan, y también falla la prueba de la vista en el servidor de rieles, indicando is_json? es un método indefinido He colocado la clase de validación que sugirió debajo de mi modelo, ¿está mal? –

+4

Supongo que hay opiniones diferentes, pero parece que la mayoría de la gente está colocando sus extensiones de clase principal en 'config/initializers /' como '* .rb' (naturalmente) donde se cargan automáticamente después de cargar Rails. Otra opción es el directorio 'lib /', pero luego tendrá que decirle a Rails que cargue su archivo. – polarblau

14

¡La mejor manera es agregar un método al módulo JSON!

Pon esto en tu config/application.rb:

module JSON 
    def self.is_json?(foo) 
    begin 
     return false unless foo.is_a?(String) 
     JSON.parse(foo).all? 
    rescue JSON::ParserError 
     false 
    end 
    end 
end 

Ahora usted estará habilitado para usar en cualquier lugar ('controlador, modelo, vista, ...'), al igual que este :

puts 'it is json' if JSON.is_json?(something) 
14

Actualmente (Rails 3/4 carriles) yo preferiría un validador encargo. También vea https://gist.github.com/joost/7ee5fbcc40e377369351.

# Put this code in lib/validators/json_validator.rb 
# Usage in your model: 
# validates :json_attribute, presence: true, json: true 
# 
# To have a detailed error use something like: 
# validates :json_attribute, presence: true, json: {message: :some_i18n_key} 
# In your yaml use: 
# some_i18n_key: "detailed exception message: %{exception_message}" 
class JsonValidator < ActiveModel::EachValidator 

    def initialize(options) 
    options.reverse_merge!(:message => :invalid) 
    super(options) 
    end 

    def validate_each(record, attribute, value) 
    value = value.strip if value.is_a?(String) 
    ActiveSupport::JSON.decode(value) 
    rescue MultiJson::LoadError, TypeError => exception 
    record.errors.add(attribute, options[:message], exception_message: exception.message) 
    end 

end 
+0

Realmente me gusta esta idea mejor que la respuesta aceptada. ¿Qué tal si usamos MultiJson.load (value) en lugar de ActiveSupport :: JSON.decode (value)? Entonces también se encontraría con el bloque de rescate ... – mfittko

0

Utilizando el analizador JSON, es posible la validación del formato JSON puro. ActiveSupport::JSON.decode(value) valida el valor "123" y 123 en verdadero. ¡Eso no es correcto!

# Usage in your model: 
# validates :json_attribute, presence: true, json: true 
# 
# To have a detailed error use something like: 
# validates :json_attribute, presence: true, json: {message: :some_i18n_key} 
# In your yaml use: 
# some_i18n_key: "detailed exception message: %{exception_message}" 
class JsonValidator < ActiveModel::EachValidator 

    def initialize(options) 
    options.reverse_merge!(message: :invalid) 
    super(options) 
    end 


    def validate_each(record, attribute, value) 
    if value.is_a?(Hash) || value.is_a?(Array) 
     value = value.to_json 
    elsif value.is_a?(String) 
     value = value.strip 
    end 
    JSON.parse(value) 
    rescue JSON::ParserError, TypeError => exception 
    record.errors.add(attribute, options[:message], exception_message: exception.message) 
    end 

end 
3

Me enfrenté a otro problema al utilizar Rails 4.2.4 y el adaptador PostgreSQL (pg) y el validador personalizado para mi campo json.

En el siguiente ejemplo:

class SomeController < BaseController 
    def update 
    @record.json_field = params[:json_field] 
    end 
end 

si pasa JSON válido para

params[:json_field] 

se ignora en silencio y "nulo" se almacena en

@record.json_field 

Si utiliza validador personalizado como

class JsonValidator < ActiveModel::Validator 
    def validate(record) 
    begin 
     JSON.parse(record.json_field) 
    rescue 
     errors.add(:json_field, 'invalid json') 
    end 
    end 
end 

no verías cadena no válida en

record.json_field 
única

"nulo" valor, porque los carriles que hace la conversión de tipos antes de pasar a su valor validador. Para superar esto, simplemente use

record.json_field_before_type_cast 

en su validador.

Cuestiones relacionadas