Je construis une application simple et je veux pouvoir stocker des chaînes JSON dans une base de données. J'ai une table Interface avec une colonne json et je veux que mon modèle Rails valide la valeur de la chaîne. Donc, quelque chose comme:
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
Comment je fais ça?
Je suppose que vous pouvez analyser le champ en question et voir s’il génère une erreur. Voici un exemple simplifié (vous pouvez laisser tomber le double coup pour quelque chose un peu plus clair):
require 'json'
class String
def is_json?
begin
!!JSON.parse(self)
rescue
false
end
end
end
Ensuite, vous pouvez utiliser cette extension de chaîne dans un validateur personnalisé.
validate :json_format
protected
def json_format
errors[:base] << "not in json format" unless json.is_json?
end
Le meilleur moyen consiste à ajouter une méthode au module JSON!
_ {Mettez ceci dans votre 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
Vous pourrez maintenant l'utiliser n'importe où ('contrôleur, modèle, vue, ...'), comme ceci:
puts 'it is json' if JSON.is_json?(something)
Actuellement (Rails 3/Rails 4), je préférerais un validateur personnalisé . Voir aussi 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
J'ai rencontré un autre problème en utilisant Rails 4.2.4 et l'adaptateur PostgreSQL (pg) et un validateur personnalisé pour mon champ json.
Dans l'exemple suivant:
class SomeController < BaseController
def update
@record.json_field = params[:json_field]
end
end
si vous transmettez un JSON invalide à
params[:json_field]
il est discrètement ignoré et "nil" est stocké dans
@record.json_field
Si vous utilisez un validateur personnalisé comme
class JsonValidator < ActiveModel::Validator
def validate(record)
begin
JSON.parse(record.json_field)
rescue
errors.add(:json_field, 'invalid json')
end
end
end
vous ne verriez pas de chaîne invalide dans
record.json_field
uniquement la valeur "nil", car Rails saisit la conversion avant de transmettre votre valeur au validateur. Afin de surmonter cela, il suffit d'utiliser
record.json_field_before_type_cast
dans votre validateur.
Grâce à l’analyseur JSON, la validation au format JSON pur est possible. ActiveSupport::JSON.decode(value)
valide la valeur "123"
et 123
sur true. Ce n'est pas correct!
# 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
Si vous n'aimez pas les validateurs de style entreprise ou la mise à jour des clés de la classe String, voici une solution simple:
class Model < ApplicationRecord
validate :json_field_format
def parsed_json_field
JSON.parse(json_field)
end
private
def json_field_format
return if json_field.blank?
begin
parsed_json_field
rescue JSON::ParserError => e
errors[:json_field] << "not is json format"
end
end
end