[yast-devel] Webyast - models in webclient and webservice - summary
Hi, because on Friday I finally merge in webclient yast model I create short summary of both models, its features and why you should use it. webservice model: Inspired by ActiveModel. It adds methods for easy validation, serialization, mass loading and callbacks. for example usage see Ntp and time plugins. For documentation see - class BaseModel::Base (I cannot connect to hudson for direct link) why use it? smart to_xml and to_json so you don't need to define own method unless you have hash with problematic keys (see time model for example how to solve it) validations which checks attributes before save same behavior as ActiveRecord and ActiveResource webclient model: Adaptation of ActiveResource to our case of changing site and credentials to target machine. It adds method model_interface which sets interface for model and fix finding single resource. Also helper methods for HttpMock which tests ActiveResource is added. for example usage see Time plugin or network plugin. For documentation see module YastModel::Base example of usage: class Stime < ActiveResource::Base extend YastModel::Base model_interface :'org.opensuse.yast.modules.yapi.time' end why use it? you can add functionality to model, so no more controllers which have more then one page same usage as any ActiveResource model, so new programmer is not confused what we using easier testing (there is still place to improve it), but I found that many plugins doesn't have test or test is really simple and don't cover enough code. for testing see how looks tests in network or time module. Thanks for attention and I welcome any comments, suggestion or bugs ( ..bugs are not so much welcome, but I fix it) Josef -- Josef Reidinger YaST team maintainer of perl-Bootloader, YaST2-Repair, webyast (language,time,basesystem,ntp) -- To unsubscribe, e-mail: yast-devel+unsubscribe@opensuse.org For additional commands, e-mail: yast-devel+help@opensuse.org
On 11.1.2010 10:24, Josef Reidinger wrote: [...]
why use it? smart to_xml and to_json so you don't need to define own method unless you have hash with problematic keys (see time model for example how to solve it) validations which checks attributes before save same behavior as ActiveRecord and ActiveResource
I have added some validations to the repository model in the REST service. It's easy and it's well readable, e.g.: validates_inclusion_of :priority, :in => 0..200 sets valid range for @priority attribute to 0-200. But I'd like to improve error reporting to the client when validation fails in save(). An ActiveModel object has errors() method which returns data about the failed validations. It's even possible to convert them to XML using to_xml. And here is the problem: to_xml returns invalid data which cannot be parsed:
r = Repository.new 'oss', 'oss', true => #<Repository:0x7fbd70deda70 @url="", @enabled=true, @keep_packages=false, @autorefresh=true, @name="oss", @id="oss", @priority=99> r.priority = 'sdfdsfd' => "sdfdsfd" r.valid? => false # this is correct, priority must be in range 0-200 and URL cannot be empty x = r.errors.to_xml => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<errors>\n <error:blank/>\n <error:inclusion/>\n</errors>\n" Hash.from_xml(x) REXML::UndefinedNamespaceException: Undefined prefix error found
^^^ The XML parsing fails here... Another (probably related) problem is that the error list doesn't contain the failed attribute name:
r.errors.full_messages => [:blank, :inclusion]
After changing the validation statements to validates_inclusion_of :priority, :in => 0..200, :message => 'invalid priority' validates_presence_of :url, :message => 'empty URL' I get:
r.errors.full_messages => ["empty URL", "invalid priority"]
And to_xml produces parseable output:
x = r.errors.to_xml => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<errors>\n <error>empty URL</error>\n <error>invalid priority</error>\n</errors>\n" Hash.from_xml(x) => {"errors"=>{"error"=>["empty URL", "invalid priority"]}}
But defining error messages in backend is wrong, they should be translated by the frontend. Moreover I don't want to add a custom error message to every validation, it could work automatically: - the REST service should return ActiveResource::Errors#to_xml result (http://api.rubyonrails.org/classes/ActiveRecord/Errors.html) - the client should - check ActiveResource::Validations#errors for errors (http://api.rubyonrails.org/classes/ActiveResource/Validations.html) - use error_messages_for() ViewHelper function for creating/displaying validation errors (http://api.rubyonrails.org/classes/ActionView/Helpers/ActiveRecordHelper.htm...) Any idea how to make it work? Or do I need to define the same validations in the client model and provide translations there? (The advantage would be no communication between client and server in case of invalid input.) -- Best Regards Ladislav Slezák Yast Developer ------------------------------------------------------------------------ SUSE LINUX, s.r.o. e-mail: lslezak@suse.cz Lihovarská 1060/12 tel: +420 284 028 960 190 00 Prague 9 fax: +420 284 028 951 Czech Republic http://www.suse.cz/ -- To unsubscribe, e-mail: yast-devel+unsubscribe@opensuse.org For additional commands, e-mail: yast-devel+help@opensuse.org
Ladislav Slezak write:
On 11.1.2010 10:24, Josef Reidinger wrote: [...]
why use it? smart to_xml and to_json so you don't need to define own method unless you have hash with problematic keys (see time model for example how to solve it) validations which checks attributes before save same behavior as ActiveRecord and ActiveResource
I have added some validations to the repository model in the REST service. It's easy and it's well readable, e.g.:
validates_inclusion_of :priority, :in => 0..200
sets valid range for @priority attribute to 0-200.
But I'd like to improve error reporting to the client when validation fails in save().
An ActiveModel object has errors() method which returns data about the failed validations. It's even possible to convert them to XML using to_xml.
And here is the problem: to_xml returns invalid data which cannot be parsed:
r = Repository.new 'oss', 'oss', true => #<Repository:0x7fbd70deda70 @url="", @enabled=true, @keep_packages=false, @autorefresh=true, @name="oss", @id="oss", @priority=99> r.priority = 'sdfdsfd' => "sdfdsfd" r.valid? => false # this is correct, priority must be in range 0-200 and URL cannot be empty x = r.errors.to_xml => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<errors>\n <error:blank/>\n <error:inclusion/>\n</errors>\n" Hash.from_xml(x) REXML::UndefinedNamespaceException: Undefined prefix error found
^^^ The XML parsing fails here...
Hmm, it looks like bug.
Another (probably related) problem is that the error list doesn't contain the failed attribute name:
r.errors.full_messages => [:blank, :inclusion]
After changing the validation statements to
validates_inclusion_of :priority, :in => 0..200, :message => 'invalid priority' validates_presence_of :url, :message => 'empty URL'
I get:
r.errors.full_messages => ["empty URL", "invalid priority"]
And to_xml produces parseable output:
x = r.errors.to_xml => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<errors>\n <error>empty URL</error>\n <error>invalid priority</error>\n</errors>\n" Hash.from_xml(x) => {"errors"=>{"error"=>["empty URL", "invalid priority"]}}
But defining error messages in backend is wrong, they should be translated by the frontend.
define error message in backend is good that it is well defined and could be easily parsed in frontend.
Moreover I don't want to add a custom error message to every validation, it could work automatically:
- the REST service should return ActiveResource::Errors#to_xml result (http://api.rubyonrails.org/classes/ActiveRecord/Errors.html) - the client should - check ActiveResource::Validations#errors for errors (http://api.rubyonrails.org/classes/ActiveResource/Validations.html)
This work if you use save! on backend. Then it return 422 with correct xml and it sets errors on frontend. If this doesn't work please report bug.
- use error_messages_for() ViewHelper function for creating/displaying validation errors (http://api.rubyonrails.org/classes/ActionView/Helpers/ActiveRecordHelper.htm...)
I think that there is problem with localization
Any idea how to make it work?
Or do I need to define the same validations in the client model and provide translations there? (The advantage would be no communication between client and server in case of invalid input.)
My idea was, that you should use save! in backend. It reports error on frontend and on frontend you can parse errors and create user friendly message for user. Of course we could have some predefined helper, which takes common validation string and translate it to default error message (not yet done, but it is easy to do). Josef
--
Best Regards
Ladislav Slezák Yast Developer ------------------------------------------------------------------------ SUSE LINUX, s.r.o. e-mail: lslezak@suse.cz Lihovarská 1060/12 tel: +420 284 028 960 190 00 Prague 9 fax: +420 284 028 951 Czech Republic http://www.suse.cz/
-- Josef Reidinger YaST team maintainer of perl-Bootloader, YaST2-Repair, parts of webyast -- To unsubscribe, e-mail: yast-devel+unsubscribe@opensuse.org For additional commands, e-mail: yast-devel+help@opensuse.org
Dne 9.3.2010 08:38, Josef Reidinger napsal(a): [...]
My idea was, that you should use save! in backend. It reports error on frontend and on frontend you can parse errors and create user friendly message for user. Of course we could have some predefined helper, which takes common validation string and translate it to default error message (not yet done, but it is easy to do).
OK, I have implemented it. It is in ApplicationController::generate_error_messages. Example usage: 1) Add validations into your REST backend model, example from the repositories model: # URL cannot be empty validates_presence_of :url # priority must be in range 0-200 validates_inclusion_of :priority, :in => 0..200 2) Use @model.save! in the REST controller (it returns 422 error code when validation fails) 3) In the client controller: if @repo.save # display success flash[:message] = _("Repository '#{@repo.name}' has been updated.") else if @repo.errors.size > 0 # display errors from the response flash[:error] = generate_error_messages @repo, attribute_mapping else # response doesn't contain details, show a generic error message flash[:error] = _("Cannot update repository '#{@repo.name}': Unknown error") end end Where attribute_mapping is a map { :id => _('Alias'), :enabled => _('Enabled'), ... } It simply maps the attribute names to printable/translatable strings (they should be same as the names which were displayed in the form). The page will then contain error "URL can't be blank" if the passed URL is empty. -- Best Regards Ladislav Slezák Yast Developer ------------------------------------------------------------------------ SUSE LINUX, s.r.o. e-mail: lslezak@suse.cz Lihovarská 1060/12 tel: +420 284 028 960 190 00 Prague 9 fax: +420 284 028 951 Czech Republic http://www.suse.cz/ -- To unsubscribe, e-mail: yast-devel+unsubscribe@opensuse.org For additional commands, e-mail: yast-devel+help@opensuse.org
participants (2)
-
Josef Reidinger
-
Ladislav Slezak