forked from trailblazer/reform
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Validation#validate implemented. this should become #valid?.
- Loading branch information
Showing
6 changed files
with
231 additions
and
134 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,174 +1,142 @@ | ||
# Mechanics for writing to forms in #validate. | ||
module Reform::Form::Validate | ||
module Writer | ||
def from_hash(*) | ||
nested_forms do |attr| | ||
attr.delete(:prepare) | ||
attr.delete(:extend) | ||
|
||
attr.merge!( | ||
:collection => attr[:collection], # TODO: Def#merge! doesn't consider :collection if it's already set in attr YET. | ||
:parse_strategy => :sync, # just use nested objects as they are. | ||
:deserialize => lambda { |object, params, args| | ||
|
||
options = args.user_options.dup | ||
options[:prefix] = options[:prefix].dup # TODO: implement Options#dup. | ||
options[:prefix] << args.binding.name # FIXME: should be #as. | ||
|
||
# puts "======= user_options: #{args.user_options.inspect}" | ||
|
||
object.validate!(params, options) | ||
}, | ||
) | ||
end | ||
|
||
super | ||
end | ||
end | ||
|
||
module Reform | ||
class Form < Validation | ||
module Validate | ||
module Writer | ||
def from_hash(*) | ||
nested_forms do |attr| | ||
attr.delete(:prepare) | ||
attr.delete(:extend) | ||
|
||
attr.merge!( | ||
:collection => attr[:collection], # TODO: Def#merge! doesn't consider :collection if it's already set in attr YET. | ||
:parse_strategy => :sync, # just use nested objects as they are. | ||
:deserialize => lambda { |object, params, args| | ||
|
||
options = args.user_options.dup | ||
options[:prefix] = options[:prefix].dup # TODO: implement Options#dup. | ||
options[:prefix] << args.binding.name # FIXME: should be #as. | ||
|
||
# puts "======= user_options: #{args.user_options.inspect}" | ||
|
||
object.validate!(params, options) | ||
}, | ||
) | ||
end | ||
|
||
module Populator | ||
class PopulateIfEmpty | ||
def initialize(*args) | ||
@form, @fragment, args = args | ||
@index = args.first | ||
@args = args.last | ||
super | ||
end | ||
end | ||
|
||
def call | ||
binding = @args.binding | ||
form = binding.get | ||
|
||
return if binding.array? and form and form[@index] # TODO: this should be handled by the Binding. | ||
return if !binding.array? and form | ||
# only get here when above form is nil. | ||
|
||
if binding[:populate_if_empty].is_a?(Proc) | ||
model = @form.instance_exec(@fragment, @args, &binding[:populate_if_empty]) # call user block. | ||
else | ||
model = binding[:populate_if_empty].new | ||
module Populator | ||
class PopulateIfEmpty | ||
def initialize(*args) | ||
@form, @fragment, args = args | ||
@index = args.first | ||
@args = args.last | ||
end | ||
|
||
form = binding[:form].new(model) # free service: wrap model with Form. this usually happens in #setup. | ||
|
||
if binding.array? | ||
@form.model.send("#{binding.getter}") << model # FIXME: i don't like this, but we have to add the model to the parent object to make associating work. i have to use #<< to stay compatible with AR's has_many API. DISCUSS: what happens when we get out-of-sync here? | ||
@form.send("#{binding.getter}")[@index] = form | ||
else | ||
@form.model.send("#{binding.setter}", model) # FIXME: i don't like this, but we have to add the model to the parent object to make associating work. | ||
@form.send("#{binding.setter}", form) # :setter is currently overwritten by :parse_strategy. | ||
end | ||
end | ||
end | ||
def call | ||
binding = @args.binding | ||
form = binding.get | ||
|
||
return if binding.array? and form and form[@index] # TODO: this should be handled by the Binding. | ||
return if !binding.array? and form | ||
# only get here when above form is nil. | ||
|
||
def from_hash(params, *args) | ||
populated_attrs = [] | ||
if binding[:populate_if_empty].is_a?(Proc) | ||
model = @form.instance_exec(@fragment, @args, &binding[:populate_if_empty]) # call user block. | ||
else | ||
model = binding[:populate_if_empty].new | ||
end | ||
|
||
nested_forms do |attr| | ||
next unless attr[:populate_if_empty] | ||
form = binding[:form].new(model) # free service: wrap model with Form. this usually happens in #setup. | ||
|
||
attr.merge!( | ||
# DISCUSS: it would be cool to move the lambda block to PopulateIfEmpty#call. | ||
:populator => lambda do |fragment, *args| | ||
PopulateIfEmpty.new(self, fragment, args).call | ||
if binding.array? | ||
@form.model.send("#{binding.getter}") << model # FIXME: i don't like this, but we have to add the model to the parent object to make associating work. i have to use #<< to stay compatible with AR's has_many API. DISCUSS: what happens when we get out-of-sync here? | ||
@form.send("#{binding.getter}")[@index] = form | ||
else | ||
@form.model.send("#{binding.setter}", model) # FIXME: i don't like this, but we have to add the model to the parent object to make associating work. | ||
@form.send("#{binding.setter}", form) # :setter is currently overwritten by :parse_strategy. | ||
end | ||
) | ||
end | ||
end | ||
|
||
|
||
nested_forms do |attr| | ||
next unless attr[:populator] | ||
def from_hash(params, *args) | ||
populated_attrs = [] | ||
|
||
attr.merge!( | ||
:parse_strategy => attr[:populator], | ||
:representable => false | ||
) | ||
populated_attrs << attr.name.to_sym | ||
end | ||
nested_forms do |attr| | ||
next unless attr[:populate_if_empty] | ||
|
||
super(params, {:include => populated_attrs}) | ||
end | ||
end | ||
attr.merge!( | ||
# DISCUSS: it would be cool to move the lambda block to PopulateIfEmpty#call. | ||
:populator => lambda do |fragment, *args| | ||
PopulateIfEmpty.new(self, fragment, args).call | ||
end | ||
) | ||
end | ||
|
||
|
||
def errors | ||
@errors ||= Errors.new(self) | ||
end | ||
nested_forms do |attr| | ||
next unless attr[:populator] | ||
|
||
def validate(params) | ||
options = {:errors => errs = Errors.new(self), :prefix => []} | ||
|
||
validate!(params, options) | ||
attr.merge!( | ||
:parse_strategy => attr[:populator], | ||
:representable => false | ||
) | ||
populated_attrs << attr.name.to_sym | ||
end | ||
|
||
self.errors = errs # if the AM valid? API wouldn't use a "global" variable this would be better. | ||
super(params, {:include => populated_attrs}) | ||
end | ||
end | ||
|
||
errors.valid? | ||
end | ||
|
||
def errors | ||
@errors ||= Validation::Errors.new(self) | ||
end | ||
|
||
def validate!(params, options) | ||
# puts "validate! in #{self.class.name}: #{params.inspect}" | ||
populate!(params) | ||
def validate(params) | ||
options = {:errors => errs = Validation::Errors.new(self), :prefix => []} | ||
|
||
# populate nested properties | ||
# update attributes of forms (from_hash) | ||
# run validate(errors) for all forms (no 1-level limitation anymore) | ||
validate!(params, options) | ||
|
||
# here it would be cool to have a validator object containing the validation rules representer-like and then pass it the formed model. | ||
self.errors = errs # if the AM valid? API wouldn't use a "global" variable this would be better. | ||
|
||
errors.valid? | ||
end | ||
|
||
prefix = options[:prefix] | ||
|
||
# sets scalars and recurses #validate. | ||
mapper.new(self).extend(Writer).from_hash(params, options) | ||
def validate!(params, options) | ||
# puts "validate! in #{self.class.name}: #{params.inspect}" | ||
populate!(params) | ||
|
||
res = valid? # this validates on <Fields> using AM::Validations, currently. | ||
# populate nested properties | ||
# update attributes of forms (from_hash) | ||
# run validate(errors) for all forms (no 1-level limitation anymore) | ||
|
||
options[:errors].merge!(self.errors, prefix) | ||
end | ||
# here it would be cool to have a validator object containing the validation rules representer-like and then pass it the formed model. | ||
|
||
private | ||
attr_writer :errors # only used in top form. | ||
|
||
def populate!(params) | ||
mapper.new(self).extend(Populator).from_hash(params) | ||
end | ||
prefix = options[:prefix] | ||
|
||
# sets scalars and recurses #validate. | ||
mapper.new(self).extend(Writer).from_hash(params, options) | ||
|
||
res = valid? # this validates on <Fields> using AM::Validations, currently. | ||
|
||
# The Errors class is planned to replace AM::Errors. It provides proper nested error messages. | ||
class Errors < ActiveModel::Errors | ||
def messages | ||
return super unless Reform.rails3_0? | ||
self | ||
options[:errors].merge!(self.errors, prefix) | ||
end | ||
|
||
# def each | ||
# messages.each_key do |attribute| | ||
# self[attribute].each { |error| yield attribute, Array.wrap(error) } | ||
# end | ||
# end | ||
|
||
def merge!(errors, prefix) | ||
prefixes = prefix.join(".") | ||
|
||
# TODO: merge into AM. | ||
errors.messages.each do |field, msgs| | ||
field = (prefix+[field]).join(".").to_sym # TODO: why is that a symbol in Rails? | ||
private | ||
|
||
msgs = [msgs] if Reform.rails3_0? # DISCUSS: fix in #each? | ||
|
||
msgs.each do |msg| | ||
next if messages[field] and messages[field].include?(msg) | ||
add(field, msg) | ||
end # Forms now contains a plain errors hash. the errors for each item are still available in item.errors. | ||
end | ||
end | ||
|
||
def valid? # TODO: test me in unit test. | ||
blank? | ||
def populate!(params) | ||
mapper.new(self).extend(Populator).from_hash(params) | ||
end | ||
end # Errors | ||
|
||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.