relaxo-model 0.11.0 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e2ad57e6711f00c73b9c202ead6b83a74487d3c6
4
- data.tar.gz: 4415ddf243915e589262fabd95710bd87f4ca37f
3
+ metadata.gz: b0c3186186902c2608d53e8f866364aba7dbc958
4
+ data.tar.gz: e35738b1d5643f40b8454bcb1d787f199faf0686
5
5
  SHA512:
6
- metadata.gz: 2002c6a843e9faa0d161ec038746e14e7e62b1510913ce2a58a6d526a43d89cccca8df7ce0f452ce1d6783d2b841d07b0a94102e954a5ea8a49d5a457e41bb37
7
- data.tar.gz: 70345816de27d90a95239d35254de896938fb36f2200bb98b4d6dadb1d7ef675b15488ef78286276047e1d4ccd2203655a615a7a8505f8dd3f3f8f88bf431d2b
6
+ metadata.gz: 01f037f4d5c92d57442ba8232d3afe64410770ac7fc62aacb4e18a391295ef1f1ad6c1c6fab1881fc24457aaa7161a71e74470587434166fc2ecbc1de9802566
7
+ data.tar.gz: 91287d51707af0a469dda51139ad8c8efb61228619b52ca7a3a84905f3f44472da1fcbe99e2ecc92a42a3f101d541ad74176750a51f1bd62c52998e33648c1bd
@@ -19,37 +19,10 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  require_relative 'recordset'
22
- require 'uri'
22
+ require_relative 'path'
23
23
 
24
24
  module Relaxo
25
25
  module Model
26
- # A string that won't be escaped when concatenating with a path:
27
- class Path < String
28
- ENCODE = {'/' => '%2F', '%' => '%25'}
29
-
30
- def to_s
31
- self
32
- end
33
-
34
- def to_str
35
- self
36
- end
37
-
38
- def self.join(path)
39
- joined_path = path.map do |part|
40
- part = part.to_s
41
-
42
- if part.is_a? Path
43
- part
44
- else
45
- part.to_s.gsub(/[\/%]/, ENCODE)
46
- end
47
- end.join('/')
48
-
49
- return self.new(joined_path)
50
- end
51
- end
52
-
53
26
  Key = Struct.new(:prefix, :index) do
54
27
  def resolve(key_path, model, **arguments)
55
28
  key_path.collect do |component|
@@ -67,11 +40,11 @@ module Relaxo
67
40
  end
68
41
 
69
42
  def object_path(model, **arguments)
70
- Path.join resolve(self.prefix + self.index, model, **arguments)
43
+ resolve(self.prefix + self.index, model, **arguments).join('/')
71
44
  end
72
45
 
73
46
  def prefix_path(model, **arguments)
74
- Path.join resolve(self.prefix, model, **arguments)
47
+ resolve(self.prefix, model, **arguments).join('/')
75
48
  end
76
49
  end
77
50
 
@@ -100,11 +73,20 @@ module Relaxo
100
73
  attr :primary_key
101
74
 
102
75
  def parent_type klass
103
- @type = Path.join([klass.type, self.type])
76
+ @type = [klass.type, self.type].join('/')
104
77
  end
105
78
 
106
- def view(name, path = nil, klass: self, index: nil)
107
- key = Key.new(path, index)
79
+ def view(name, *path, klass: self, index: nil)
80
+ # Support array as 2nd argument, e.g.
81
+ # view :by_owner, [:type, 'by_owner', ]
82
+ if path.empty?
83
+ path = [:type, name.to_s, name.to_s.split('_', 2).last.to_sym]
84
+ elsif path.count == 1 and path.first.is_a? Array
85
+ warn "Legacy view for #{self}: #{name} -> #{path}"
86
+ path = path.first
87
+ end
88
+
89
+ key = Key.new(path, Array(index))
108
90
 
109
91
  if index
110
92
  @keys[name] = key
@@ -124,6 +106,12 @@ module Relaxo
124
106
  end
125
107
  end
126
108
 
109
+ def unique(*keys)
110
+ lambda do |arguments|
111
+ Path.escape keys.map{|key| arguments[key] || self[key] || 'null'}
112
+ end
113
+ end
114
+
127
115
  def property(name, klass = nil)
128
116
  if @properties.key? name
129
117
  raise ArgumentError.new("Property #{name.inspect} already defined!")
@@ -22,6 +22,7 @@ require 'relaxo/model/base'
22
22
 
23
23
  module Relaxo
24
24
  module Model
25
+ # Represents an underlying object with changes which can be persisted.
25
26
  module Component
26
27
  def self.included(child)
27
28
  # $stderr.puts "#{self} included -> #{child} extend Base"
@@ -30,35 +31,31 @@ module Relaxo
30
31
 
31
32
  def initialize(dataset, object = nil, changed = {}, **attributes)
32
33
  @dataset = dataset
34
+
35
+ # The object from the dataset:
33
36
  @object = object
34
- @changed = changed
37
+
38
+ # Underlying attributes from the dataset:
35
39
  @attributes = attributes
40
+
41
+ # Contains non-primitve attributes and changes:
42
+ @changed = changed
36
43
  end
37
44
 
38
- def load_object
39
- if @object
40
- attributes = MessagePack.load(@object.data, symbolize_keys: true)
41
-
42
- # We prefer existing @attributes over ones loaded from data. This allows the API to load from an object, but specify new attributes.
43
- @attributes = attributes.merge(@attributes)
44
- end
45
- end
45
+ # The dataset this document is currently bound to:
46
+ attr :dataset
47
+
48
+ # The attributes specified/loaded from the dataset:
49
+ attr :attributes
50
+
51
+ # Attributes that have been changed or de-serialized from the dataset:
52
+ attr :changed
46
53
 
47
54
  def reload
48
55
  @changed.clear
49
56
  self.load_object
50
57
  end
51
58
 
52
- def dump
53
- flatten!
54
-
55
- MessagePack.dump(@attributes)
56
- end
57
-
58
- attr :attributes
59
- attr :dataset
60
- attr :changed
61
-
62
59
  def clear(key)
63
60
  @changed.delete(key)
64
61
  @attributes.delete(key)
@@ -108,9 +105,31 @@ module Relaxo
108
105
  def validate
109
106
  # Do nothing :)
110
107
  end
111
-
108
+
109
+ def to_hash
110
+ @attributes
111
+ end
112
+
113
+ def load_object
114
+ if @object
115
+ attributes = MessagePack.load(@object.data, symbolize_keys: true)
116
+
117
+ # We prefer existing @attributes over ones loaded from data. This allows the API to load from an object, but specify new attributes.
118
+ @attributes = attributes.merge(@attributes)
119
+ end
120
+ end
121
+
122
+ protected
123
+
124
+ # Flatten all changes and return a serialized version of the object.
125
+ def dump
126
+ flatten!
127
+
128
+ MessagePack.dump(@attributes)
129
+ end
130
+
131
+ # Moves values from `@changed` into `@attributes`.
112
132
  def flatten!
113
- # Flatten changed properties:
114
133
  self.class.properties.each do |key, klass|
115
134
  if @changed.include?(key)
116
135
  if klass
@@ -130,10 +149,6 @@ module Relaxo
130
149
 
131
150
  @changed = {}
132
151
  end
133
-
134
- def to_hash
135
- @attributes
136
- end
137
152
  end
138
153
  end
139
154
  end
@@ -105,7 +105,7 @@ module Relaxo
105
105
  end
106
106
 
107
107
  def type
108
- Path.new @attributes[:type]
108
+ @attributes[:type]
109
109
  end
110
110
 
111
111
  def valid_type?
@@ -119,6 +119,7 @@ module Relaxo
119
119
  def after_save
120
120
  end
121
121
 
122
+ # The canonical path to the object in the data store, assuming there is some unique way to identify the object.
122
123
  def to_s
123
124
  if primary_key = self.class.primary_key
124
125
  primary_key.object_path(self)
@@ -127,6 +128,10 @@ module Relaxo
127
128
  end
128
129
  end
129
130
 
131
+ def inspect
132
+ "\#<#{self.class}:#{self.id} #{self.attributes.inspect}>"
133
+ end
134
+
130
135
  # Duplicate the model object, and possibly change the dataset it is connected to. You will potentially have two objects referring to the same record.
131
136
  def dup
132
137
  clone = self.class.new(@dataset, @object, @changed, **@attributes.dup)
@@ -0,0 +1,50 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <https://www.codeotaku.com/>
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
+ # A string that won't be escaped when concatenating with a path:
24
+ class Path < String
25
+ ENCODE = {'/' => '%2F', '%' => '%25'}
26
+
27
+ def to_s
28
+ self
29
+ end
30
+
31
+ def to_str
32
+ self
33
+ end
34
+
35
+ def self.escape(values)
36
+ escaped_path = values.map do |part|
37
+ part = part.to_s
38
+
39
+ if part.is_a? Path
40
+ part
41
+ else
42
+ part.to_s.gsub(/[\/%]/, ENCODE)
43
+ end
44
+ end.join('-')
45
+
46
+ return self.new(escaped_path)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Relaxo
22
22
  module Model
23
- VERSION = "0.11.0"
23
+ VERSION = "0.12.0"
24
24
  end
25
25
  end
@@ -29,8 +29,17 @@ RSpec.describe Relaxo::Model::Document do
29
29
  expect(Invoice.all(database.current).count).to be == 3
30
30
  end
31
31
 
32
+ it "should have valid type" do
33
+ expect(Invoice.type).to be == "invoice"
34
+ expect(Invoice::Transaction.type).to be == "invoice/transaction"
35
+ end
36
+
32
37
  it "should have resolved type" do
33
- expect(Invoice::Transaction.type).to be_a Relaxo::Model::Path
38
+ transaction = Invoice::Transaction.create(database.current, {id: 'test'})
39
+
40
+ transaction.attributes[:type] = ["Invoice", "Transaction"]
41
+
42
+ expect(transaction.paths.first).to be == "Invoice/Transaction/test"
34
43
  end
35
44
 
36
45
  it "should create model indexes" do
@@ -56,6 +65,26 @@ RSpec.describe Relaxo::Model::Document do
56
65
  expect(transaction.to_s).to be == "invoice/transaction/#{transaction.id}"
57
66
  end
58
67
 
68
+ it "can edit model objects" do
69
+ invoice = nil
70
+
71
+ database.commit(message: "Adding test model") do |dataset|
72
+ invoice = Invoice.create(dataset, name: "Software Development")
73
+ invoice.save(dataset)
74
+ end
75
+
76
+ # Fetch the invoice from the database:
77
+ invoice = Invoice.fetch_all(database.current, id: invoice.id)
78
+ invoice.name = "Software Engineering"
79
+
80
+ database.commit(message: "Editing test model") do |dataset|
81
+ invoice.save(dataset)
82
+ end
83
+
84
+ invoice = Invoice.fetch_all(database.current, id: invoice.id)
85
+ expect(invoice.name).to be == "Software Engineering"
86
+ end
87
+
59
88
  it "updates indexes correctly" do
60
89
  transaction = nil
61
90
 
@@ -12,7 +12,7 @@ class Invoice
12
12
 
13
13
  property :date, Attribute[Date]
14
14
 
15
- view :all, [:type], index: [:id]
15
+ view :all, :type, index: :id
16
16
 
17
17
  def transactions
18
18
  Invoice::Transaction.by_invoice(@dataset, invoice: self)
@@ -28,9 +28,9 @@ class Invoice::Transaction
28
28
  property :invoice, BelongsTo[Invoice]
29
29
  property :date, Attribute[Date]
30
30
 
31
- view :all, [:type], index: [:id]
31
+ view :all, :type, index: :id
32
32
 
33
- view :by_invoice, [:type, 'by_invoice', :invoice], index: [[:date, :id]]
33
+ view :by_invoice, index: unique(:date, :id)
34
34
  end
35
35
 
36
36
  class User
@@ -40,9 +40,9 @@ class User
40
40
  property :name
41
41
  property :intro
42
42
 
43
- view :all, [:type], index: [:email]
43
+ view :all, :type, index: unique(:email)
44
44
 
45
- view :by_name, [:type, 'by_name'], index: [:name]
45
+ view :by_name, :type, 'by_name', index: unique(:name)
46
46
  end
47
47
 
48
48
  RSpec.shared_context "model" do
@@ -7,9 +7,9 @@ RSpec.describe Relaxo::Model::Recordset do
7
7
  context "with several invoices" do
8
8
  before(:each) do
9
9
  database.commit(message: "Adding test model") do |dataset|
10
- @first = Invoice.insert(dataset, name: "Software Development")
11
- @middle = Invoice.insert(dataset, name: "Website Hosting")
12
- @last = Invoice.insert(dataset, name: "Backup Services")
10
+ @first = Invoice.insert(dataset, id: "a", name: "Software Development")
11
+ @middle = Invoice.insert(dataset, id: "b", name: "Website Hosting")
12
+ @last = Invoice.insert(dataset, id: "c", name: "Backup Services")
13
13
  end
14
14
  end
15
15
 
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.11.0
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -99,6 +99,7 @@ files:
99
99
  - lib/relaxo/model/base.rb
100
100
  - lib/relaxo/model/component.rb
101
101
  - lib/relaxo/model/document.rb
102
+ - lib/relaxo/model/path.rb
102
103
  - lib/relaxo/model/properties.rb
103
104
  - lib/relaxo/model/properties/attribute.rb
104
105
  - lib/relaxo/model/properties/bcrypt.rb