rie 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4f20eb6f5e275de66f7eecb8776d6b050d778fd0
4
+ data.tar.gz: 26672d08cfeee732ea10de1d5ac688ded3b92ff0
5
+ SHA512:
6
+ metadata.gz: 8cb9a6c21a61de5238cf33b34a5719d343d2ee229799c02acc017349d184dec8ed5f2002c2f8e50b428f2d9686db34a9757e656d0ce00b88e75519c65e40d151
7
+ data.tar.gz: e89b60722ac59e0b22587d1dab000a2027eae8a0fc71deb62ddec855aa7700675d5b746b781d8ca3ecd1afaf2ea5fa0c8642d1ba7fa075421585edc5d800e3d2
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+ gem 'minitest'
5
+ gem 'wrong'
6
+
7
+ gem 'jbundler'
8
+ gem 'dalton'
@@ -0,0 +1,10 @@
1
+ require 'logger' # stdlib
2
+ require 'dalton'
3
+
4
+ load_dir = Pathname.new(__FILE__).dirname
5
+ load load_dir.join('rie/model.rb')
6
+ load load_dir.join('rie/schema.rb')
7
+ load load_dir.join('rie/attribute.rb')
8
+ load load_dir.join('rie/base_finder.rb')
9
+ load load_dir.join('rie/base_changer.rb')
10
+ load load_dir.join('rie/validator.rb')
@@ -0,0 +1,149 @@
1
+ module Rie
2
+ class Attribute
3
+ attr_reader :name, :model, :datomic_attribute, :type
4
+ def initialize(model, name, opts={})
5
+ @name = name
6
+ @model = model
7
+ @datomic_attribute = opts.fetch(:datomic_attribute) { default_datomic_attribute }
8
+ @type = Type.for(opts[:type])
9
+ end
10
+
11
+ def default_datomic_attribute
12
+ "#{model.namespace}.#{model.datomic_name}/#{name.to_s.tr('_', '-')}"
13
+ end
14
+
15
+ def load(value)
16
+ type.load(self, value)
17
+ end
18
+
19
+ def dump(value)
20
+ type.dump(self, value)
21
+ end
22
+
23
+ def datoms_for(changer, value, &b)
24
+ type.datoms_for(self, changer, value, &b)
25
+ end
26
+
27
+ class Type
28
+ def self.for(definition)
29
+ return definition if definition.is_a? Type
30
+ return AutoType.new if definition.nil?
31
+
32
+ type, *args = definition
33
+ type_name = "#{type.capitalize}Type"
34
+ raise "no such type #{type}" unless const_defined?(type_name)
35
+ const_get(type_name).new(*args)
36
+ end
37
+
38
+ def load(attr, value)
39
+ value
40
+ end
41
+
42
+ def dump(attr, value)
43
+ value
44
+ end
45
+
46
+ def datoms_for(attr, changer, value, &b)
47
+ yield(:'db/id' => changer.id, attr.datomic_attribute => dump(attr, value))
48
+ end
49
+
50
+ def invalid_value!(attr, value)
51
+ raise ::TypeError, "invalid value for #{attr.datomic_attribute}: #{value.inspect}"
52
+ end
53
+
54
+ class Scalar < Type
55
+ def inspect
56
+ "(scalar)"
57
+ end
58
+ end
59
+
60
+ class AutoType < Type
61
+ def type_from_value(value)
62
+ case value
63
+ when Enumerable
64
+ SetType.new(self)
65
+ when Dalton::Entity
66
+ RefType.new(raise 'TODO')
67
+ when Numeric, String, Symbol, true, false, nil
68
+ Scalar.new
69
+ else
70
+ raise TypeError.new("unknown value type: #{value.inspect}")
71
+ end
72
+ end
73
+
74
+ def load(attr, value)
75
+ type_from_value(value).load(attr, value)
76
+ end
77
+
78
+ def dump(attr, value)
79
+ type_from_value(value).dump(attr, value)
80
+ end
81
+
82
+ def datoms_for(attr, changer, value, &b)
83
+ type_from_value(value).datoms_for(attr, changer, value, &b)
84
+ end
85
+
86
+ def inspect
87
+ "(auto)"
88
+ end
89
+ end
90
+
91
+ class RefType < Type
92
+ attr_reader :ref_class
93
+ def initialize(ref_class)
94
+ @ref_class = ref_class
95
+ end
96
+
97
+ def inspect
98
+ "(ref #{@ref_class.name})"
99
+ end
100
+
101
+ def load(attr, entity_map)
102
+ return nil if entity_map.nil?
103
+ registry_name = entity_map.get(attr.model.datomic_type_key)
104
+ invalid_value!(attr, entity_map) unless registry_name == @ref_class.datomic_type
105
+ @ref_class.new(entity_map)
106
+ end
107
+
108
+ def dump(attr, value)
109
+ value.id
110
+ end
111
+
112
+ def datoms_for(attr, changer, value, &block)
113
+ invalid_value!(attr, value) unless value.respond_to? :id
114
+
115
+ yield(:'db/id' => changer.id, attr.datomic_attribute => value.id)
116
+
117
+ value.generate_datoms(&block) if value.is_a? BaseChanger
118
+ end
119
+ end
120
+
121
+ class SetType < Type
122
+ def initialize(element_type)
123
+ @element_type = Type.for(element_type)
124
+ end
125
+
126
+ def inspect
127
+ "(set #{@element_type.inspect})"
128
+ end
129
+
130
+ def dump(attr, value)
131
+ value.map { |x| @element_type.dump(value) }
132
+ end
133
+
134
+ def load(attr, value)
135
+ # empty sets are often returned as nil in datomic :[
136
+ return Set.new if value.nil?
137
+ invalid_value!(attr, value) unless value.is_a? Enumerable
138
+ Set.new(value.map { |e| @element_type.load(attr, e) })
139
+ end
140
+
141
+ def datoms_for(attr, changer, value, &block)
142
+ value.each do |v|
143
+ @element_type.datoms_for(attr, changer, v, &block)
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,93 @@
1
+ module Rie
2
+ class BaseChanger
3
+ attr_reader :id, :original, :changes, :retractions
4
+ def initialize(id, attrs)
5
+ @id = id
6
+ @original = attrs.dup.freeze
7
+ @changes = {}
8
+ @retractions = Set.new
9
+ end
10
+
11
+ def retract!(attribute)
12
+ @retractions << attribute
13
+ end
14
+
15
+ def change(key=nil, &b)
16
+ b.call(self)
17
+ self
18
+ end
19
+
20
+ def change_ref(key, &b)
21
+ attribute = model.get_attribute(key)
22
+ type = attribute.type
23
+
24
+ unless type.respond_to? :ref_class
25
+ raise ::TypeError, "change_ref only works on refs - #{key} is a #{type.inspect} is not a ref"
26
+ end
27
+
28
+ self[key] = self[key] ? self[key].change(&b) : type.ref_class.create(&b)
29
+ end
30
+
31
+ def change!(&b)
32
+ change(&b)
33
+ save!
34
+ end
35
+
36
+ def [](key)
37
+ return nil if @retractions.include? key
38
+ @changes.fetch(key) { @original[key] }
39
+ end
40
+
41
+ def original(key)
42
+ @original[key]
43
+ end
44
+
45
+ def change_in(key)
46
+ [original(key), self[key]]
47
+ end
48
+
49
+ def []=(key, val)
50
+ if val.nil?
51
+ @retractions << key
52
+ else
53
+ @retractions.delete(key)
54
+ @changes[key] = val
55
+ end
56
+ end
57
+
58
+ def updated_attributes
59
+ out = model.attributes.merge(@changes)
60
+ @retractions.each { |r| out.delete(r) }
61
+ out
62
+ end
63
+
64
+ def generate_datoms(&b)
65
+ return enum_for(:generate_datoms).to_a unless block_given?
66
+
67
+ yield model.base_attributes.merge(:'db/id' => @id)
68
+ @changes.each do |key, new_val|
69
+ attribute = model.get_attribute(key)
70
+ attribute.datoms_for(self, new_val, &b)
71
+ end
72
+ end
73
+
74
+ private
75
+ def save!
76
+ validate!
77
+ persist!
78
+ end
79
+
80
+ def persist!
81
+ result = model.transact(generate_datoms)
82
+ @id = result.resolve_tempid(@id) unless @id.is_a? Fixnum
83
+ model.new(result.db_after.entity(@id))
84
+ rescue Dalton::TypeError, Dalton::UniqueConflict => e
85
+ raise TransactionValidationError.new(self, e)
86
+ end
87
+
88
+ def validate!
89
+ model.validator.run_all!(self)
90
+ end
91
+ end
92
+ end
93
+
@@ -0,0 +1,99 @@
1
+ module Rie
2
+ class NotFound < StandardError
3
+ attr_reader :model, :id
4
+ def initialize(model, id)
5
+ @model = model
6
+ @id = id
7
+ end
8
+
9
+ def message
10
+ "Could not find #{model} with id #{id}"
11
+ end
12
+ end
13
+
14
+ class BaseFinder
15
+ include Enumerable
16
+ include Dalton::Utility
17
+
18
+ # should be overridden automatically
19
+ def model
20
+ raise "abstract"
21
+ end
22
+
23
+ def inspect
24
+ translated = Dalton::Translation.from_ruby(all_constraints).to_edn[1..-2]
25
+ "#<#{self.class.name} ##{db.basis_t} :where #{translated}>"
26
+ end
27
+
28
+ attr_reader :db, :constraints
29
+ def initialize(db, constraints=[])
30
+ @db = db
31
+ @constraints = constraints
32
+ end
33
+
34
+ def where(*constraints)
35
+ new_constraints = @constraints.dup
36
+ constraints.each do |c|
37
+ case c
38
+ when Array
39
+ new_constraints << c
40
+ when Hash
41
+ interpret_constraints(c, &new_constraints.method(:<<))
42
+ end
43
+ end
44
+
45
+ self.class.new(@db, new_constraints)
46
+ end
47
+
48
+ def entity(id)
49
+ entity = @db.entity(id)
50
+
51
+ unless entity.get(model.datomic_type_key) == model.datomic_type
52
+ raise NotFound.new(model, id)
53
+ end
54
+
55
+ model.new(entity)
56
+ end
57
+
58
+ def results
59
+ query = [:find, sym('?e'), :in, sym('$'), :where, *all_constraints]
60
+ q(query).lazy.map do |el|
61
+ model.new(@db.entity(el.first))
62
+ end
63
+ end
64
+
65
+ def type_constraint
66
+ [sym('?e'), model.datomic_type_key, model.datomic_type]
67
+ end
68
+
69
+ def all_constraints
70
+ [type_constraint, *constraints]
71
+ end
72
+
73
+ def each(&b)
74
+ results.each(&b)
75
+ end
76
+
77
+ def with_model(model)
78
+ model.finder(@db)
79
+ end
80
+
81
+ private
82
+
83
+ def interpret_constraints(hash, &b)
84
+ return enum_for(:interpret_constraints, hash) unless block_given?
85
+
86
+ hash.each do |key, value|
87
+ attribute = model.get_attribute(key)
88
+ yield [sym('?e'), attribute.datomic_attribute, attribute.dump(value)]
89
+ end
90
+ end
91
+
92
+ def q(query)
93
+ translated_query = Dalton::Translation.from_ruby(query)
94
+ Model.logger.info("datomic.q #{translated_query.to_edn}")
95
+ result = @db.q(translated_query)
96
+ Dalton::Translation.from_clj(result)
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,21 @@
1
+ module Rie
2
+ module HasDatabase
3
+ attr_accessor :db
4
+
5
+ def datomic_uri
6
+ raise "please define datomic_uri on #{self.class.name}"
7
+ end
8
+
9
+ def datomic_connection
10
+ Dalton::Connection.connect(datomic_uri)
11
+ end
12
+
13
+ def refresh_datomic!
14
+ @db = datomic_connection.db
15
+ end
16
+
17
+ def find(model, *args)
18
+ model.finder(@db, *args)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,225 @@
1
+ module Rie
2
+ module Model
3
+ def self.included(base)
4
+ base.class_eval do
5
+ @attributes = {}
6
+ @base_attributes = {}
7
+ @defaults = {}
8
+ @validator = Validator.new(base)
9
+
10
+ const_set :Finder, Class.new(BaseFinder) {
11
+ # we use a constant here so that `super` works
12
+ # in overriding generated methods
13
+ const_set :AttributeMethods, Module.new
14
+ include self::AttributeMethods
15
+ define_method(:model) { base }
16
+ }
17
+
18
+ const_set :Changer, Class.new(BaseChanger) {
19
+ # as above
20
+ const_set :AttributeMethods, Module.new
21
+ include self::AttributeMethods
22
+ define_method(:model) { base }
23
+ }
24
+
25
+ extend Rie::Model::ClassMethods
26
+ end
27
+ end
28
+
29
+ @registry = {}
30
+ @logger = Logger.new($stderr)
31
+ @logger.level = Logger::WARN
32
+
33
+ class << self
34
+ attr_reader :registry
35
+ attr_writer :logger
36
+
37
+ def install_schemas!
38
+ registry.values.each(&:install_schema!)
39
+ end
40
+
41
+ def install_bases!
42
+ registry.values.each(&:install_base!)
43
+ end
44
+
45
+ def install!
46
+ install_bases!
47
+ install_schemas!
48
+ end
49
+
50
+ attr_accessor :namespace, :partition, :uri, :logger
51
+ def configure(&b)
52
+ yield self
53
+ end
54
+ end
55
+
56
+ module ClassMethods
57
+ attr_reader :attributes
58
+ attr_reader :defaults
59
+ attr_reader :validator
60
+ attr_reader :datomic_name
61
+ attr_reader :namespace
62
+ attr_reader :partition
63
+ attr_reader :base_attributes
64
+
65
+ def transact(edn)
66
+ Model.logger.info("datomic.transact #{Dalton::Connection.convert_datoms(edn).to_edn}")
67
+ connection.transact(edn)
68
+ end
69
+
70
+ def base_attribute(key, val)
71
+ @base_attributes.merge!(key => val)
72
+ end
73
+
74
+ def uri(arg=nil)
75
+ @uri = arg if arg
76
+ @uri or Model.uri or raise "you must specify a datomic uri for #{self}"
77
+ end
78
+
79
+ def connection
80
+ Dalton::Connection.connect(uri)
81
+ end
82
+
83
+ def datomic_type
84
+ :"#{namespace}.type/#{datomic_name}"
85
+ end
86
+
87
+ def datomic_type_key
88
+ :"#{namespace}/type"
89
+ end
90
+
91
+ def attribute(attr, datomic_key=nil, opts={})
92
+ if datomic_key.is_a? Hash
93
+ opts = datomic_key
94
+ datomic_key = nil
95
+ end
96
+
97
+ datomic_key ||= "#{self.namespace}.#{self.datomic_name}/#{attr.to_s.tr('_', '-')}"
98
+ define_attribute(attr, datomic_key, opts)
99
+ end
100
+
101
+ def define_attribute(key, datomic_key, opts={})
102
+ @attributes[key] = Attribute.new(self, key, opts.merge(datomic_attribute: datomic_key))
103
+ @defaults[key] = opts[:default]
104
+
105
+ define_method(key) { self[key] }
106
+
107
+ self::Finder::AttributeMethods.class_eval do
108
+ define_method("by_#{key}") { |v| where(key => v) }
109
+ end
110
+
111
+ self::Changer::AttributeMethods.class_eval do
112
+ define_method(key) { self[key] }
113
+ define_method("#{key}=") { |v| self[key] = v }
114
+ end
115
+ end
116
+
117
+ def get_attribute(key)
118
+ @attributes.fetch(key) do
119
+ raise ArgumentError, "Undefined attribute #{key} for #{self}"
120
+ end
121
+ end
122
+
123
+ def finders(&b)
124
+ self::Finder.class_eval(&b)
125
+ end
126
+
127
+ def changers(&b)
128
+ self::Changer.class_eval(&b)
129
+ end
130
+
131
+ def validation(&b)
132
+ @validator.specify(&b)
133
+ end
134
+
135
+ def finder(db, constraints=[])
136
+ self::Finder.new(db).where(constraints)
137
+ end
138
+
139
+ def create!(&b)
140
+ self::Changer.new(Dalton::Utility.tempid(partition), defaults).change!(&b)
141
+ end
142
+
143
+ def create(&b)
144
+ self::Changer.new(Dalton::Utility.tempid(partition), defaults).change(&b)
145
+ end
146
+ end
147
+
148
+ attr_reader :finder, :entity
149
+ def initialize(entity)
150
+ @entity = entity
151
+ @finder = self.class::Finder.new(entity.db)
152
+ end
153
+
154
+ def id
155
+ entity.get(:'db/id')
156
+ end
157
+
158
+ def db
159
+ entity.db
160
+ end
161
+
162
+ def at(db)
163
+ self.class::Finder.new(db).entity(self.id)
164
+ end
165
+
166
+ def [](key)
167
+ definition = self.class.get_attribute(key)
168
+
169
+ definition.load(entity.get(definition.datomic_attribute))
170
+ end
171
+
172
+ def interpret_value(value)
173
+ case value
174
+ when Enumerable
175
+ value.lazy.map { |e| interpret_value(e) }
176
+ when Java::DatomicQuery::EntityMap
177
+ self.class.interpret_entity(value)
178
+ when Numeric, String, Symbol, true, false, nil
179
+ value
180
+ else
181
+ raise TypeError.new("unknown value type: #{value.inspect}")
182
+ end
183
+ end
184
+
185
+ def attributes
186
+ out = {}
187
+
188
+ self.class.attributes.each do |attr, _|
189
+ out[attr] = send(attr)
190
+ end
191
+
192
+ out
193
+ end
194
+
195
+ def to_h
196
+ attributes.merge(:id => id)
197
+ end
198
+
199
+ # TODO: fix this implementation
200
+ def updated_at
201
+ txid = db.q('[:find (max ?t) :in $ ?e :where [?e _ _ ?t]]', self.id).first.first
202
+ db.entity(txid).get(:'db/txInstant').to_time
203
+ end
204
+
205
+ def changer
206
+ self.class::Changer.new(id, attributes)
207
+ end
208
+
209
+ def change(&b)
210
+ changer.change(&b)
211
+ end
212
+
213
+ def change!(&b)
214
+ changer.change!(&b)
215
+ end
216
+
217
+ def retract!
218
+ self.class.transact([[:'db.fn/retractEntity', self.id]])
219
+ end
220
+
221
+ def ==(other)
222
+ self.entity == other.entity
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,112 @@
1
+ module Rie
2
+ module Model
3
+ module ClassMethods
4
+ def schema(name=nil, opts={}, &b)
5
+ return @schema unless block_given?
6
+
7
+ if name.is_a? Hash
8
+ opts = name
9
+ name = nil
10
+ end
11
+
12
+ @datomic_name = name
13
+ @datomic_name ||= self.name
14
+ .gsub(/[^[:alpha:]]+/, '-')
15
+ .gsub(/(?<=[[:lower:]])(?=[[:upper:]])/, '-')
16
+ .downcase
17
+
18
+ @namespace = opts.fetch(:namespace) { Model.namespace } \
19
+ or raise ArgumentError.new("no namespace configured for #{self} or globally")
20
+ @partition = opts.fetch(:partition) { Model.partition } \
21
+ or raise ArgumentError.new("no partition configured for #{self} or globally")
22
+ @partition = :"db.part/#{partition}" unless partition.to_s.start_with?('db.part/')
23
+
24
+ Model.registry[datomic_type.to_s] = self
25
+ base_attribute datomic_type_key, datomic_type
26
+
27
+ @schema = Schema.new(self, &b)
28
+ end
29
+
30
+ def install_schema!
31
+ raise ArgumentError.new("no schema defined for #{self}!") unless schema
32
+ schema.install!
33
+ end
34
+
35
+ def install_base!
36
+ transact <<-EDN
37
+ [{:db/id #db/id[:db.part/db]
38
+ :db/ident :#{partition}
39
+ :db.install/_partition :db.part/db}
40
+
41
+ {:db/id #db/id[:db.part/db]
42
+ :db/ident :#{namespace}/type
43
+ :db/valueType :db.type/ref
44
+ :db/cardinality :db.cardinality/one
45
+ :db/doc "A model's type"
46
+ :db.install/_attribute :db.part/db}]
47
+ EDN
48
+ end
49
+ end
50
+ end
51
+
52
+ class Schema
53
+ include Dalton::Utility
54
+
55
+ attr_reader :model, :transactions
56
+ def initialize(model, &block)
57
+ @model = model
58
+ @transactions = []
59
+ declare_type
60
+ instance_exec(&block)
61
+ end
62
+
63
+ def name
64
+ model.datomic_name
65
+ end
66
+
67
+ def partition
68
+ model.partition
69
+ end
70
+
71
+ def namespace
72
+ model.namespace
73
+ end
74
+
75
+ def key(key, subkey=nil)
76
+ if subkey
77
+ :"#{namespace}.#{key}/#{subkey}"
78
+ else
79
+ :"#{namespace}/#{key}"
80
+ end
81
+ end
82
+
83
+ def declare_type
84
+ edn [:'db/add', Peer.tempid(kw(partition)), :'db/ident', key(:type, name)]
85
+ end
86
+
87
+ def edn(edn)
88
+ @transactions << edn
89
+ end
90
+
91
+ def attribute(attr_key, opts={})
92
+ config = {
93
+ :'db/id' => opts.fetch(:id) { Peer.tempid(kw('db.part/db')) },
94
+ :'db/ident' => kw(opts.fetch(:ident) { key(model.datomic_name, attr_key) }),
95
+ :'db/valueType' => :"db.type/#{opts.fetch(:value_type)}",
96
+ :'db/cardinality' => :"db.cardinality/#{opts.fetch(:cardinality, :one)}",
97
+ :'db/doc' => opts.fetch(:doc) { "The #{attr_key} attribute" },
98
+ :'db.install/_attribute' => :'db.part/db',
99
+ }
100
+
101
+ config[:'db/unique'] = :"db.unique/#{opts[:unique]}" if opts[:unique]
102
+
103
+ edn(config)
104
+ end
105
+
106
+ def install!
107
+ transactions.each do |t|
108
+ model.transact([t])
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,105 @@
1
+ module Rie
2
+ class ValidationError < StandardError
3
+ attr_reader :changes, :errors
4
+
5
+ def initialize(changes, errors)
6
+ @changes = changes
7
+ @errors = errors
8
+ end
9
+
10
+ def errors_on(key, &b)
11
+ return enum_for(:errors_on, key).to_a unless block_given?
12
+
13
+ errors.each do |(keys, message)|
14
+ yield message if keys.include? key
15
+ end
16
+ end
17
+
18
+ def errors_on?(key)
19
+ errors_on(key).any?
20
+ end
21
+ end
22
+
23
+ class TransactionValidationError < ValidationError
24
+ def initialize(changes, datomic_error)
25
+ @changes = changes
26
+ @datomic_error = datomic_error
27
+ end
28
+
29
+ def errors
30
+ # TODO: translate this key
31
+ [@datomic_error.attribute, @datomic_error.message]
32
+ end
33
+
34
+ def errors_on?(key)
35
+ changes.model.get_attribute(key).datomic_attribute == @datomic_error.attribute
36
+ end
37
+ end
38
+
39
+ class Validator
40
+ # a definition of a validator. the block gets run in the context of
41
+ # a Scope, and may call `invalid!` with optional attributes and an
42
+ # error message
43
+ class Rule
44
+ class Scope
45
+ def initialize(attrs, validate, &report)
46
+ @validate = validate
47
+ @attrs = attrs
48
+ @report = report
49
+ end
50
+
51
+ def invalid!(attr_names=nil, description)
52
+ attr_names ||= @attrs
53
+ attr_names = Array(attr_names)
54
+
55
+ @report.call [attr_names, description]
56
+ end
57
+
58
+ def run(values)
59
+ instance_exec(*values, &@validate)
60
+ end
61
+ end
62
+
63
+ def initialize(*attrs, &block)
64
+ @attrs = attrs
65
+ @block = block
66
+ end
67
+
68
+ def run(changer, &out)
69
+ values = @attrs.map { |a| changer.send(a) }
70
+ Scope.new(@attrs, @block, &out).run(values)
71
+ end
72
+ end
73
+
74
+ attr_reader :validators
75
+ def initialize(model, &defn)
76
+ @model = model
77
+ @validators = []
78
+ specify(&defn) if defn
79
+ end
80
+
81
+ def specify(&defn)
82
+ instance_eval(&defn)
83
+ end
84
+
85
+ # define a validation on *attrs using &block.
86
+ # See Rule
87
+ def validate(*attrs, &block)
88
+ validators << Rule.new(*attrs, &block)
89
+ end
90
+
91
+ # returns an enumerable of validation errors on the changeset,
92
+ # which is empty if the changeset is valid
93
+ def run_all(changer, &report)
94
+ return enum_for(:run_all, changer).to_a unless block_given?
95
+
96
+ validators.each { |v| v.run(changer, &report) }
97
+ end
98
+
99
+ # raises a ValidationError if the changeset is invalid
100
+ def run_all!(changer)
101
+ errors = run_all(changer)
102
+ raise ValidationError.new(changer, errors) if errors.any?
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,5 @@
1
+ module Rie
2
+ def self.version
3
+ '0.0.2'
4
+ end
5
+ end
@@ -0,0 +1,20 @@
1
+ require './lib/rie/version'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "rie"
5
+ s.version = Rie.version
6
+ s.authors = ["Jeanine Adkisson"]
7
+ s.email = ["jneen@goodguide.com"]
8
+ s.summary = "A modeling library for datomic"
9
+
10
+ s.description = <<-desc.strip.gsub(/\s+/, ' ')
11
+ Immutable models, first-class changesets, value-based programming
12
+ desc
13
+
14
+ # s.add_dependency 'dalton'
15
+
16
+ s.homepage = "https://github.com/GoodGuide/rie"
17
+ s.rubyforge_project = "rie"
18
+ s.files = Dir['README.md', 'Gemfile', 'LICENSE', 'rie.gemspec', 'lib/**/*.rb']
19
+ s.license = 'EPL'
20
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rie
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Jeanine Adkisson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-06-02 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Immutable models, first-class changesets, value-based programming
14
+ email:
15
+ - jneen@goodguide.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - Gemfile
21
+ - rie.gemspec
22
+ - lib/rie.rb
23
+ - lib/rie/schema.rb
24
+ - lib/rie/base_changer.rb
25
+ - lib/rie/validator.rb
26
+ - lib/rie/attribute.rb
27
+ - lib/rie/has_database.rb
28
+ - lib/rie/model.rb
29
+ - lib/rie/version.rb
30
+ - lib/rie/base_finder.rb
31
+ homepage: https://github.com/GoodGuide/rie
32
+ licenses:
33
+ - EPL
34
+ metadata: {}
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubyforge_project: rie
51
+ rubygems_version: 2.1.9
52
+ signing_key:
53
+ specification_version: 4
54
+ summary: A modeling library for datomic
55
+ test_files: []
56
+ has_rdoc: