passiverecord 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +6 -0
- data/Manifest.txt +12 -0
- data/README.txt +79 -0
- data/Rakefile +19 -0
- data/lib/passive_record.rb +17 -0
- data/lib/passive_record/associations.rb +276 -0
- data/lib/passive_record/base.rb +126 -0
- data/lib/passive_record/schema.rb +25 -0
- data/lib/passive_record/version.rb +9 -0
- data/test/test_associations.rb +96 -0
- data/test/test_base.rb +87 -0
- data/test/test_helper.rb +2 -0
- metadata +79 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
README.txt
|
4
|
+
Rakefile
|
5
|
+
lib/passive_record.rb
|
6
|
+
lib/passive_record/associations.rb
|
7
|
+
lib/passive_record/base.rb
|
8
|
+
lib/passive_record/schema.rb
|
9
|
+
lib/passive_record/version.rb
|
10
|
+
test/test_associations.rb
|
11
|
+
test/test_base.rb
|
12
|
+
test/test_helper.rb
|
data/README.txt
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
= PassiveRecord
|
2
|
+
by Jason L Perry (http://paint.itred.org)
|
3
|
+
|
4
|
+
== DESCRIPTION:
|
5
|
+
|
6
|
+
A replacement for ActiveRecord when you just need to model
|
7
|
+
a few unchanging objects, and don't necessarily need a full blown
|
8
|
+
ActiveRecord class and table in the database.
|
9
|
+
|
10
|
+
== FEATURES:
|
11
|
+
|
12
|
+
* some basic ActiveRecord style finders
|
13
|
+
* dynamic attribute based finders, supporting basic comparisons as well
|
14
|
+
regular expressions and ranges
|
15
|
+
* some integrated ActiveRecord associactions, ie: ActiveRecord#belongs_to(:passive_record)
|
16
|
+
PassiveRecord#has_many(:active_records) (excluding has_many :through)
|
17
|
+
|
18
|
+
== TODOs:
|
19
|
+
|
20
|
+
* has_many :through
|
21
|
+
* extend find options like :conditions, :order
|
22
|
+
* find(:first) (depends on :order being implemented)
|
23
|
+
* better documentation of the code
|
24
|
+
* fix warnings in tests
|
25
|
+
|
26
|
+
== SYNOPSIS:
|
27
|
+
|
28
|
+
class Continent < PassiveRecord::Base
|
29
|
+
has_many :countries # => an ActiveRecord class
|
30
|
+
|
31
|
+
schema :name => String, :size => Integer, :population => Integer
|
32
|
+
|
33
|
+
create :name => "Africa", :size => 30370000, :population => 890000000
|
34
|
+
create :name => "Antarctica", :size => 13720000, :population => 1000
|
35
|
+
create :name => "Australia", :size => 7600000, :population => 20000000
|
36
|
+
create :name => "Eurasia", :size => 53990000, :population => 4510000000
|
37
|
+
create :name => "North America", :size => 24490000, :population => 515000000
|
38
|
+
create :name => "South America", :size => 17840000, :population => 371000000
|
39
|
+
end
|
40
|
+
|
41
|
+
Continent.find(1) # => Africa
|
42
|
+
Continent.find_by_name("Africa") # => Yes, also Africa
|
43
|
+
Continent.find_by_name_and_size(/America/, 17840000) # => South America
|
44
|
+
Continent.find_all_by_population(1000..20000000) # => Antarctica and Australia
|
45
|
+
Continent.find(:all) # => All 6 (though not in any particular order, yet)
|
46
|
+
|
47
|
+
== REQUIREMENTS:
|
48
|
+
|
49
|
+
* activerecord
|
50
|
+
|
51
|
+
== INSTALL:
|
52
|
+
|
53
|
+
* gem install passiverecord
|
54
|
+
* require "passive_record"
|
55
|
+
|
56
|
+
== LICENSE:
|
57
|
+
|
58
|
+
(The MIT License)
|
59
|
+
|
60
|
+
Copyright (c) 2007 Jason L Perry (Paint.itRed)
|
61
|
+
|
62
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
63
|
+
a copy of this software and associated documentation files (the
|
64
|
+
'Software'), to deal in the Software without restriction, including
|
65
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
66
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
67
|
+
permit persons to whom the Software is furnished to do so, subject to
|
68
|
+
the following conditions:
|
69
|
+
|
70
|
+
The above copyright notice and this permission notice shall be
|
71
|
+
included in all copies or substantial portions of the Software.
|
72
|
+
|
73
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
74
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
75
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
76
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
77
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
78
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
79
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + "/lib")
|
2
|
+
require 'rubygems'
|
3
|
+
require 'hoe'
|
4
|
+
|
5
|
+
require 'passive_record/version'
|
6
|
+
|
7
|
+
Hoe.new('passiverecord', PassiveRecord::VERSION::STRING) do |p|
|
8
|
+
p.rubyforge_name = 'paintitred'
|
9
|
+
p.author = 'Jason L Perry'
|
10
|
+
p.email = 'jasper@ambethia.com'
|
11
|
+
p.summary = 'Pacifying overactive records'
|
12
|
+
p.description = 'Pacifying overactive records'
|
13
|
+
p.url = "http://code.itred.org/projects/passive-record"
|
14
|
+
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
15
|
+
p.extra_deps = [['activerecord', '>= 1.15.3']]
|
16
|
+
p.remote_rdoc_dir = "passive_record"
|
17
|
+
end
|
18
|
+
|
19
|
+
Hoe.send :define_method, :extra_deps, proc { @extra_deps.reject! { |x| Array(x).first == 'hoe' };@extra_deps}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'active_record'
|
5
|
+
rescue LoadError => e
|
6
|
+
require 'rubygems'
|
7
|
+
require 'active_record'
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'passive_record/base'
|
11
|
+
require 'passive_record/schema'
|
12
|
+
require 'passive_record/associations'
|
13
|
+
|
14
|
+
PassiveRecord::Base.class_eval do
|
15
|
+
include PassiveRecord::Schema
|
16
|
+
include PassiveRecord::Associations
|
17
|
+
end
|
@@ -0,0 +1,276 @@
|
|
1
|
+
# Currently this is a big hack job of code from active record
|
2
|
+
# TODO: Trim this down, and test it thoroughly
|
3
|
+
module PassiveRecord
|
4
|
+
module Associations
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
|
9
|
+
ActiveRecord::Callbacks::CALLBACKS.each do |method|
|
10
|
+
base.class_eval <<-"end_eval"
|
11
|
+
def self.#{method}(*callbacks, &block)
|
12
|
+
callbacks << block if block_given?
|
13
|
+
write_inheritable_array(#{method.to_sym.inspect}, callbacks)
|
14
|
+
end
|
15
|
+
end_eval
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def quoted_id #:nodoc:
|
20
|
+
ActiveRecord::Base.quote_value(self.key)
|
21
|
+
end
|
22
|
+
|
23
|
+
def attribute_present?(attribute)
|
24
|
+
value = self.respond_to?(attribute) ? self.send(attribute) : nil
|
25
|
+
!value.blank? or value == 0
|
26
|
+
end
|
27
|
+
|
28
|
+
def read_attribute(attribute)
|
29
|
+
self.send(attribute)
|
30
|
+
end
|
31
|
+
|
32
|
+
def new_record?
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
module ClassMethods
|
37
|
+
|
38
|
+
def has_many(association_id, options = {}, &extension)
|
39
|
+
reflection = create_has_many_reflection(association_id, options, &extension)
|
40
|
+
|
41
|
+
ActiveRecord::Base.send(:configure_dependency_for_has_many, reflection)
|
42
|
+
|
43
|
+
if options[:through]
|
44
|
+
collection_reader_method(reflection, ActiveRecord::Associations::HasManyThroughAssociation)
|
45
|
+
else
|
46
|
+
add_multiple_associated_save_callbacks(reflection.name)
|
47
|
+
add_association_callbacks(reflection.name, reflection.options)
|
48
|
+
collection_accessor_methods(reflection, ActiveRecord::Associations::HasManyAssociation)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def has_one(association_id, options = {})
|
53
|
+
reflection = create_has_one_reflection(association_id, options)
|
54
|
+
|
55
|
+
module_eval do
|
56
|
+
after_save <<-EOF
|
57
|
+
association = instance_variable_get("@#{reflection.name}")
|
58
|
+
if !association.nil? && (new_record? || association.new_record? || association["#{reflection.primary_key_name}"] != id)
|
59
|
+
association["#{reflection.primary_key_name}"] = id
|
60
|
+
association.save(true)
|
61
|
+
end
|
62
|
+
EOF
|
63
|
+
end
|
64
|
+
|
65
|
+
association_accessor_methods(reflection, ActiveRecord::Associations::HasOneAssociation)
|
66
|
+
association_constructor_method(:build, reflection, ActiveRecord::Associations::HasOneAssociation)
|
67
|
+
association_constructor_method(:create, reflection, ActiveRecord::Associations::HasOneAssociation)
|
68
|
+
|
69
|
+
ActiveRecord::Base.send(:configure_dependency_for_has_one, reflection)
|
70
|
+
end
|
71
|
+
|
72
|
+
def create_has_many_reflection(association_id, options, &extension)
|
73
|
+
options.assert_valid_keys(
|
74
|
+
:class_name, :table_name, :foreign_key,
|
75
|
+
:exclusively_dependent, :dependent,
|
76
|
+
:select, :conditions, :include, :order, :group, :limit, :offset,
|
77
|
+
:as, :through, :source, :source_type,
|
78
|
+
:uniq,
|
79
|
+
:finder_sql, :counter_sql,
|
80
|
+
:before_add, :after_add, :before_remove, :after_remove,
|
81
|
+
:extend
|
82
|
+
)
|
83
|
+
|
84
|
+
options[:extend] = create_extension_module(association_id, extension) if block_given?
|
85
|
+
|
86
|
+
create_reflection(:has_many, association_id, options, self)
|
87
|
+
end
|
88
|
+
|
89
|
+
def create_has_one_reflection(association_id, options)
|
90
|
+
options.assert_valid_keys(
|
91
|
+
:class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as
|
92
|
+
)
|
93
|
+
|
94
|
+
create_reflection(:has_one, association_id, options, self)
|
95
|
+
end
|
96
|
+
|
97
|
+
def create_reflection(macro, name, options, active_record)
|
98
|
+
case macro
|
99
|
+
when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
|
100
|
+
reflection = ActiveRecord::Reflection::AssociationReflection.new(macro, name, options, active_record)
|
101
|
+
when :composed_of
|
102
|
+
reflection = AggregateReflection.new(macro, name, options, active_record)
|
103
|
+
end
|
104
|
+
write_inheritable_hash :reflections, name => reflection
|
105
|
+
reflection
|
106
|
+
end
|
107
|
+
|
108
|
+
def add_multiple_associated_save_callbacks(association_name)
|
109
|
+
method_name = "validate_associated_records_for_#{association_name}".to_sym
|
110
|
+
define_method(method_name) do
|
111
|
+
association = instance_variable_get("@#{association_name}")
|
112
|
+
if association.respond_to?(:loaded?)
|
113
|
+
if new_record?
|
114
|
+
association
|
115
|
+
else
|
116
|
+
association.select { |record| record.new_record? }
|
117
|
+
end.each do |record|
|
118
|
+
errors.add "#{association_name}" unless record.valid?
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
validate method_name
|
124
|
+
before_save("@new_record_before_save = new_record?; true")
|
125
|
+
|
126
|
+
after_callback = <<-end_eval
|
127
|
+
association = instance_variable_get("@#{association_name}")
|
128
|
+
|
129
|
+
if association.respond_to?(:loaded?)
|
130
|
+
if @new_record_before_save
|
131
|
+
records_to_save = association
|
132
|
+
else
|
133
|
+
records_to_save = association.select { |record| record.new_record? }
|
134
|
+
end
|
135
|
+
records_to_save.each { |record| association.send(:insert_record, record) }
|
136
|
+
association.send(:construct_sql) # reconstruct the SQL queries now that we know the owner's id
|
137
|
+
end
|
138
|
+
end_eval
|
139
|
+
|
140
|
+
# Doesn't use after_save as that would save associations added in after_create/after_update twice
|
141
|
+
after_create(after_callback)
|
142
|
+
after_update(after_callback)
|
143
|
+
end
|
144
|
+
|
145
|
+
def add_association_callbacks(association_name, options)
|
146
|
+
callbacks = %w(before_add after_add before_remove after_remove)
|
147
|
+
callbacks.each do |callback_name|
|
148
|
+
full_callback_name = "#{callback_name}_for_#{association_name}"
|
149
|
+
defined_callbacks = options[callback_name.to_sym]
|
150
|
+
if options.has_key?(callback_name.to_sym)
|
151
|
+
class_inheritable_reader full_callback_name.to_sym
|
152
|
+
write_inheritable_array(full_callback_name.to_sym, [defined_callbacks].flatten)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def collection_accessor_methods(reflection, association_proxy_class)
|
158
|
+
collection_reader_method(reflection, association_proxy_class)
|
159
|
+
|
160
|
+
define_method("#{reflection.name}=") do |new_value|
|
161
|
+
# Loads proxy class instance (defined in collection_reader_method) if not already loaded
|
162
|
+
association = send(reflection.name)
|
163
|
+
association.replace(new_value)
|
164
|
+
association
|
165
|
+
end
|
166
|
+
|
167
|
+
define_method("#{reflection.name.to_s.singularize}_ids") do
|
168
|
+
send(reflection.name).map(&:id)
|
169
|
+
end
|
170
|
+
|
171
|
+
define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
|
172
|
+
ids = (new_value || []).reject { |nid| nid.blank? }
|
173
|
+
send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def collection_reader_method(reflection, association_proxy_class)
|
178
|
+
define_method(reflection.name) do |*params|
|
179
|
+
force_reload = params.first unless params.empty?
|
180
|
+
association = instance_variable_get("@#{reflection.name}")
|
181
|
+
|
182
|
+
unless association.respond_to?(:loaded?)
|
183
|
+
association = association_proxy_class.new(self, reflection)
|
184
|
+
instance_variable_set("@#{reflection.name}", association)
|
185
|
+
end
|
186
|
+
|
187
|
+
association.reload if force_reload
|
188
|
+
|
189
|
+
association
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def association_accessor_methods(reflection, association_proxy_class)
|
194
|
+
define_method(reflection.name) do |*params|
|
195
|
+
force_reload = params.first unless params.empty?
|
196
|
+
association = instance_variable_get("@#{reflection.name}")
|
197
|
+
|
198
|
+
if association.nil? || force_reload
|
199
|
+
association = association_proxy_class.new(self, reflection)
|
200
|
+
retval = association.reload
|
201
|
+
if retval.nil? and association_proxy_class == BelongsToAssociation
|
202
|
+
instance_variable_set("@#{reflection.name}", nil)
|
203
|
+
return nil
|
204
|
+
end
|
205
|
+
instance_variable_set("@#{reflection.name}", association)
|
206
|
+
end
|
207
|
+
|
208
|
+
association.target.nil? ? nil : association
|
209
|
+
end
|
210
|
+
|
211
|
+
define_method("#{reflection.name}=") do |new_value|
|
212
|
+
association = instance_variable_get("@#{reflection.name}")
|
213
|
+
if association.nil?
|
214
|
+
association = association_proxy_class.new(self, reflection)
|
215
|
+
end
|
216
|
+
|
217
|
+
association.replace(new_value)
|
218
|
+
|
219
|
+
unless new_value.nil?
|
220
|
+
instance_variable_set("@#{reflection.name}", association)
|
221
|
+
else
|
222
|
+
instance_variable_set("@#{reflection.name}", nil)
|
223
|
+
return nil
|
224
|
+
end
|
225
|
+
|
226
|
+
association
|
227
|
+
end
|
228
|
+
|
229
|
+
define_method("set_#{reflection.name}_target") do |target|
|
230
|
+
return if target.nil? and association_proxy_class == BelongsToAssociation
|
231
|
+
association = association_proxy_class.new(self, reflection)
|
232
|
+
association.target = target
|
233
|
+
instance_variable_set("@#{reflection.name}", association)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def association_constructor_method(constructor, reflection, association_proxy_class)
|
238
|
+
define_method("#{constructor}_#{reflection.name}") do |*params|
|
239
|
+
attributees = params.first unless params.empty?
|
240
|
+
replace_existing = params[1].nil? ? true : params[1]
|
241
|
+
association = instance_variable_get("@#{reflection.name}")
|
242
|
+
|
243
|
+
if association.nil?
|
244
|
+
association = association_proxy_class.new(self, reflection)
|
245
|
+
instance_variable_set("@#{reflection.name}", association)
|
246
|
+
end
|
247
|
+
|
248
|
+
if association_proxy_class == HasOneAssociation
|
249
|
+
association.send(constructor, attributees, replace_existing)
|
250
|
+
else
|
251
|
+
association.send(constructor, attributees)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def validate(*methods, &block)
|
257
|
+
methods << block if block_given?
|
258
|
+
write_inheritable_set(:validate, methods)
|
259
|
+
end
|
260
|
+
|
261
|
+
def write_inheritable_set(key, methods)
|
262
|
+
existing_methods = read_inheritable_attribute(key) || []
|
263
|
+
write_inheritable_attribute(key, methods | existing_methods)
|
264
|
+
end
|
265
|
+
|
266
|
+
def compute_type(*args)
|
267
|
+
ActiveRecord::Base.send(:compute_type, *args)
|
268
|
+
end
|
269
|
+
|
270
|
+
def reflect_on_association(*args)
|
271
|
+
ActiveRecord::Base.send(:reflect_on_association, *args)
|
272
|
+
end
|
273
|
+
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module PassiveRecord
|
2
|
+
class PassiveRecordError < StandardError; end
|
3
|
+
class RecordNotFound < PassiveRecordError; end
|
4
|
+
|
5
|
+
class Base
|
6
|
+
@@instances = {}
|
7
|
+
|
8
|
+
attr_accessor :attributes, :key
|
9
|
+
|
10
|
+
alias :id :key
|
11
|
+
|
12
|
+
def initialize(attributes = {})
|
13
|
+
@attributes = attributes
|
14
|
+
yield self if block_given?
|
15
|
+
end
|
16
|
+
|
17
|
+
def [](attribute_name)
|
18
|
+
self.attributes[attribute_name]
|
19
|
+
end
|
20
|
+
|
21
|
+
def []=(attribute_name, value)
|
22
|
+
self.attributes[attribute_name] = value
|
23
|
+
end
|
24
|
+
|
25
|
+
def ==(comparison_object)
|
26
|
+
comparison_object.equal?(self) ||
|
27
|
+
(comparison_object.instance_of?(self.class) &&
|
28
|
+
comparison_object.id == id &&
|
29
|
+
!comparison_object.new_record?)
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
class << self
|
34
|
+
|
35
|
+
def create(*args)
|
36
|
+
attributes = extract_options!(args)
|
37
|
+
key = args.first || @@instances.size+1
|
38
|
+
instance = self.new(attributes)
|
39
|
+
instance.key = key
|
40
|
+
@@instances[key] = instance
|
41
|
+
return key
|
42
|
+
end
|
43
|
+
|
44
|
+
private :create
|
45
|
+
|
46
|
+
def find(*args)
|
47
|
+
options = extract_options!(args)
|
48
|
+
args.first == :all ? find_every : find_from_keys(args)
|
49
|
+
end
|
50
|
+
|
51
|
+
def count
|
52
|
+
find_every.size
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
# def find_initial(options)
|
58
|
+
# end
|
59
|
+
|
60
|
+
def find_every
|
61
|
+
@@instances.select { |key, value| value.is_a?(self) }.map {|its| its.last}
|
62
|
+
end
|
63
|
+
|
64
|
+
def find_from_keys(keys, options = {})
|
65
|
+
|
66
|
+
expects_array = keys.first.kind_of?(Array)
|
67
|
+
return keys.first if expects_array && keys.first.empty?
|
68
|
+
keys = keys.flatten.compact.uniq
|
69
|
+
|
70
|
+
case keys.size
|
71
|
+
when 0
|
72
|
+
raise RecordNotFound, "Couldn't find #{name} without a key"
|
73
|
+
when 1
|
74
|
+
result = find_one(keys.first)
|
75
|
+
expects_array ? [ result ] : result
|
76
|
+
else
|
77
|
+
find_some(keys)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def find_one(_key)
|
82
|
+
values = @@instances.select { |key, value| _key == key && value.is_a?(self) }.map {|its| its.last}
|
83
|
+
unless values.empty?
|
84
|
+
return values.first
|
85
|
+
else
|
86
|
+
raise RecordNotFound, "Couldn't find #{name} with the given key."
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def find_some(keys)
|
91
|
+
values = @@instances.select { |key, value| (keys).include?(key) && value.is_a?(self) }.map {|its| its.last}
|
92
|
+
end
|
93
|
+
|
94
|
+
def extract_options!(args) #:nodoc:
|
95
|
+
args.last.is_a?(Hash) ? args.pop : {}
|
96
|
+
end
|
97
|
+
|
98
|
+
# TODO: clean this mess up
|
99
|
+
def method_missing(method_id, *arguments)
|
100
|
+
if match = /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(method_id.to_s)
|
101
|
+
expects_array = match.captures.first == 'all_by'
|
102
|
+
attribute_names = match.captures.last.split('_and_')
|
103
|
+
# super unless all_attributes_exists?(attribute_names)
|
104
|
+
attributes = {}
|
105
|
+
attribute_names.each_with_index { |name, idx| attributes[name] = arguments[idx] }
|
106
|
+
expressions = []
|
107
|
+
attributes.each_pair { |key, value| expressions << attribute_expression("value.#{key}", value) }
|
108
|
+
results = @@instances.select { |key, value| eval expressions.join(" && ") }.map {|its| its.last}
|
109
|
+
return expects_array ? results : results.pop
|
110
|
+
else
|
111
|
+
super
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def attribute_expression(name, argument)
|
116
|
+
case argument
|
117
|
+
when Regexp then "#{name} =~ /#{argument}/"
|
118
|
+
when Range then "(#{argument}).include?(#{name})"
|
119
|
+
when String then "#{name} == \"#{argument}\""
|
120
|
+
else "#{name} == #{argument}"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module PassiveRecord
|
2
|
+
module Schema
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
# Define the schema for your model, this also adds attribute accessors for schema columns
|
11
|
+
def schema(definition = {})
|
12
|
+
@@schema = definition
|
13
|
+
definition.keys.each do |attribute_name|
|
14
|
+
define_method("#{attribute_name}") do
|
15
|
+
self[attribute_name]
|
16
|
+
end
|
17
|
+
define_method("#{attribute_name}=") do |new_value|
|
18
|
+
self[attribute_name] = new_value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
|
3
|
+
class Room < PassiveRecord::Base
|
4
|
+
has_many :furniture, :order => :name
|
5
|
+
has_one :light_fixture, :class_name => "Furniture"
|
6
|
+
|
7
|
+
has_many :ins, :class_name => "Door", :foreign_key => "outside_id"
|
8
|
+
has_many :outs, :class_name => "Door", :foreign_key => "inside_id"
|
9
|
+
|
10
|
+
has_many :exits, :through => :outs
|
11
|
+
has_many :entrances, :through => :ins
|
12
|
+
|
13
|
+
schema :name => String
|
14
|
+
|
15
|
+
create :name => "Family Room"
|
16
|
+
create :name => "Kitchen"
|
17
|
+
create :name => "Bedroom"
|
18
|
+
create :name => "Restroom"
|
19
|
+
create :name => "Office"
|
20
|
+
end
|
21
|
+
|
22
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
23
|
+
ActiveRecord::Base.connection.create_table "furniture", :force => true do |t|
|
24
|
+
t.column :name, :string
|
25
|
+
t.column :room_id, :integer
|
26
|
+
end
|
27
|
+
|
28
|
+
ActiveRecord::Base.connection.create_table "doors", :force => true do |t|
|
29
|
+
t.column :inside_id, :integer
|
30
|
+
t.column :outside_id, :integer
|
31
|
+
end
|
32
|
+
|
33
|
+
Inflector.inflections { |inflect| inflect.uncountable %w( furniture )}
|
34
|
+
|
35
|
+
class Furniture < ActiveRecord::Base
|
36
|
+
belongs_to :room
|
37
|
+
end
|
38
|
+
|
39
|
+
class Door < ActiveRecord::Base
|
40
|
+
belongs_to :inside, :class_name => "Room", :foreign_key => "inside_id"
|
41
|
+
belongs_to :outside, :class_name => "Room", :foreign_key => "outside_id"
|
42
|
+
end
|
43
|
+
|
44
|
+
class PassiveRecord::AssociationsTest < Test::Unit::TestCase
|
45
|
+
|
46
|
+
# some "fixtures"
|
47
|
+
def setup
|
48
|
+
Furniture.create :name => "Couch", :room_id => Room.find_by_name("Family Room").id
|
49
|
+
Furniture.create :name => "Ottoman", :room_id => Room.find_by_name("Family Room").id
|
50
|
+
Furniture.create :name => "Ott-lite", :room_id => Room.find_by_name("Office").id
|
51
|
+
|
52
|
+
Door.create :inside_id => Room.find_by_name("Office").id,
|
53
|
+
:outside_id => Room.find_by_name("Family Room").id
|
54
|
+
|
55
|
+
Door.create :inside_id => Room.find_by_name("Restroom").id,
|
56
|
+
:outside_id => Room.find_by_name("Family Room").id
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_should_have_many
|
60
|
+
furniture = [
|
61
|
+
Furniture.find_by_name("Couch"),
|
62
|
+
Furniture.find_by_name("Ottoman")
|
63
|
+
]
|
64
|
+
room = Room.find_by_name("Family Room")
|
65
|
+
assert_equal furniture, room.furniture
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_should_have_many_through
|
69
|
+
rooms = [
|
70
|
+
Room.find_by_name("Office"),
|
71
|
+
Room.find_by_name("Restroom")
|
72
|
+
]
|
73
|
+
|
74
|
+
room = Room.find_by_name("Family Room")
|
75
|
+
|
76
|
+
# assert_equal room, room.exits
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_should_have_one
|
80
|
+
lamp = Furniture.find_by_name("Ott-lite")
|
81
|
+
room = Room.find_by_name("Office")
|
82
|
+
assert_equal lamp, room.light_fixture
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_should_belong_to
|
86
|
+
lamp = Furniture.find_by_name("Ott-lite")
|
87
|
+
room = Room.find_by_name("Office")
|
88
|
+
|
89
|
+
assert_equal room, lamp.room
|
90
|
+
end
|
91
|
+
|
92
|
+
def teardown
|
93
|
+
Furniture.delete_all
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
data/test/test_base.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
|
3
|
+
class Continent < PassiveRecord::Base
|
4
|
+
schema :name => String, :size => Integer, :population => Integer
|
5
|
+
end
|
6
|
+
|
7
|
+
class PassiveRecord::BaseTest < Test::Unit::TestCase
|
8
|
+
|
9
|
+
def setup
|
10
|
+
# Add the geographic 6 continents
|
11
|
+
Continent.send :create, :name => "Africa", :size => 30370000, :population => 890000000
|
12
|
+
Continent.send :create, :name => "Antarctica", :size => 13720000, :population => 1000
|
13
|
+
Continent.send :create, :name => "Australia", :size => 7600000, :population => 20000000
|
14
|
+
Continent.send :create, :name => "Eurasia", :size => 53990000, :population => 4510000000
|
15
|
+
Continent.send :create, :name => "North America", :size => 24490000, :population => 515000000
|
16
|
+
Continent.send :create, :name => "South America", :size => 17840000, :population => 371000000
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_should_create_instance
|
20
|
+
assert_equal 7, Continent.send(:create, :name => "Atlantis", :size => 0, :population => 0)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_should_create_instance_with_manual_key
|
24
|
+
assert_equal "ATL", Continent.send(:create, "ATL", :name => "Atlantis", :size => 0, :population => 0)
|
25
|
+
assert Continent.find("ATL")
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_should_count_instances
|
29
|
+
assert_equal 6, Continent.count
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_should_find_one_by_key
|
33
|
+
assert Continent.find(1).is_a?(Continent)
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_should_find_one_by_key_as_array
|
37
|
+
assert Continent.find([6]).is_a?(Array)
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_should_find_some_by_key
|
41
|
+
assert_equal 2, Continent.find(1,2).size
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_should_find_some_by_key_as_array
|
45
|
+
assert_equal 2, Continent.find([5,6]).size
|
46
|
+
assert_equal 2, Continent.find([5,6,7]).size
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_should_find_all
|
50
|
+
assert_equal 6, Continent.find(:all).size
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_should_get_attributes
|
54
|
+
assert_equal "Africa", Continent.find(1).name
|
55
|
+
assert_equal 1000, Continent.find(2).population
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_should_set_attributes
|
59
|
+
assert Continent.find(1).name = "Motherland"
|
60
|
+
assert_equal "Motherland", Continent.find(1).name
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_should_find_by_attribute
|
64
|
+
assert_equal Continent.find(1), Continent.find_by_name("Africa")
|
65
|
+
assert_equal Continent.find(5), Continent.find_by_population(515000000)
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_should_find_all_by_attribute_as_regex
|
69
|
+
assert_equal Continent.find(5,6), Continent.find_all_by_name(/America/)
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_should_find_all_by_attribute_in_range
|
73
|
+
assert_equal Continent.find(2,3), Continent.find_all_by_population(1000..20000000)
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_should_find_by_many_attributes
|
77
|
+
assert_equal Continent.find(6), Continent.find_by_name_and_size(/America/, 17840000)
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_should_puts_some_stuff
|
81
|
+
end
|
82
|
+
|
83
|
+
def teardown
|
84
|
+
Continent.send :class_variable_set, "@@instances", {}
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.4
|
3
|
+
specification_version: 1
|
4
|
+
name: passiverecord
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.0.1
|
7
|
+
date: 2007-09-26 00:00:00 -04:00
|
8
|
+
summary: Pacifying overactive records
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: jasper@ambethia.com
|
12
|
+
homepage: http://code.itred.org/projects/passive-record
|
13
|
+
rubyforge_project: paintitred
|
14
|
+
description: Pacifying overactive records
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Jason L Perry
|
31
|
+
files:
|
32
|
+
- History.txt
|
33
|
+
- Manifest.txt
|
34
|
+
- README.txt
|
35
|
+
- Rakefile
|
36
|
+
- lib/passive_record.rb
|
37
|
+
- lib/passive_record/associations.rb
|
38
|
+
- lib/passive_record/base.rb
|
39
|
+
- lib/passive_record/schema.rb
|
40
|
+
- lib/passive_record/version.rb
|
41
|
+
- test/test_associations.rb
|
42
|
+
- test/test_base.rb
|
43
|
+
- test/test_helper.rb
|
44
|
+
test_files:
|
45
|
+
- test/test_associations.rb
|
46
|
+
- test/test_base.rb
|
47
|
+
- test/test_helper.rb
|
48
|
+
rdoc_options:
|
49
|
+
- --main
|
50
|
+
- README.txt
|
51
|
+
extra_rdoc_files:
|
52
|
+
- History.txt
|
53
|
+
- Manifest.txt
|
54
|
+
- README.txt
|
55
|
+
executables: []
|
56
|
+
|
57
|
+
extensions: []
|
58
|
+
|
59
|
+
requirements: []
|
60
|
+
|
61
|
+
dependencies:
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: activerecord
|
64
|
+
version_requirement:
|
65
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.15.3
|
70
|
+
version:
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: hoe
|
73
|
+
version_requirement:
|
74
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: 1.3.0
|
79
|
+
version:
|