mr 0.35.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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/Gemfile +13 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/bench/all.rb +4 -0
- data/bench/factory.rb +68 -0
- data/bench/fake_record.rb +174 -0
- data/bench/model.rb +201 -0
- data/bench/read_model.rb +191 -0
- data/bench/results/factory.txt +21 -0
- data/bench/results/fake_record.txt +37 -0
- data/bench/results/model.txt +44 -0
- data/bench/results/read_model.txt +46 -0
- data/bench/setup.rb +132 -0
- data/lib/mr.rb +11 -0
- data/lib/mr/after_commit.rb +49 -0
- data/lib/mr/after_commit/fake_record.rb +39 -0
- data/lib/mr/after_commit/record.rb +48 -0
- data/lib/mr/after_commit/record_procs_methods.rb +82 -0
- data/lib/mr/factory.rb +82 -0
- data/lib/mr/factory/config.rb +240 -0
- data/lib/mr/factory/model_factory.rb +103 -0
- data/lib/mr/factory/model_stack.rb +28 -0
- data/lib/mr/factory/read_model_factory.rb +104 -0
- data/lib/mr/factory/record_factory.rb +130 -0
- data/lib/mr/factory/record_stack.rb +219 -0
- data/lib/mr/fake_query.rb +53 -0
- data/lib/mr/fake_record.rb +58 -0
- data/lib/mr/fake_record/associations.rb +257 -0
- data/lib/mr/fake_record/attributes.rb +168 -0
- data/lib/mr/fake_record/persistence.rb +116 -0
- data/lib/mr/json_field.rb +180 -0
- data/lib/mr/json_field/fake_record.rb +31 -0
- data/lib/mr/json_field/record.rb +38 -0
- data/lib/mr/model.rb +67 -0
- data/lib/mr/model/associations.rb +161 -0
- data/lib/mr/model/configuration.rb +67 -0
- data/lib/mr/model/fields.rb +177 -0
- data/lib/mr/model/persistence.rb +79 -0
- data/lib/mr/query.rb +126 -0
- data/lib/mr/read_model.rb +83 -0
- data/lib/mr/read_model/data.rb +38 -0
- data/lib/mr/read_model/fields.rb +218 -0
- data/lib/mr/read_model/query_expression.rb +188 -0
- data/lib/mr/read_model/querying.rb +214 -0
- data/lib/mr/read_model/set_querying.rb +82 -0
- data/lib/mr/read_model/subquery.rb +98 -0
- data/lib/mr/record.rb +35 -0
- data/lib/mr/test_helpers.rb +229 -0
- data/lib/mr/type_converter.rb +85 -0
- data/lib/mr/version.rb +3 -0
- data/log/.gitkeep +0 -0
- data/mr.gemspec +29 -0
- data/test/helper.rb +21 -0
- data/test/support/db.rb +10 -0
- data/test/support/factory.rb +13 -0
- data/test/support/factory/area.rb +6 -0
- data/test/support/factory/comment.rb +14 -0
- data/test/support/factory/image.rb +6 -0
- data/test/support/factory/user.rb +6 -0
- data/test/support/models/area.rb +58 -0
- data/test/support/models/comment.rb +60 -0
- data/test/support/models/image.rb +53 -0
- data/test/support/models/user.rb +96 -0
- data/test/support/read_model/querying.rb +150 -0
- data/test/support/read_models/comment_with_user_data.rb +27 -0
- data/test/support/read_models/set_data.rb +49 -0
- data/test/support/read_models/subquery_data.rb +41 -0
- data/test/support/read_models/user_with_area_data.rb +15 -0
- data/test/support/schema.rb +39 -0
- data/test/support/setup_test_db.rb +10 -0
- data/test/system/factory/model_factory_tests.rb +87 -0
- data/test/system/factory/model_stack_tests.rb +30 -0
- data/test/system/factory/record_factory_tests.rb +84 -0
- data/test/system/factory/record_stack_tests.rb +51 -0
- data/test/system/factory_tests.rb +32 -0
- data/test/system/read_model_tests.rb +199 -0
- data/test/system/with_model_tests.rb +275 -0
- data/test/unit/after_commit/fake_record_tests.rb +110 -0
- data/test/unit/after_commit/record_procs_methods_tests.rb +177 -0
- data/test/unit/after_commit/record_tests.rb +134 -0
- data/test/unit/after_commit_tests.rb +113 -0
- data/test/unit/factory/config_tests.rb +651 -0
- data/test/unit/factory/model_factory_tests.rb +473 -0
- data/test/unit/factory/model_stack_tests.rb +97 -0
- data/test/unit/factory/read_model_factory_tests.rb +195 -0
- data/test/unit/factory/record_factory_tests.rb +446 -0
- data/test/unit/factory/record_stack_tests.rb +549 -0
- data/test/unit/factory_tests.rb +213 -0
- data/test/unit/fake_query_tests.rb +137 -0
- data/test/unit/fake_record/associations_tests.rb +585 -0
- data/test/unit/fake_record/attributes_tests.rb +265 -0
- data/test/unit/fake_record/persistence_tests.rb +239 -0
- data/test/unit/fake_record_tests.rb +106 -0
- data/test/unit/json_field/fake_record_tests.rb +75 -0
- data/test/unit/json_field/record_tests.rb +80 -0
- data/test/unit/json_field_tests.rb +302 -0
- data/test/unit/model/associations_tests.rb +346 -0
- data/test/unit/model/configuration_tests.rb +92 -0
- data/test/unit/model/fields_tests.rb +278 -0
- data/test/unit/model/persistence_tests.rb +114 -0
- data/test/unit/model_tests.rb +137 -0
- data/test/unit/query_tests.rb +300 -0
- data/test/unit/read_model/data_tests.rb +56 -0
- data/test/unit/read_model/fields_tests.rb +416 -0
- data/test/unit/read_model/query_expression_tests.rb +381 -0
- data/test/unit/read_model/querying_tests.rb +613 -0
- data/test/unit/read_model/set_querying_tests.rb +149 -0
- data/test/unit/read_model/subquery_tests.rb +242 -0
- data/test/unit/read_model_tests.rb +187 -0
- data/test/unit/record_tests.rb +45 -0
- data/test/unit/test_helpers_tests.rb +431 -0
- data/test/unit/type_converter_tests.rb +207 -0
- metadata +285 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'mr/query'
|
2
|
+
|
3
|
+
module MR
|
4
|
+
|
5
|
+
class FakeQuery
|
6
|
+
|
7
|
+
attr_reader :results, :count
|
8
|
+
alias :results! :results
|
9
|
+
alias :count! :count
|
10
|
+
|
11
|
+
def initialize(results)
|
12
|
+
@results = results || []
|
13
|
+
@count = @results.size
|
14
|
+
end
|
15
|
+
|
16
|
+
def first
|
17
|
+
@results.first
|
18
|
+
end
|
19
|
+
alias :first! :first
|
20
|
+
|
21
|
+
def paged(page_num = nil, page_size = nil)
|
22
|
+
FakePagedQuery.new(self, page_num, page_size)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
class FakePagedQuery < FakeQuery
|
28
|
+
|
29
|
+
attr_reader :page_num, :page_size, :page_offset, :total_count
|
30
|
+
alias :total_count! :total_count
|
31
|
+
|
32
|
+
def initialize(query, page_num, page_size)
|
33
|
+
@page_num = MR::PagedQuery::PageNumber.new(page_num)
|
34
|
+
@page_size = MR::PagedQuery::PageSize.new(page_size)
|
35
|
+
@page_offset = MR::PagedQuery::PageOffset.new(@page_num, @page_size)
|
36
|
+
@unpaged_results = query.results.dup
|
37
|
+
@total_count = @unpaged_results.size
|
38
|
+
@has_next_page = (@page_offset + @page_size) < @total_count
|
39
|
+
|
40
|
+
super(@unpaged_results.dup[@page_offset, @page_size])
|
41
|
+
end
|
42
|
+
|
43
|
+
def has_next_page?
|
44
|
+
@has_next_page
|
45
|
+
end
|
46
|
+
|
47
|
+
def is_last_page?
|
48
|
+
!self.has_next_page?
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'much-plugin'
|
2
|
+
require 'mr/fake_record/associations'
|
3
|
+
require 'mr/fake_record/attributes'
|
4
|
+
require 'mr/fake_record/persistence'
|
5
|
+
require 'mr/record'
|
6
|
+
|
7
|
+
module MR
|
8
|
+
|
9
|
+
module FakeRecord
|
10
|
+
include MuchPlugin
|
11
|
+
extend MR::FakeRecord::Persistence::TransactionMethods
|
12
|
+
|
13
|
+
plugin_included do
|
14
|
+
include MR::Record
|
15
|
+
include MR::FakeRecord::Associations
|
16
|
+
include MR::FakeRecord::Attributes
|
17
|
+
include MR::FakeRecord::Persistence
|
18
|
+
extend ClassMethods
|
19
|
+
include InstanceMethods
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
|
24
|
+
def model_class(value = nil)
|
25
|
+
@model_class = value if value
|
26
|
+
@model_class
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
module InstanceMethods
|
32
|
+
|
33
|
+
def initialize(attrs = nil)
|
34
|
+
self.attributes = attrs || {}
|
35
|
+
end
|
36
|
+
|
37
|
+
def ==(other)
|
38
|
+
other.kind_of?(self.class) ? self.id == other.id : super
|
39
|
+
end
|
40
|
+
alias :eql? :==
|
41
|
+
|
42
|
+
def hash
|
43
|
+
self.id.hash
|
44
|
+
end
|
45
|
+
|
46
|
+
def inspect
|
47
|
+
object_hex = (self.object_id << 1).to_s(16)
|
48
|
+
attributes_inspect = self.class.attributes.map do |attribute|
|
49
|
+
"@#{attribute.name}=#{attribute.read(self).inspect}"
|
50
|
+
end.sort.join(" ")
|
51
|
+
"#<#{self.class}:0x#{object_hex} #{attributes_inspect}>"
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,257 @@
|
|
1
|
+
require 'much-plugin'
|
2
|
+
require 'active_support/core_ext'
|
3
|
+
require 'mr/fake_record/attributes'
|
4
|
+
|
5
|
+
module MR; end
|
6
|
+
module MR::FakeRecord
|
7
|
+
|
8
|
+
module Associations
|
9
|
+
include MuchPlugin
|
10
|
+
|
11
|
+
plugin_included do
|
12
|
+
include MR::FakeRecord::Attributes
|
13
|
+
extend ClassMethods
|
14
|
+
include InstanceMethods
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
|
19
|
+
def reflections
|
20
|
+
@reflections ||= MR::FakeRecord::ReflectionSet.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def belongs_to(name, options = nil)
|
24
|
+
self.reflections.add_belongs_to(self, name, options)
|
25
|
+
end
|
26
|
+
|
27
|
+
def has_one(name, options = nil)
|
28
|
+
self.reflections.add_has_one(self, name, options)
|
29
|
+
end
|
30
|
+
|
31
|
+
def has_many(name, options = nil)
|
32
|
+
self.reflections.add_has_many(self, name, options)
|
33
|
+
end
|
34
|
+
|
35
|
+
# ActiveRecord methods
|
36
|
+
|
37
|
+
def reflect_on_association(name)
|
38
|
+
self.reflections.find(name)
|
39
|
+
end
|
40
|
+
|
41
|
+
def reflect_on_all_associations(type = nil)
|
42
|
+
self.reflections.all(type)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
module InstanceMethods
|
48
|
+
|
49
|
+
# ActiveRecord methods
|
50
|
+
|
51
|
+
def association(name)
|
52
|
+
@association_cache ||= {}
|
53
|
+
@association_cache[name.to_sym] ||= begin
|
54
|
+
reflection = self.class.reflections.find(name)
|
55
|
+
reflection.association_class.new(self, reflection)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
class ReflectionSet
|
64
|
+
def initialize
|
65
|
+
@belongs_to = {}
|
66
|
+
@has_one = {}
|
67
|
+
@has_many = {}
|
68
|
+
end
|
69
|
+
|
70
|
+
def belongs_to
|
71
|
+
@belongs_to.values.sort
|
72
|
+
end
|
73
|
+
|
74
|
+
def has_one
|
75
|
+
@has_one.values.sort
|
76
|
+
end
|
77
|
+
|
78
|
+
def has_many
|
79
|
+
@has_many.values.sort
|
80
|
+
end
|
81
|
+
|
82
|
+
def find(name)
|
83
|
+
@belongs_to[name.to_s] || @has_one[name.to_s] || @has_many[name.to_s]
|
84
|
+
end
|
85
|
+
|
86
|
+
def all(type = nil)
|
87
|
+
case type
|
88
|
+
when :belongs_to, :has_one, :has_many
|
89
|
+
self.send(type)
|
90
|
+
else
|
91
|
+
self.belongs_to + self.has_one + self.has_many
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def add_belongs_to(fake_record_class, name, options = nil)
|
96
|
+
options ||= {}
|
97
|
+
options[:foreign_key] ||= "#{name}_id"
|
98
|
+
if options[:polymorphic]
|
99
|
+
options[:foreign_type] ||= "#{name}_type"
|
100
|
+
end
|
101
|
+
reflection = Reflection.new(:belongs_to, name, options)
|
102
|
+
reflection.define_accessor_on(fake_record_class)
|
103
|
+
@belongs_to[name.to_s] = reflection
|
104
|
+
end
|
105
|
+
|
106
|
+
def add_has_one(fake_record_class, name, options = nil)
|
107
|
+
reflection = Reflection.new(:has_one, name, options)
|
108
|
+
reflection.define_accessor_on(fake_record_class)
|
109
|
+
@has_one[name.to_s] = reflection
|
110
|
+
end
|
111
|
+
|
112
|
+
def add_has_many(fake_record_class, name, options = nil)
|
113
|
+
reflection = Reflection.new(:has_many, name, options)
|
114
|
+
reflection.define_accessor_on(fake_record_class)
|
115
|
+
@has_many[name.to_s] = reflection
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class Reflection
|
120
|
+
attr_reader :reader_method_name, :writer_method_name
|
121
|
+
|
122
|
+
# ActiveRecord methods
|
123
|
+
attr_reader :name, :macro, :options
|
124
|
+
attr_reader :foreign_key, :foreign_type
|
125
|
+
attr_reader :association_class
|
126
|
+
|
127
|
+
BELONGS_TO_ASSOC_PROC = proc do |r|
|
128
|
+
r.options[:polymorphic] ? PolymorphicBelongsToAssociation : BelongsToAssociation
|
129
|
+
end
|
130
|
+
ASSOCIATION_CLASS = {
|
131
|
+
:belongs_to => BELONGS_TO_ASSOC_PROC,
|
132
|
+
:has_one => proc{ HasOneAssociation },
|
133
|
+
:has_many => proc{ HasManyAssociation }
|
134
|
+
}.freeze
|
135
|
+
|
136
|
+
def initialize(macro, name, options = nil)
|
137
|
+
@macro = macro.to_sym
|
138
|
+
@name = name
|
139
|
+
@options = options || {}
|
140
|
+
|
141
|
+
@class_name = @options[:class_name]
|
142
|
+
@foreign_key = @options[:foreign_key]
|
143
|
+
@foreign_type = @options[:foreign_type]
|
144
|
+
|
145
|
+
@reader_method_name = name.to_s
|
146
|
+
@writer_method_name = "#{@reader_method_name}="
|
147
|
+
@association_class = ASSOCIATION_CLASS[@macro].call(self)
|
148
|
+
end
|
149
|
+
|
150
|
+
# ActiveRecord method
|
151
|
+
def klass
|
152
|
+
@klass ||= (@class_name.to_s.constantize if @class_name)
|
153
|
+
end
|
154
|
+
|
155
|
+
def define_accessor_on(fake_record_class)
|
156
|
+
reflection = self
|
157
|
+
fake_record_class.class_eval do
|
158
|
+
|
159
|
+
define_method(reflection.reader_method_name) do
|
160
|
+
self.association(reflection.name).read
|
161
|
+
end
|
162
|
+
define_method(reflection.writer_method_name) do |value|
|
163
|
+
self.association(reflection.name).write(value)
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def <=>(other)
|
170
|
+
if other.kind_of?(self.class)
|
171
|
+
[ self.macro, self.options[:polymorphic], self.name ].map(&:to_s) <=>
|
172
|
+
[ other.macro, other.options[:polymorphic], other.name ].map(&:to_s)
|
173
|
+
else
|
174
|
+
super
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
class Association
|
180
|
+
# ActiveRecord method
|
181
|
+
attr_reader :owner, :reflection
|
182
|
+
|
183
|
+
def initialize(owner, reflection)
|
184
|
+
@owner = owner
|
185
|
+
@reflection = reflection
|
186
|
+
@ivar_name = "@#{@reflection.name}"
|
187
|
+
end
|
188
|
+
|
189
|
+
def read; raise NotImplementedError; end
|
190
|
+
def write(value); raise NotImplementedError; end
|
191
|
+
|
192
|
+
# ActiveRecord method
|
193
|
+
def klass
|
194
|
+
self.reflection.klass
|
195
|
+
end
|
196
|
+
|
197
|
+
def <=>(other)
|
198
|
+
other.kind_of?(Association) ? self.reflection <=> other.reflection : super
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
class OneToOneAssociation < Association
|
203
|
+
def read
|
204
|
+
@owner.instance_variable_get(@ivar_name)
|
205
|
+
end
|
206
|
+
|
207
|
+
def write(value)
|
208
|
+
@owner.instance_variable_set(@ivar_name, value)
|
209
|
+
write_attributes(value || NULL_RECORD)
|
210
|
+
value
|
211
|
+
end
|
212
|
+
|
213
|
+
private
|
214
|
+
|
215
|
+
def write_attributes(associated_fake_record); end
|
216
|
+
|
217
|
+
NullRecord = Struct.new(:id, :class)
|
218
|
+
NullClass = Struct.new(:name)
|
219
|
+
NULL_RECORD = NullRecord.new(nil, NullClass.new)
|
220
|
+
end
|
221
|
+
|
222
|
+
class OneToManyAssociation < Association
|
223
|
+
def read
|
224
|
+
@owner.instance_variable_get(@ivar_name) ||
|
225
|
+
@owner.instance_variable_set(@ivar_name, [])
|
226
|
+
end
|
227
|
+
|
228
|
+
def write(value)
|
229
|
+
@owner.instance_variable_set(@ivar_name, [*value].compact)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
class BelongsToAssociation < OneToOneAssociation
|
234
|
+
def write_attributes(associated_fake_record)
|
235
|
+
super
|
236
|
+
associated_id = associated_fake_record.id
|
237
|
+
@owner.send("#{self.reflection.foreign_key}=", associated_id)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
class PolymorphicBelongsToAssociation < BelongsToAssociation
|
242
|
+
def write_attributes(associated_fake_record)
|
243
|
+
super
|
244
|
+
associated_type = associated_fake_record.class.name
|
245
|
+
@owner.send("#{self.reflection.foreign_type}=", associated_type)
|
246
|
+
end
|
247
|
+
|
248
|
+
def klass
|
249
|
+
class_name = @owner.send(self.reflection.foreign_type)
|
250
|
+
class_name.constantize if class_name
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
HasOneAssociation = OneToOneAssociation
|
255
|
+
HasManyAssociation = OneToManyAssociation
|
256
|
+
|
257
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'much-plugin'
|
2
|
+
|
3
|
+
module MR; end
|
4
|
+
module MR::FakeRecord
|
5
|
+
|
6
|
+
module Attributes
|
7
|
+
include MuchPlugin
|
8
|
+
|
9
|
+
plugin_included do
|
10
|
+
extend ClassMethods
|
11
|
+
include InstanceMethods
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
|
16
|
+
def attributes
|
17
|
+
@attributes ||= MR::FakeRecord::AttributeSet.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def attribute(name, type, options = nil)
|
21
|
+
self.attributes.add(self, name, type, options)
|
22
|
+
end
|
23
|
+
|
24
|
+
# ActiveRecord methods
|
25
|
+
|
26
|
+
def columns
|
27
|
+
self.attributes.to_a
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
module InstanceMethods
|
33
|
+
|
34
|
+
attr_writer :saved_attributes
|
35
|
+
|
36
|
+
def saved_attributes
|
37
|
+
@saved_attributes ||= {}
|
38
|
+
end
|
39
|
+
|
40
|
+
# ActiveRecord methods
|
41
|
+
|
42
|
+
def attributes
|
43
|
+
self.class.attributes.read_all(self)
|
44
|
+
end
|
45
|
+
|
46
|
+
def attributes=(new_attributes)
|
47
|
+
return unless new_attributes.is_a?(Hash)
|
48
|
+
self.class.attributes.batch_write(new_attributes, self)
|
49
|
+
end
|
50
|
+
|
51
|
+
def column_for_attribute(name)
|
52
|
+
self.class.attributes.find(name)
|
53
|
+
rescue NoAttributeError
|
54
|
+
return nil
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
class AttributeSet
|
62
|
+
include Enumerable
|
63
|
+
|
64
|
+
def initialize
|
65
|
+
@attributes = {}
|
66
|
+
end
|
67
|
+
|
68
|
+
def find(name)
|
69
|
+
@attributes[name.to_s] || raise(NoAttributeError.new(name))
|
70
|
+
end
|
71
|
+
|
72
|
+
def each(&block)
|
73
|
+
@attributes.values.each(&block)
|
74
|
+
end
|
75
|
+
|
76
|
+
def read_all(record)
|
77
|
+
@attributes.values.inject({}) do |h, attribute|
|
78
|
+
h.merge(attribute.name => attribute.read(record))
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def batch_write(new_attributes, record)
|
83
|
+
new_attributes.each{ |name, value| find(name).write(value, record) }
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_a
|
87
|
+
@attributes.values.sort
|
88
|
+
end
|
89
|
+
|
90
|
+
def add(record_class, name, type, options = nil)
|
91
|
+
attribute = Attribute.new(name, type, options)
|
92
|
+
attribute.define_on(record_class)
|
93
|
+
@attributes[name.to_s] = attribute
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
class Attribute
|
99
|
+
attr_reader :name, :type
|
100
|
+
attr_reader :reader_method_name, :writer_method_name
|
101
|
+
attr_reader :was_method_name, :changed_method_name
|
102
|
+
|
103
|
+
# ActiveRecord methods
|
104
|
+
attr_reader :primary, :null
|
105
|
+
|
106
|
+
def initialize(name, type, options = nil)
|
107
|
+
options ||= {}
|
108
|
+
@name = name.to_s
|
109
|
+
@type = type.to_sym
|
110
|
+
@primary = (@type == :primary_key)
|
111
|
+
@null = options.key?(:null) ? !!options[:null] : true
|
112
|
+
|
113
|
+
@reader_method_name = @name
|
114
|
+
@writer_method_name = "#{@reader_method_name}="
|
115
|
+
@was_method_name = "#{@reader_method_name}_was"
|
116
|
+
@changed_method_name = "#{@reader_method_name}_changed?"
|
117
|
+
end
|
118
|
+
|
119
|
+
def read(record)
|
120
|
+
record.send(@reader_method_name)
|
121
|
+
end
|
122
|
+
|
123
|
+
def write(value, record)
|
124
|
+
record.send(@writer_method_name, value)
|
125
|
+
end
|
126
|
+
|
127
|
+
def was(record)
|
128
|
+
record.saved_attributes[@name]
|
129
|
+
end
|
130
|
+
|
131
|
+
def changed?(record)
|
132
|
+
read(record) != was(record)
|
133
|
+
end
|
134
|
+
|
135
|
+
def ==(other)
|
136
|
+
self.name == other.name &&
|
137
|
+
self.type == other.type &&
|
138
|
+
self.null == other.null
|
139
|
+
end
|
140
|
+
|
141
|
+
def <=>(other)
|
142
|
+
self.name <=> other.name
|
143
|
+
end
|
144
|
+
|
145
|
+
def define_on(record_class)
|
146
|
+
attribute = self
|
147
|
+
record_class.class_eval do
|
148
|
+
|
149
|
+
attr_accessor attribute.reader_method_name
|
150
|
+
define_method(attribute.was_method_name) do
|
151
|
+
attribute.was(self)
|
152
|
+
end
|
153
|
+
define_method(attribute.changed_method_name) do
|
154
|
+
attribute.changed?(self)
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
class NoAttributeError < RuntimeError
|
163
|
+
def initialize(attr_name)
|
164
|
+
super "the '#{attr_name}' attribute doesn't exist"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|