Class: Pod4::Model
- Inherits:
-
BasicModel
- Object
- BasicModel
- Pod4::Model
- Defined in:
- lib/pod4/model.rb
Overview
The parent of all CRUDL models.
Models & Interfaces
Note that we distinguish between ‘models’ and ‘interfaces’:
The model represents the data to your application, in the format that makes most sense to your application: that might be the same format that it is stored in on the database, or it might not. The model doesn’t care about where the data comes from. Models are all subclasses of Pod4::Model.
An interface encapsulates the connection to whatever is providing the data. It might be a wrapper for calls to the Sequel ORM, for example. Or it could be a making a series of calls to a set of Nebulous verbs. It only cares about dealing with the data source, and it is only called by the model.
An interface is a seperate class, which is defined for each model. There are parent classes for most of the data sources you will need, but failing that, you can always create one from the ultimate parent, Pod4::Interface.
Simple Example
The most basic example model (and interface):
class ExampleModel < Pod4::Model
class ExampleInterface < Pod4::SequelInterface
set_table :example
set_id_fld :id
end
set_interface ExampleInterface.new($db)
attr_columns :one, :two, :three
end
In this example we have a model that relies on the Sequel ORM to talk to a table ‘example’. The table has a primary key field ‘id’ and columns which correspond to our three attributes one, two and three. There is no validation or error control.
Here is an example of this model in use:
# find record 14; raise error otherwise. Update and save.
x = ExampleModel.new(14).read.or_die
x.two = "new value"
x.update
# create a new record from the params hash -- unless validation fails.
y = ExampleModel.new
y.set(params)
y.create unless y.model_status == :error
Overriding Column Representation
If you want to represent information differently on the model than it is stored on the data source, there are four methods you potentially need to know about and override:
-
set – used by you to set model column values
-
to_ot – used by you to get model column values
-
map_to_model – used by the model to set column values from the interface
-
map_to_interface – used by the model to set interface values
See the methods themselves for more detail.
Constant Summary
Constants inherited from BasicModel
Instance Attribute Summary
Attributes inherited from BasicModel
Class Method Summary collapse
-
.attr_columns(*cols) ⇒ Object
You should call this in your model definition to define model ‘columns’ – it gives you exactly the functionality of ‘attr_accessor` but also registers the attribute as one that `to_ot`, `map_to_model` and `map_to_interface` will try to help you with.
-
.columns ⇒ Object
Returns the list of columns from attr_columns.
- .fail_no_id ⇒ Object
- .fail_no_id_fld ⇒ Object
-
.list(params = nil) ⇒ Object
Call this to return an array of record information.
- .test_for_invalid_status(action, status) ⇒ Object
- .test_for_octo(param) ⇒ Object
Instance Method Summary collapse
-
#columns ⇒ Object
Syntactic sugar; pretty much the same as self.class.columns, which returns the ‘attr_columns` array.
-
#create ⇒ Object
Call this to write a new record to the data source.
-
#delete ⇒ Object
Call this to delete the record on the data source.
-
#map_to_interface ⇒ Object
Used by the model to get an OT to pass to the interface on #create and #update.
-
#map_to_model(ot) ⇒ Object
Used by the interface to set the column values on the model.
-
#read ⇒ Object
Call this to fetch the data for this instance from the data source.
-
#set(ot) ⇒ Object
Set instance values on the model from a Hash or Octothorpe.
-
#to_ot ⇒ Object
Return an Octothorpe of all the attr_columns attributes.
-
#update ⇒ Object
Call this to update the data source with the current attribute values.
-
#validate(vmode = nil) ⇒ Object
Call this to validate the model.
Methods inherited from BasicModel
#alerts, #clear_alerts, #initialize, #interface, interface, #raise_exceptions, set_interface
Methods included from Metaxing
#define_class_method, #metaclass
Constructor Details
This class inherits a constructor from Pod4::BasicModel
Class Method Details
.attr_columns(*cols) ⇒ Object
You should call this in your model definition to define model ‘columns’ – it gives you exactly the functionality of ‘attr_accessor` but also registers the attribute as one that `to_ot`, `map_to_model` and `map_to_interface` will try to help you with.
87 88 89 90 91 92 |
# File 'lib/pod4/model.rb', line 87 def attr_columns(*cols) c = columns.dup c += cols define_class_method(:columns) {c} attr_accessor *cols end |
.columns ⇒ Object
Returns the list of columns from attr_columns
97 98 99 |
# File 'lib/pod4/model.rb', line 97 def columns [] end |
.fail_no_id ⇒ Object
147 148 149 |
# File 'lib/pod4/model.rb', line 147 def fail_no_id raise Pod4Error, "ID field missing from record", caller end |
.fail_no_id_fld ⇒ Object
143 144 145 |
# File 'lib/pod4/model.rb', line 143 def fail_no_id_fld raise Pod4Error, "No ID field defined in interface", caller end |
.list(params = nil) ⇒ Object
Call this to return an array of record information.
What you actually get depends on the interface, but it must include a recognisable record ID in each array element.
For the purposes of Model we assume that we can make an instance out of each array element, and we return an array of instances of the model. Override this method if that is not true for your Interface.
Note that list should ALWAYS return an array, and array elements should always respond to :id – otherwise we raise a Pod4Error.
Note also that while list returns an array of model objects, ‘read` has not been run against each object. The data is there, but @model_status == :unknown, and validation has not been run. This is partly for the sake of efficiency, partly to help avoid recursive loops in validation.
119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/pod4/model.rb', line 119 def list(params=nil) fail_no_id_fld unless interface.id_fld interface.list(params).map do |ot| key = ot[interface.id_fld]; fail_no_id unless key rec = self.new(key) rec.map_to_model(ot) # seperately, in case model forgot to return self rec end end |
.test_for_invalid_status(action, status) ⇒ Object
137 138 139 140 141 |
# File 'lib/pod4/model.rb', line 137 def test_for_invalid_status(action, status) raise( Pod4Error, "Invalid model status for an action of #{action}", caller ) \ if [:unknown, :deleted].include? status end |
.test_for_octo(param) ⇒ Object
131 132 133 134 135 |
# File 'lib/pod4/model.rb', line 131 def test_for_octo(param) raise( ArgumentError, 'Parameter must be a Hash or Octothorpe', caller ) \ unless param.kind_of?(Hash) || param.kind_of?(Octothorpe) end |
Instance Method Details
#columns ⇒ Object
Syntactic sugar; pretty much the same as self.class.columns, which returns the ‘attr_columns` array.
157 |
# File 'lib/pod4/model.rb', line 157 def columns; self.class.columns.dup; end |
#create ⇒ Object
Call this to write a new record to the data source.
Note: create needs to set @model_id. But interface.create should return it, so that’s okay.
164 165 166 167 168 169 170 171 172 173 |
# File 'lib/pod4/model.rb', line 164 def create run_validation(:create) @model_id = interface.create(map_to_interface) unless @model_status == :error @model_status = :okay if @model_status == :unknown self rescue Pod4::WeakError add_alert(:error, $!) self end |
#delete ⇒ Object
Call this to delete the record on the data source.
Note: does not delete the instance…
219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/pod4/model.rb', line 219 def delete Model.test_for_invalid_status(:delete, @model_status) clear_alerts; run_validation(:delete) unless @model_status == :error interface.delete(@model_id) @model_status = :deleted end self rescue Pod4::WeakError add_alert(:error, $!) self end |
#map_to_interface ⇒ Object
Used by the model to get an OT to pass to the interface on #create and #update.
Override it if you want the model to represent data differently than the data source – in which case you also need to override ‘map_to_model`.
Bear in mind that any attribute could be nil, and likely will be when ‘map_to_interface` is called from the create method.
NB: we always pass the ID field to the Interface, regardless of whether the field autoincrements or whether it’s been named in ‘attr_columns`.
See also: ‘map_to_model’
305 306 307 |
# File 'lib/pod4/model.rb', line 305 def map_to_interface Octothorpe.new(to_h) end |
#map_to_model(ot) ⇒ Object
Used by the interface to set the column values on the model.
By default this does exactly the same as ‘set`. Override it if you want the model to represent data differently than the data source does – but then you will have to override `map_to_interface`, too, to convert the data back.
See also: ‘map_to_interface’
286 287 288 289 |
# File 'lib/pod4/model.rb', line 286 def map_to_model(ot) merge(ot) self end |
#read ⇒ Object
Call this to fetch the data for this instance from the data source
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
# File 'lib/pod4/model.rb', line 178 def read r = interface.read(@model_id) if r.empty? add_alert(:error, "Record ID '#@model_id' not found on the data source") else map_to_model(r) run_validation(:read) @model_status = :okay if @model_status == :unknown end self rescue Pod4::WeakError add_alert(:error, $!) self end |
#set(ot) ⇒ Object
Set instance values on the model from a Hash or Octothorpe.
This is what your code calls when it wants to update the model. Override it if you need it to set anything not in attr_columns, or to control data types, etc.
You might want to put validation here, too, if what you are validating is something that isn’t actually stored on the model. You can call add_alert from here just fine.
See also: ‘to_ot`, `map_to_model`, `map_to_interface`
260 261 262 263 |
# File 'lib/pod4/model.rb', line 260 def set(ot) merge(ot) self end |
#to_ot ⇒ Object
Return an Octothorpe of all the attr_columns attributes. This includes the ID field, whether or not it has been named in attr_columns.
Override if you want to return any extra data. (You will need to create a new Octothorpe.)
See also: ‘set`
273 274 275 |
# File 'lib/pod4/model.rb', line 273 def to_ot Octothorpe.new(to_h) end |
#update ⇒ Object
Call this to update the data source with the current attribute values
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/pod4/model.rb', line 198 def update Model.test_for_invalid_status(:update, @model_status) clear_alerts; run_validation(:update) interface.update(@model_id, map_to_interface) unless @model_status == :error unless interface.id_ai @model_id = instance_variable_get( "@#{interface.id_fld}".to_sym ) end self rescue Pod4::WeakError add_alert(:error, $!) self end |
#validate(vmode = nil) ⇒ Object
Call this to validate the model.
Override this to add validation - calling ‘add_alert` for each problem.
Note that you can only validate what is actually stored on the model. If you want to check the data being passed to the model in ‘set`, you need to override that routine.
You may optionally catch the vmode parameter, which is one of :create, :read, :update, :delete, to have different validation under these circumstances; or you may safely ignore it and override ‘create`, `read`, `update` or `delete` as you wish.
245 246 247 |
# File 'lib/pod4/model.rb', line 245 def validate(vmode=nil) # Holding pattern. All models should use super, in principal end |