relaxo-model 0.3.0 → 0.3.6

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -7,6 +7,64 @@ Relaxo Model
7
7
 
8
8
  Relaxo Model provides a framework for business logic on top of Relaxo/CouchDB. While it supports some traditional ORM style patterns, it is primary focus is to model business processes and logic.
9
9
 
10
+ Basic Usage
11
+ -----------
12
+
13
+ Here is a simple example of a traditional ORM style model:
14
+
15
+ require 'relaxo'
16
+ require 'relaxo/model'
17
+
18
+ $database = Relaxo.connect("http://localhost:5984/test")
19
+
20
+ $trees = [
21
+ {:name => 'Hinoki', :planted => Date.parse("1948/4/2")},
22
+ {:name => 'Rimu', :planted => Date.parse("1962/8/7")}
23
+ ]
24
+
25
+ class Tree
26
+ include Relaxo::Model
27
+
28
+ property :name
29
+ property :planted, Attribute[Date]
30
+
31
+ # Ensure you've loaded an appropriate design document:
32
+ view :all, 'catalog/tree', Tree
33
+ end
34
+
35
+ $trees.each do |doc|
36
+ tree = Tree.create($database, doc)
37
+
38
+ tree.save
39
+ end
40
+
41
+ Tree.all($database).each do |tree|
42
+ puts "A #{tree.name} was planted on #{tree.planted.to_s}."
43
+
44
+ # Expected output:
45
+ # => A Rimu was planted on 1962-08-07.
46
+ # => A Hinoki was planted on 1948-04-02.
47
+
48
+ tree.delete
49
+ end
50
+
51
+ Here is the design document:
52
+
53
+ - _id: "_design/catalog"
54
+ language: javascript
55
+ views:
56
+ tree:
57
+ map: |
58
+ function(doc) {
59
+ if (doc.type == 'tree') {
60
+ emit(doc._id, doc._rev);
61
+ }
62
+ }
63
+
64
+ If the design document was saved as `catalog.yaml`, you could load it using relaxo into the `test` database as follows:
65
+
66
+ relaxo test catalog.yaml
67
+
10
68
  License
11
69
  -------
12
70
 
data/lib/relaxo/model.rb CHANGED
@@ -18,18 +18,20 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require 'relaxo/model/base'
22
- require 'relaxo/recordset'
23
- require 'relaxo/properties'
21
+ require 'relaxo'
22
+
23
+ require 'relaxo/model/document'
24
+ require 'relaxo/model/properties'
25
+ require 'relaxo/model/recordset'
24
26
 
25
27
  module Relaxo
26
28
  module Model
27
29
  def self.included(child)
28
30
  # Include all available properties
29
- child.send(:include, Relaxo::Properties)
30
- child.send(:include, Relaxo::Model::Base)
31
-
32
- child.send(:extend, Relaxo::Model::ClassMethods)
31
+
32
+ # $stderr.puts "#{self} included -> #{child} include Properties, Document"
33
+ child.send(:include, Relaxo::Model::Properties)
34
+ child.send(:include, Relaxo::Model::Document)
33
35
  end
34
36
  end
35
37
  end
@@ -18,26 +18,30 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
+ require 'relaxo/model/recordset'
22
+
21
23
  module Relaxo
22
24
  module Model
23
- module ClassMethods
24
- DEFAULT_VIEW_OPTIONS = {:include_docs => true}
25
-
25
+ module Base
26
26
  def self.extended(child)
27
+ # $stderr.puts "#{self} extended -> #{child} (setup Base)"
27
28
  child.instance_variable_set(:@properties, {})
28
29
  child.instance_variable_set(:@relationships, {})
29
-
30
+
30
31
  default_type = child.name.split('::').last.gsub(/(.)([A-Z])/,'\1_\2').downcase!
31
32
  child.instance_variable_set(:@type, default_type)
32
33
  end
33
-
34
+
34
35
  def metaclass
35
36
  class << self; self; end
36
37
  end
37
-
38
+
39
+ attr :type
38
40
  attr :properties
39
41
  attr :relationships
40
-
42
+
43
+ DEFAULT_VIEW_OPTIONS = {:include_docs => true}
44
+
41
45
  def view(name, path, *args)
42
46
  options = Hash === args.last ? args.pop : DEFAULT_VIEW_OPTIONS
43
47
  klass = args.pop || options[:class]
@@ -48,20 +52,52 @@ module Relaxo
48
52
  end
49
53
  end
50
54
 
51
- def relationship(name, path, *args)
52
- options = Hash === args.last ? args.pop : {}
53
- klass = args.pop || options[:class]
55
+ DEFAULT_RELATIONSHIP_OPTIONS = {
56
+ :key => lambda {|object, query| query[:key] = object.id},
57
+ :include_docs => true
58
+ }
59
+
60
+ def relationship(name, path, *args, &block)
61
+ options = Hash === args.last ? args.pop : DEFAULT_RELATIONSHIP_OPTIONS
62
+ klass = block || args.pop || options[:class]
54
63
 
55
64
  @relationships[name] = options
56
65
 
66
+ # This reduction returns a single result, so just provide the first row directly:
67
+ reduction = options.delete(:reduction)
68
+
69
+ # Specify a composite key, e.g. :key => :self or :key => [:self]
70
+ if key = options[:key]
71
+ options = options.dup
72
+
73
+ if key == :self
74
+ options[:key] = lambda do |object, query|
75
+ query[:key] = object.id
76
+ end
77
+ elsif Array === key
78
+ index = key.index(:self)
79
+
80
+ options[:key] = lambda do |object, query|
81
+ query[:key] = key.dup
82
+ query[:key][index] = object.id
83
+ end
84
+ end
85
+ end
86
+
57
87
  self.send(:define_method, name) do |query = {}|
58
88
  query = query.merge(options)
59
89
 
60
- unless query.include? :key
61
- query[:key] = self.id
90
+ if options[:key].respond_to? :call
91
+ options[:key].call(self, query)
62
92
  end
63
93
 
64
- Recordset.new(@database, @database.view(path, query), klass)
94
+ recordset = Recordset.new(@database, @database.view(path, query), klass)
95
+
96
+ if reduction == :first
97
+ recordset.first
98
+ else
99
+ recordset
100
+ end
65
101
  end
66
102
  end
67
103
 
@@ -73,11 +109,13 @@ module Relaxo
73
109
  self.send(:define_method, name) do
74
110
  if @changed.include? name
75
111
  return @changed[name]
76
- elsif @document.include? name
112
+ elsif @attributes.include? name
77
113
  if klass
78
- @changed[name] = klass.convert_from_document(@database, @document[name])
114
+ value = @attributes[name]
115
+
116
+ @changed[name] = klass.convert_from_primative(@database, value)
79
117
  else
80
- @changed[name] = @document[name]
118
+ @changed[name] = @attributes[name]
81
119
  end
82
120
  else
83
121
  nil
@@ -87,125 +125,18 @@ module Relaxo
87
125
  self.send(:define_method, "#{name}=") do |value|
88
126
  @changed[name] = value
89
127
  end
90
- end
91
-
92
- def create(database, properties)
93
- instance = self.new(database, {'type' => @type})
94
-
95
- properties.each do |key, value|
96
- instance[key] = value
97
- end
98
-
99
- instance.after_create
100
-
101
- return instance
102
- end
103
-
104
- def fetch(database, id)
105
- instance = self.new(database, database.get(id).to_hash)
106
-
107
- instance.after_fetch
108
-
109
- return instance
110
- end
111
- end
112
-
113
- module Base
114
- def initialize(database, document = {})
115
- # Raw key-value database
116
- @document = document
117
- @database = database
118
- @changed = {}
119
- end
120
-
121
- attr :database
122
-
123
- def id
124
- @document[ID]
125
- end
126
-
127
- def rev
128
- @document[REV]
129
- end
130
-
131
- def clear(key)
132
- @changed.delete(key)
133
- @document.delete(key)
134
- end
135
-
136
- def [] name
137
- name = name.to_s
138
-
139
- if self.class.properties.include? name
140
- self.send(name)
141
- else
142
- raise KeyError.new(name)
143
- end
144
- end
145
-
146
- def []= name, value
147
- name = name.to_s
148
-
149
- if self.class.properties.include? name
150
- self.send("#{name}=", value)
151
- else
152
- raise KeyError.new(name)
153
- end
154
- end
155
-
156
- # Update any calculations:
157
- def before_save
158
- end
159
-
160
- def after_save
161
- end
162
-
163
- def save
164
- before_save
165
-
166
- return if @changed.size == 0 && self.id
167
-
168
- # Flatten changed properties:
169
- self.class.properties.each do |key, klass|
170
- if @changed.include? key
171
- if klass
172
- @document[key] = klass.convert_to_document(@changed.delete(key))
173
- else
174
- @document[key] = @changed.delete(key)
175
- end
128
+
129
+ self.send(:define_method, "#{name}?") do
130
+ value = self.send(name)
131
+
132
+ if value == nil || value == false
133
+ false
134
+ elsif value.respond_to? :empty?
135
+ !value.empty?
136
+ else
137
+ true
176
138
  end
177
139
  end
178
-
179
- # Non-specific properties, serialised by JSON:
180
- @changed.each do |key, value|
181
- @document[key] = value
182
- end
183
-
184
- @changed = {}
185
- @database.save(@document)
186
-
187
- after_save
188
- end
189
-
190
- def before_delete
191
- end
192
-
193
- def after_delete
194
- end
195
-
196
- def delete
197
- before_delete
198
-
199
- @database.delete(@document)
200
-
201
- after_delete
202
- end
203
-
204
- def after_fetch
205
- end
206
-
207
- # Set any default values:
208
- def after_create
209
140
  end
210
141
  end
211
142
  end
@@ -0,0 +1,133 @@
1
+ # Copyright (c) 2012 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'relaxo/model/base'
22
+
23
+ module Relaxo
24
+ module Model
25
+ class Error
26
+ def initialize(key, exception)
27
+ @key = key
28
+ @exception = exception
29
+ end
30
+
31
+ attr :key
32
+ attr :exception
33
+ end
34
+
35
+ module Component
36
+ def self.included(child)
37
+ # $stderr.puts "#{self} included -> #{child} extend Base"
38
+ child.send(:extend, Base)
39
+ end
40
+
41
+ def initialize(database, attributes = {})
42
+ # Raw key-value database
43
+ @attributes = attributes
44
+ @database = database
45
+ @changed = {}
46
+ end
47
+
48
+ attr :attributes
49
+ attr :database
50
+ attr :changed
51
+
52
+ def clear(key)
53
+ @changed.delete(key)
54
+ @attributes.delete(key)
55
+ end
56
+
57
+ def assign(primative_attributes, only = :all)
58
+ enumerator = primative_attributes
59
+
60
+ if only == :all
61
+ enumerator = enumerator.select{|key, value| self.class.properties.include? key.to_s}
62
+ elsif only.respond_to? :include?
63
+ enumerator = enumerator.select{|key, value| only.include? key.to_sym}
64
+ end
65
+
66
+ enumerator.each do |key, value|
67
+ key = key.to_s
68
+
69
+ klass = self.class.properties[key]
70
+
71
+ if klass
72
+ # This might raise a validation error
73
+ value = klass.convert_from_primative(@database, value)
74
+ end
75
+
76
+ self[key] = value
77
+ end
78
+ end
79
+
80
+ def [] name
81
+ name = name.to_s
82
+
83
+ if self.class.properties.include? name
84
+ self.send(name)
85
+ else
86
+ raise KeyError.new(name)
87
+ end
88
+ end
89
+
90
+ def []= name, value
91
+ name = name.to_s
92
+
93
+ if self.class.properties.include? name
94
+ self.send("#{name}=", value)
95
+ else
96
+ raise KeyError.new(name)
97
+ end
98
+ end
99
+
100
+ def flatten!
101
+ errors = []
102
+
103
+ # Flatten changed properties:
104
+ self.class.properties.each do |key, klass|
105
+ if @changed.include? key
106
+ if klass
107
+ begin
108
+ @attributes[key] = klass.convert_to_primative(@changed.delete(key))
109
+ rescue StandardError => error
110
+ errors << Error.new(key, error)
111
+ end
112
+ else
113
+ @attributes[key] = @changed.delete(key)
114
+ end
115
+ end
116
+ end
117
+
118
+ # Non-specific properties, serialised by JSON:
119
+ @changed.each do |key, value|
120
+ @attributes[key] = value
121
+ end
122
+
123
+ @changed = {}
124
+
125
+ errors
126
+ end
127
+
128
+ def to_hash
129
+ @attributes
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,148 @@
1
+ # Copyright (c) 2012 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'relaxo/model/component'
22
+
23
+ module Relaxo
24
+ module Model
25
+ class ValidationErrors < StandardError
26
+ def self.message_for_errors(errors)
27
+ errors.map{|error| "#{error.key} (#{error.exception.message})"}.join(', ')
28
+ end
29
+
30
+ def initialize(errors)
31
+ super "Model validation errors occurred: #{self.class.message_for_errors(errors)}"
32
+
33
+ @errors = errors
34
+ end
35
+
36
+ attr :errors
37
+ end
38
+
39
+ module Document
40
+ TYPE = 'type'
41
+
42
+ def self.included(child)
43
+ # $stderr.puts "#{self} included -> #{child} extend ClassMethods"
44
+ child.send(:include, Component)
45
+ child.send(:extend, ClassMethods)
46
+ end
47
+
48
+ module ClassMethods
49
+ def create(database, properties = nil)
50
+ instance = self.new(database, {TYPE => @type})
51
+
52
+ if properties
53
+ properties.each do |key, value|
54
+ instance[key] = value
55
+ end
56
+ end
57
+
58
+ instance.after_create
59
+
60
+ return instance
61
+ end
62
+
63
+ def fetch(database, id_or_attributes)
64
+ if Hash === id_or_attributes
65
+ instance = self.new(database, id_or_attributes)
66
+ else
67
+ instance = self.new(database, database.get(id_or_attributes).to_hash)
68
+ end
69
+
70
+ instance.after_fetch
71
+
72
+ return instance
73
+ end
74
+ end
75
+
76
+ include Comparable
77
+
78
+ def id
79
+ @attributes[ID]
80
+ end
81
+
82
+ def saved?
83
+ @attributes.key? ID
84
+ end
85
+
86
+ def rev
87
+ @attributes[REV]
88
+ end
89
+
90
+ def type
91
+ @attributes[TYPE]
92
+ end
93
+
94
+ # Update any calculations:
95
+ def before_save
96
+ end
97
+
98
+ def after_save
99
+ end
100
+
101
+ # Reconnect this document with a new database session, typically used for updating an existing model within a session. Changes to the original object may be lost.
102
+ def attach(database)
103
+ clone = self.class.new(database, @attributes.dup)
104
+
105
+ clone.after_fetch
106
+
107
+ return clone
108
+ end
109
+
110
+ def save
111
+ before_save
112
+
113
+ errors = self.flatten!
114
+ raise ValidationErrors.new(errors) if errors.size > 0
115
+
116
+ @database.save(@attributes)
117
+
118
+ after_save
119
+ end
120
+
121
+ def before_delete
122
+ end
123
+
124
+ def after_delete
125
+ end
126
+
127
+ def delete
128
+ before_delete
129
+
130
+ @database.delete(@attributes)
131
+
132
+ after_delete
133
+ end
134
+
135
+ def after_fetch
136
+ end
137
+
138
+ # Set any default values:
139
+ def after_create
140
+ end
141
+
142
+ # Equality is done only on id
143
+ def <=> other
144
+ self.id <=> other.id
145
+ end
146
+ end
147
+ end
148
+ end
@@ -18,4 +18,5 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require 'relaxo/model'
21
+ require 'relaxo/model/properties/attribute'
22
+ require 'relaxo/model/properties/composite'
@@ -0,0 +1,160 @@
1
+ # Copyright (c) 2012 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'date'
22
+
23
+ module Relaxo
24
+ module Model
25
+ module Properties
26
+ class Attribute
27
+ @@attributes = {}
28
+
29
+ def self.for_class(klass, &block)
30
+ @@attributes[klass] = Proc.new(&block)
31
+ end
32
+
33
+ def self.[] (klass, proc = nil)
34
+ self.new(klass, &proc)
35
+ end
36
+
37
+ def initialize(klass, &serialization)
38
+ @klass = klass
39
+
40
+ if block_given?
41
+ self.instance_eval &serialization
42
+ else
43
+ self.instance_eval &@@attributes[klass]
44
+ end
45
+ end
46
+ end
47
+
48
+ class Serialized
49
+ def self.[] (klass, proc = nil)
50
+ self.new(klass, &proc)
51
+ end
52
+
53
+ def initialize(klass, &serialization)
54
+ @klass = klass
55
+
56
+ raise ArgumentError.new("Klass doesn't respond to parse!") unless @klass.respond_to? :parse
57
+ end
58
+
59
+ def convert_to_primative(value)
60
+ value.to_s
61
+ end
62
+
63
+ def convert_from_primative(database, value)
64
+ @klass.parse(value)
65
+ end
66
+ end
67
+
68
+ Required = Attribute
69
+
70
+ class Optional
71
+ def self.[] klass
72
+ self.new(klass)
73
+ end
74
+
75
+ def initialize(klass)
76
+ @klass = klass
77
+ end
78
+
79
+ def convert_to_primative(value)
80
+ if value == nil || value == ''
81
+ nil
82
+ else
83
+ @klass.convert_to_primative(value)
84
+ end
85
+ end
86
+
87
+ def convert_from_primative(database, value)
88
+ if value == nil || value.empty?
89
+ nil
90
+ else
91
+ @klass.convert_from_primative(database, value)
92
+ end
93
+ end
94
+ end
95
+
96
+ class Boolean
97
+ end
98
+
99
+ Attribute.for_class(Boolean) do
100
+ def convert_to_primative(value)
101
+ value ? true : false
102
+ end
103
+
104
+ def convert_from_primative(database, value)
105
+ [true, "on", "true"].include?(value)
106
+ end
107
+ end
108
+
109
+ Attribute.for_class(Integer) do
110
+ def convert_to_primative(value)
111
+ value.to_i
112
+ end
113
+
114
+ def convert_from_primative(database, value)
115
+ value.to_i
116
+ end
117
+ end
118
+
119
+ Attribute.for_class(Float) do
120
+ def convert_to_primative(value)
121
+ value.to_f
122
+ end
123
+
124
+ def convert_from_primative(database, value)
125
+ value.to_f
126
+ end
127
+ end
128
+
129
+ Attribute.for_class(Date) do
130
+ def convert_to_primative(value)
131
+ value.iso8601
132
+ end
133
+
134
+ def convert_from_primative(database, value)
135
+ Date.parse(value)
136
+ end
137
+ end
138
+
139
+ Attribute.for_class(DateTime) do
140
+ def convert_to_primative(value)
141
+ value.iso8601
142
+ end
143
+
144
+ def convert_from_primative(database, value)
145
+ DateTime.parse(value)
146
+ end
147
+ end
148
+
149
+ Attribute.for_class(String) do
150
+ def convert_to_primative(value)
151
+ value.to_s
152
+ end
153
+
154
+ def convert_from_primative(database, value)
155
+ value.to_s
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -18,17 +18,19 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require 'money'
21
+ require 'bigdecimal'
22
22
 
23
23
  module Relaxo
24
- module Properties
25
- Attribute.for_class(Money) do
26
- def convert_to_document(value)
27
- [value.cents, value.currency.to_s]
28
- end
24
+ module Model
25
+ module Properties
26
+ Attribute.for_class(BigDecimal) do
27
+ def convert_to_primative(value)
28
+ value.to_s('F')
29
+ end
29
30
 
30
- def convert_from_document(database, value)
31
- Money.new(value[0], value[1])
31
+ def convert_from_primative(database, value)
32
+ value.to_d
33
+ end
32
34
  end
33
35
  end
34
36
  end
@@ -0,0 +1,144 @@
1
+ # Copyright (c) 2012 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Relaxo
22
+ module Model
23
+ module Properties
24
+
25
+ class Polymorphic
26
+ def self.[] *klasses
27
+ self.new(klasses)
28
+ end
29
+
30
+ def initialize(klasses)
31
+ @klasses = klasses
32
+ @lookup = nil
33
+ end
34
+
35
+ def lookup(type)
36
+ unless @lookup
37
+ @lookup = {}
38
+
39
+ @klasses.each do |klass|
40
+ @lookup[klass.type] = klass
41
+ end
42
+ end
43
+
44
+ @lookup[type]
45
+ end
46
+
47
+ def convert_to_primative(object)
48
+ unless object.saved?
49
+ object.save
50
+ end
51
+
52
+ [object.type, object.id]
53
+ end
54
+
55
+ def convert_from_primative(database, reference)
56
+ # Legacy support for old polymorphic types - to remove.
57
+ if Array === reference
58
+ type, id = reference
59
+ else
60
+ id = reference
61
+ end
62
+
63
+ attributes = database.get(id).to_hash
64
+
65
+ klass = lookup(attributes['type'])
66
+
67
+ klass.fetch(database, attributes)
68
+ end
69
+ end
70
+
71
+ class BelongsTo
72
+ def self.[] *klasses
73
+ if klasses.size == 1
74
+ self.new(klasses[0])
75
+ else
76
+ Polymorphic.new(*klasses)
77
+ end
78
+ end
79
+
80
+ def initialize(klass)
81
+ @klass = klass
82
+ end
83
+
84
+ def convert_to_primative(object)
85
+ unless object.saved?
86
+ object.save
87
+ end
88
+
89
+ object.id
90
+ end
91
+
92
+ def convert_from_primative(database, id)
93
+ @klass.fetch(database, id)
94
+ end
95
+ end
96
+
97
+ class HasOne < BelongsTo
98
+ end
99
+
100
+ class HasMany < HasOne
101
+ def convert_to_primative(value)
102
+ value.each do |document|
103
+ document.save unless value.id
104
+ end
105
+
106
+ value.collect{|document| document.id}
107
+ end
108
+
109
+ def convert_from_primative(database, value)
110
+ value.collect{|id| @klass.fetch(database, id)}
111
+ end
112
+ end
113
+
114
+ # Returns the raw value, typically used for reductions:
115
+ module ValueOf
116
+ def self.new(database, value)
117
+ value
118
+ end
119
+ end
120
+
121
+ class ArrayOf
122
+ def self.[] klass
123
+ self.new(klass)
124
+ end
125
+
126
+ def initialize(klass)
127
+ @klass = Attribute.new(klass)
128
+ end
129
+
130
+ def convert_to_primative(value)
131
+ value.collect do |item|
132
+ @klass.convert_to_primative(item)
133
+ end
134
+ end
135
+
136
+ def convert_from_primative(database, value)
137
+ value.collect do |item|
138
+ @klass.convert_from_primative(database, item)
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
@@ -1,4 +1,3 @@
1
-
2
1
  # Copyright (c) 2012 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
3
2
  #
4
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -19,42 +18,23 @@
19
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
19
  # THE SOFTWARE.
21
20
 
21
+ require 'latinum/resource'
22
+
22
23
  module Relaxo
23
- class Recordset
24
- include Enumerable
25
-
26
- def initialize(database, view, klass = nil)
27
- @database = database
28
- @view = view
29
-
30
- @klass = klass
31
- end
32
-
33
- attr :klass
34
- attr :database
35
-
36
- def count
37
- @view["total_rows"]
38
- end
39
-
40
- def offset
41
- @view["offset"]
42
- end
43
-
44
- def rows
45
- @view["rows"]
46
- end
47
-
48
- def each(klass = nil, &block)
49
- klass ||= @klass
50
-
51
- if klass
52
- rows.each do |row|
53
- # If user specified :include_docs => true, row['doc'] contains the primary value:
54
- yield klass.new(@database, row['doc'] || row['value'])
24
+ module Model
25
+ module Properties
26
+ Attribute.for_class(Latinum::Resource) do
27
+ def convert_to_primative(value)
28
+ [value.amount.to_s('F'), value.name]
29
+ end
30
+
31
+ def convert_from_primative(database, value)
32
+ if Array === value
33
+ @klass.new(value[0], value[1])
34
+ else
35
+ @klass.parse(value)
36
+ end
55
37
  end
56
- else
57
- rows.each &block
58
38
  end
59
39
  end
60
40
  end
@@ -0,0 +1,69 @@
1
+
2
+ # Copyright (c) 2012 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+
22
+ module Relaxo
23
+ module Model
24
+ class Recordset
25
+ include Enumerable
26
+
27
+ def initialize(database, view, klass = nil)
28
+ @database = database
29
+ @view = view
30
+
31
+ @klass = klass
32
+ end
33
+
34
+ attr :klass
35
+ attr :database
36
+
37
+ def count
38
+ @view["total_rows"]
39
+ end
40
+
41
+ def offset
42
+ @view["offset"]
43
+ end
44
+
45
+ def rows
46
+ @view["rows"]
47
+ end
48
+
49
+ def each(klass = nil, &block)
50
+ klass ||= @klass
51
+
52
+ if klass
53
+ if klass.respond_to? :call
54
+ rows.each do |row|
55
+ yield klass.call(@database, row)
56
+ end
57
+ else
58
+ rows.each do |row|
59
+ # If user specified :include_docs => true, row['doc'] contains the primary value:
60
+ yield klass.new(@database, row['doc'] || row['value'])
61
+ end
62
+ end
63
+ else
64
+ rows.each &block
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -23,7 +23,7 @@ module Relaxo
23
23
  module VERSION
24
24
  MAJOR = 0
25
25
  MINOR = 3
26
- TINY = 0
26
+ TINY = 6
27
27
 
28
28
  STRING = [MAJOR, MINOR, TINY].join('.')
29
29
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: relaxo-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-11 00:00:00.000000000 Z
12
+ date: 2012-07-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: relaxo
@@ -50,12 +50,16 @@ extensions: []
50
50
  extra_rdoc_files: []
51
51
  files:
52
52
  - lib/relaxo/model/base.rb
53
+ - lib/relaxo/model/component.rb
54
+ - lib/relaxo/model/document.rb
55
+ - lib/relaxo/model/properties/attribute.rb
56
+ - lib/relaxo/model/properties/bigdecimal.rb
57
+ - lib/relaxo/model/properties/composite.rb
58
+ - lib/relaxo/model/properties/latinum.rb
59
+ - lib/relaxo/model/properties.rb
60
+ - lib/relaxo/model/recordset.rb
53
61
  - lib/relaxo/model/version.rb
54
62
  - lib/relaxo/model.rb
55
- - lib/relaxo/properties/money.rb
56
- - lib/relaxo/properties.rb
57
- - lib/relaxo/recordset.rb
58
- - lib/relaxo-model.rb
59
63
  - README.md
60
64
  homepage: http://www.oriontransfer.co.nz/gems/relaxo
61
65
  licenses: []
@@ -82,3 +86,4 @@ signing_key:
82
86
  specification_version: 3
83
87
  summary: Relaxo Model is a high level business logic framework for CouchDB/Relaxo.
84
88
  test_files: []
89
+ has_rdoc:
@@ -1,129 +0,0 @@
1
- # Copyright (c) 2012 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining a copy
4
- # of this software and associated documentation files (the "Software"), to deal
5
- # in the Software without restriction, including without limitation the rights
6
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- # copies of the Software, and to permit persons to whom the Software is
8
- # furnished to do so, subject to the following conditions:
9
- #
10
- # The above copyright notice and this permission notice shall be included in
11
- # all copies or substantial portions of the Software.
12
- #
13
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- # THE SOFTWARE.
20
-
21
- require 'date'
22
-
23
- module Relaxo
24
- module Properties
25
- class Attribute
26
- @@attributes = {}
27
-
28
- def self.for_class(klass, &block)
29
- @@attributes[klass] = Proc.new(&block)
30
- end
31
-
32
- def self.[] klass
33
- self.new(klass)
34
- end
35
-
36
- def initialize(klass)
37
- @klass = klass
38
-
39
- self.instance_eval &@@attributes[klass]
40
- end
41
- end
42
-
43
- Attribute.for_class(Integer) do
44
- def convert_to_document(value)
45
- value.to_i
46
- end
47
-
48
- def convert_from_document(database, value)
49
- value.to_i
50
- end
51
- end
52
-
53
- Attribute.for_class(Float) do
54
- def convert_to_document(value)
55
- value.to_f
56
- end
57
-
58
- def convert_from_document(database, value)
59
- value.to_f
60
- end
61
- end
62
-
63
- Attribute.for_class(Date) do
64
- def convert_to_document(value)
65
- value.iso8601
66
- end
67
-
68
- def convert_from_document(database, string)
69
- Date.parse(string)
70
- end
71
- end
72
-
73
- Attribute.for_class(DateTime) do
74
- def convert_to_document(value)
75
- value.iso8601
76
- end
77
-
78
- def convert_from_document(database, string)
79
- DateTime.parse(string)
80
- end
81
- end
82
-
83
- Attribute.for_class(String) do
84
- def convert_to_document(value)
85
- value.to_s
86
- end
87
-
88
- def convert_from_document(database, string)
89
- string.to_s
90
- end
91
- end
92
-
93
- class BelongsTo
94
- def self.[] klass
95
- self.new(klass)
96
- end
97
-
98
- def initialize(klass)
99
- @klass = klass
100
- end
101
-
102
- def convert_to_document(value)
103
- unless value.id
104
- value.save
105
- end
106
-
107
- value.id
108
- end
109
-
110
- def convert_from_document(database, string)
111
- @klass.fetch(database, string)
112
- end
113
- end
114
-
115
- class Lookup
116
- def self.[] klass
117
- self.class.new(klass)
118
- end
119
-
120
- def initialize(klass)
121
- @klass = klass
122
- end
123
-
124
- def new(database, id)
125
- @klass.fetch(database, id)
126
- end
127
- end
128
- end
129
- end