merged cont.
[opensuse:yast-rest-service.git] / webservice / lib / base_model / base.rb
1 #--
2 # Webyast Webservice framework
3 #
4 # Copyright (C) 2009, 2010 Novell, Inc. 
5 #   This library is free software; you can redistribute it and/or modify
6 # it only under the terms of version 2.1 of the GNU Lesser General Public
7 # License as published by the Free Software Foundation. 
8 #
9 #   This library is distributed in the hope that it will be useful, but WITHOUT
10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 
12 # details. 
13 #
14 #   You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software 
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 #++
18
19 module BaseModel
20   # == Base
21   # Shared ancestor for models that want to act similar as ActiveResource or ActiveRecord model.
22   #
23   # Inspired by ActiveModel from rails3.0
24   # Supported features
25   # * Validation
26   # * Callbacks
27   # * Mass assignment
28   # * Serialization
29   #
30   # === Validation
31   # It is used validation from ActiveRecord. For details see ActiveRecord::Validations
32   # 
33   # Not all validation is usable in all models. Basic supported ones is:
34   # * validates_presence_of
35   # * validates_format_of
36   # * validates_inclusion_of
37   # * validates_exclusion_of
38   # * validates_range_of
39   # * validates_lenght_of
40   # * validates_numberically_of
41   # * validates_each (general validation)
42   # * validates_numberically_of
43   # see ActiveRecord::Validations::ClassMethods documentation for arguments
44   #
45   # === Callbacks
46   # It is used to add hook to actions from ActiveRecord. For details see ActiveRecord::Callbacks
47   # Supported callbacks (all have before and after variant also):
48   # * around_create
49   # * around_destroy
50   # * around_save
51   # * around_update
52   # * around_validation
53   # * around_validation_on_create
54   # * around_validation_on_update
55   # * general around_filter
56   # see ActiveRecord::Callbacks documentation for arguments
57   # 
58   # === Mass assignment
59   # see BaseModel::MassAssignment
60   #
61   # === Serialization
62   # Framework to support serialization. By default is support xml and json serialization (method to_xml and to_json)
63   # and deserialization (from_xml and from_json).
64   # 
65   # === Example
66   #   class Systemtime < BaseModel::Base
67   #
68   #     # Date settings format is dd/mm/yyyy
69   #     attr_accessor :date
70   #     validates_format_of :date, :with => /^\d{2}\/\d{2}\/\d{4}$/, :allow_nil => true
71   #     # time settings format is hh:mm:ss
72   #     attr_accessor :time
73   #     validates_format_of :time, :with => /^\d{2}:\d{2}:\d{2}$/, :allow_nil => true
74   #     # Current timezone as id
75   #     attr_accessor :timezone
76   #     #check if zone exists
77   #     validates_each :timezone, :allow_nil => true do |model,attr,zone|
78   #       contain = false
79   #       unless model.timezones.nil?
80   #         model.timezones.each do |z|
81   #           contain = true if z["entries"][zone]
82   #         end
83   #         model.errors.add attr, "Unknown timezone" unless contain
84   #       end
85   #     end
86   #     # Utc status possible values is UTCOnly, UTC and localtime see yast2-country doc
87   #     attr_accessor :utcstatus
88   #     validates_inclusion_of :utcstatus, :in => [true,false], :allow_nil => true
89   #     attr_accessor :timezones
90   #     validates_presence_of :timezones
91   #     # do not massload timezones, as it is read-only
92   #     attr_protected :timezones
93   #
94   #     after_save :restart_collectd
95   #     # to_xml and to_json is automatic provided
96   #     # load and new(options) is also automatic provided
97   #   end  
98
99
100   class Base
101
102
103     # requirement for class which should be used in ActiveModel
104     # see http://www.engineyard.com/blog/2009/my-five-favorite-things-about-rails-3/ (paragraph 4)
105     def to_model
106       self
107     end
108
109     # initialize attributes by hash in attr
110     def initialize(attr={})
111       load(attr)
112     end
113
114     # saves result
115     # if fails sets error
116     # see ActiveRecord::Base#save or ActiveResource::Base#save
117     # Do not overwritte it, overwrite instead create or update
118     def save
119       create_or_update
120     end
121
122     # Initial fake implementation which allows ActiveRecord::Validation to alias it, but we use our own version defined below.
123     # see save
124     def save!
125       raise("Internal error: Save! is only to allow alias of activeRecord in validations, but we redefine it so
126           this implementation should not be ever called")
127     end
128
129
130     # identification if create new source or update already existing one
131     # by default return false ( always update )
132     def new_record?
133       false #always update by default
134     end
135
136     # Creates or updates source depending on result of new_record?
137     # Use method save unless you really know what you are doing.
138     def create_or_update
139       (new_record? ? create : update) != false
140     end
141
142     # Creates new source.
143     # By default do nothing.
144     # Overwrite only if you overwrite also new_record? otherwise it is never call
145     # If problem occur returns false and must properly set Error structure see ActiveRecord::Error
146     def create
147       true
148     end
149
150     # Updates source.
151     # By default do nothing.
152     # Overwrite it if model is not read-only
153     # If problem occur returns false and must properly set Error structure see ActiveRecord::Error
154     def update
155       true
156     end
157
158     # destroys source.
159     # By default do nothing.
160     # Overwrite it if model is not read-only
161     # If problem occur returns false and must properly set Error structure see ActiveRecord::Error
162     def destroy
163       true
164     end
165
166     #remove overwritten method_missing from activeRecord (as Base model doesn't know attributes)
167     alias_method :method_missing_orig, :method_missing
168     #required by validations
169     include ActiveRecord::AttributeMethods
170     alias_method :method_missing, :method_missing_orig
171     #remove overwritten respond_to (as Base model doesn't have attributes
172     alias_method :respond_to?, :respond_to_without_attributes?
173
174     #Validations in model
175     include ActiveRecord::Validations
176     
177     #extend validation with site validation
178
179     # validates that in attributes is set valid URI
180     def self.validates_uri(*attr_names)
181       configuration = {}
182       configuration.update attr_names.extract_options!
183
184       validates_each(attr_names,configuration) do |record,attr_name,value|
185         begin
186           URI.parse value
187         rescue URI::InvalidURIError => e
188           Rails.logger.warn "Invalid URI: #{e.inspect}"
189           record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value)
190         end
191       end
192     end
193
194     #Callbacks in model
195     include ActiveRecord::Callbacks
196
197
198     #Mass assignment support
199     include BaseModel::MassAssignment
200     #serialization of models
201     include BaseModel::Serialization
202
203     # This is redefined save! from ActiveRecord, as we want to throw own exceptions
204     # throws InvalidParameters exception when validation failed. Return same value as return save.
205     # if exception is not raised it is correctly reported to webclient as failed validation see ActiveResource#validations
206     def save!
207       unless valid?
208         report = {}
209         errors.each { |attr,msg| report[attr.to_sym] = msg }
210         raise InvalidParameters.new report
211       end
212       save
213     end
214   end
215 end
216 #Hack to properly generate error message without ActiveRecord special methods
217 module ActiveRecord
218   class Error
219     # do not call any record specific methods
220     def generate_message(*args)
221       @message
222     end
223
224     # do not call any record specific methods
225     def generate_full_message(*args)
226       @message
227     end
228
229     # do not call any record specific methods
230     def default_options
231       {}
232     end
233   end
234 end