rie 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: