honkster-active_hash 0.7.3
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.
- data/.document +5 -0
- data/.gitignore +7 -0
- data/CHANGELOG +40 -0
- data/LICENSE +20 -0
- data/README.md +291 -0
- data/Rakefile +48 -0
- data/VERSION +1 -0
- data/active_hash.gemspec +68 -0
- data/geminstaller.yml +13 -0
- data/lib/active_file/base.rb +50 -0
- data/lib/active_hash.rb +5 -0
- data/lib/active_hash/base.rb +265 -0
- data/lib/active_yaml/base.rb +24 -0
- data/lib/associations/associations.rb +40 -0
- data/spec/active_file/base_spec.rb +83 -0
- data/spec/active_hash/base_spec.rb +736 -0
- data/spec/active_yaml/base_spec.rb +85 -0
- data/spec/associations/associations_spec.rb +122 -0
- data/spec/fixtures/array_rows.yml +4 -0
- data/spec/fixtures/cities.yml +8 -0
- data/spec/fixtures/countries.yml +15 -0
- data/spec/fixtures/states.yml +6 -0
- data/spec/spec_helper.rb +22 -0
- metadata +94 -0
data/geminstaller.yml
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
defaults:
|
|
2
|
+
install_options: --source=http://gemcutter.org --source=http://gems.rubyforge.org --include-dependencies --no-ri --no-rdoc
|
|
3
|
+
gems:
|
|
4
|
+
- name: acts_as_fu
|
|
5
|
+
version: 0.0.5
|
|
6
|
+
- name: activesupport
|
|
7
|
+
version: >= 2.2.2
|
|
8
|
+
- name: rspec
|
|
9
|
+
version: >= 1.2.8
|
|
10
|
+
- name: fixjour
|
|
11
|
+
version: 0.2.0
|
|
12
|
+
- name: jeweler
|
|
13
|
+
version: 1.2.1
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module ActiveFile
|
|
2
|
+
|
|
3
|
+
class Base < ActiveHash::Base
|
|
4
|
+
class_inheritable_accessor :filename, :root_path, :data_loaded
|
|
5
|
+
|
|
6
|
+
class << self
|
|
7
|
+
|
|
8
|
+
def all
|
|
9
|
+
reload unless data_loaded
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def delete_all
|
|
14
|
+
self.data_loaded = true
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def reload(foo = true)
|
|
19
|
+
self.data_loaded = true
|
|
20
|
+
self.data = load_file
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def set_filename(name)
|
|
24
|
+
write_inheritable_attribute :filename, name
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def set_root_path(path)
|
|
28
|
+
write_inheritable_attribute :root_path, path
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def load_file
|
|
32
|
+
raise "Override Me"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def full_path
|
|
36
|
+
root_path = read_inheritable_attribute(:root_path) || Dir.pwd
|
|
37
|
+
filename = read_inheritable_attribute(:filename) || name.tableize
|
|
38
|
+
File.join(root_path, "#{filename}.#{extension}")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def extension
|
|
42
|
+
raise "Override Me"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
protected :extension
|
|
46
|
+
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
data/lib/active_hash.rb
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
module ActiveHash
|
|
2
|
+
class Base
|
|
3
|
+
class_inheritable_accessor :data
|
|
4
|
+
class << self
|
|
5
|
+
attr_reader :field_names
|
|
6
|
+
|
|
7
|
+
def data=(array_of_hashes)
|
|
8
|
+
@records = nil
|
|
9
|
+
write_inheritable_attribute(:data, array_of_hashes)
|
|
10
|
+
if array_of_hashes
|
|
11
|
+
auto_assign_fields( array_of_hashes )
|
|
12
|
+
array_of_hashes.each do |hash|
|
|
13
|
+
insert new(hash)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def insert(record)
|
|
19
|
+
@records ||= []
|
|
20
|
+
record.attributes[:id] ||= next_id
|
|
21
|
+
@records << record
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def next_id
|
|
25
|
+
max_record = all.max {|a, b| a.id <=> b.id }
|
|
26
|
+
if max_record.nil?
|
|
27
|
+
1
|
|
28
|
+
elsif max_record.id.is_a?(Numeric)
|
|
29
|
+
max_record.id.succ
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def create(attributes = {})
|
|
34
|
+
record = new(attributes)
|
|
35
|
+
record.save
|
|
36
|
+
record
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def create!(attributes = {})
|
|
40
|
+
record = new(attributes)
|
|
41
|
+
record.save!
|
|
42
|
+
record
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def all
|
|
46
|
+
@records || []
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def count
|
|
50
|
+
all.length
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def transaction
|
|
54
|
+
yield
|
|
55
|
+
rescue ActiveRecord::Rollback
|
|
56
|
+
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def delete_all
|
|
60
|
+
@records = []
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def find(id, *args)
|
|
64
|
+
case id
|
|
65
|
+
when :all
|
|
66
|
+
all
|
|
67
|
+
when Array
|
|
68
|
+
all.select {|record| id.map(&:to_i).include?(record.id) }
|
|
69
|
+
else
|
|
70
|
+
find_by_id(id)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def find_by_id(id)
|
|
75
|
+
all.detect {|record| record.id == id.to_i}
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
delegate :first, :last, :to => :all
|
|
79
|
+
|
|
80
|
+
def fields(*args)
|
|
81
|
+
options = args.extract_options!
|
|
82
|
+
args.each do |field|
|
|
83
|
+
field(field, options)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def field(field_name, options = {})
|
|
88
|
+
@field_names ||= []
|
|
89
|
+
@field_names << field_name
|
|
90
|
+
|
|
91
|
+
define_getter_method(field_name, options[:default])
|
|
92
|
+
define_setter_method(field_name)
|
|
93
|
+
define_interrogator_method(field_name)
|
|
94
|
+
define_custom_find_method(field_name)
|
|
95
|
+
define_custom_find_all_method(field_name)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def respond_to?(method_name, include_private=false)
|
|
99
|
+
super ||
|
|
100
|
+
begin
|
|
101
|
+
config = configuration_for_custom_finder(method_name)
|
|
102
|
+
config && config[:fields].all? do |field|
|
|
103
|
+
field_names.include?(field.to_sym) || field.to_sym == :id
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def method_missing(method_name, *args)
|
|
109
|
+
return super unless respond_to? method_name
|
|
110
|
+
|
|
111
|
+
config = configuration_for_custom_finder(method_name)
|
|
112
|
+
attribute_pairs = config[:fields].zip(args)
|
|
113
|
+
matches = all.select { |base| attribute_pairs.all? { |field, value| base.send(field).to_s == value.to_s } }
|
|
114
|
+
config[:all?] ? matches : matches.first
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def configuration_for_custom_finder(finder_name)
|
|
118
|
+
if finder_name.to_s.match(/^find_(all_)?by_(.*)/)
|
|
119
|
+
{
|
|
120
|
+
:all? => !!$1,
|
|
121
|
+
:fields => $2.split('_and_')
|
|
122
|
+
}
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
private :configuration_for_custom_finder
|
|
127
|
+
|
|
128
|
+
def define_getter_method(field, default_value)
|
|
129
|
+
unless instance_methods.include?(field.to_s)
|
|
130
|
+
define_method(field) do
|
|
131
|
+
attributes[field].nil? ? default_value : attributes[field]
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
private :define_getter_method
|
|
137
|
+
|
|
138
|
+
def define_setter_method(field)
|
|
139
|
+
method_name = "#{field}="
|
|
140
|
+
unless instance_methods.include?(method_name)
|
|
141
|
+
define_method(method_name) do |new_val|
|
|
142
|
+
attributes[field] = new_val
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private :define_setter_method
|
|
148
|
+
|
|
149
|
+
def define_interrogator_method(field)
|
|
150
|
+
method_name = "#{field}?"
|
|
151
|
+
unless instance_methods.include?(method_name)
|
|
152
|
+
define_method(method_name) do
|
|
153
|
+
send(field).present?
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
private :define_interrogator_method
|
|
159
|
+
|
|
160
|
+
def define_custom_find_method(field_name)
|
|
161
|
+
method_name = "find_by_#{field_name}"
|
|
162
|
+
unless singleton_methods.include?(method_name)
|
|
163
|
+
metaclass.instance_eval do
|
|
164
|
+
define_method(method_name) do |name|
|
|
165
|
+
all.detect {|record| record.send(field_name) == name }
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
private :define_custom_find_method
|
|
172
|
+
|
|
173
|
+
def define_custom_find_all_method(field_name)
|
|
174
|
+
method_name = "find_all_by_#{field_name}"
|
|
175
|
+
unless singleton_methods.include?(method_name)
|
|
176
|
+
metaclass.instance_eval do
|
|
177
|
+
unless singleton_methods.include?(method_name)
|
|
178
|
+
define_method(method_name) do |name|
|
|
179
|
+
all.select {|record| record.send(field_name) == name }
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
private :define_custom_find_all_method
|
|
187
|
+
|
|
188
|
+
def auto_assign_fields(array_of_hashes)
|
|
189
|
+
(array_of_hashes || []).inject([]) do |array, row|
|
|
190
|
+
row.symbolize_keys!
|
|
191
|
+
row.keys.each do |key|
|
|
192
|
+
unless key.to_s == "id"
|
|
193
|
+
array << key
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
array
|
|
197
|
+
end.uniq.each do |key|
|
|
198
|
+
field key
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
private :auto_assign_fields
|
|
203
|
+
|
|
204
|
+
# Needed for ActiveRecord polymorphic associations
|
|
205
|
+
def base_class
|
|
206
|
+
ActiveHash::Base
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
attr_reader :attributes
|
|
212
|
+
|
|
213
|
+
def initialize(options = {})
|
|
214
|
+
options.symbolize_keys!
|
|
215
|
+
@attributes = options
|
|
216
|
+
options.each do |key, value|
|
|
217
|
+
send "#{key}=", value
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def id
|
|
222
|
+
attributes[:id] ? attributes[:id] : nil
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def id=(id)
|
|
226
|
+
attributes[:id] = id
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
alias quoted_id id
|
|
230
|
+
|
|
231
|
+
def new_record?
|
|
232
|
+
! self.class.all.include?(self)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def readonly?
|
|
236
|
+
true
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def to_param
|
|
240
|
+
id.to_s
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def eql?(other)
|
|
244
|
+
other.instance_of?(self.class) and not id.nil? and (id == other.id)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
alias == eql?
|
|
248
|
+
|
|
249
|
+
def hash
|
|
250
|
+
id.hash
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def save
|
|
254
|
+
self.class.insert(self)
|
|
255
|
+
true
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
alias save! save
|
|
259
|
+
|
|
260
|
+
def valid?
|
|
261
|
+
true
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
end
|
|
265
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module ActiveYaml
|
|
2
|
+
|
|
3
|
+
class Base < ActiveFile::Base
|
|
4
|
+
class << self
|
|
5
|
+
def load_file
|
|
6
|
+
if (data = raw_data).is_a?(Array)
|
|
7
|
+
data
|
|
8
|
+
else
|
|
9
|
+
data.values
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def raw_data
|
|
14
|
+
YAML.load_file(full_path)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def extension
|
|
18
|
+
"yml"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module ActiveHash
|
|
2
|
+
module Associations
|
|
3
|
+
|
|
4
|
+
def self.included(base)
|
|
5
|
+
base.send(:extend, ClassMethods)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
def has_many(association_id, options = {})
|
|
10
|
+
|
|
11
|
+
define_method(association_id) do
|
|
12
|
+
options = {
|
|
13
|
+
:class_name => association_id.to_s.classify,
|
|
14
|
+
:foreign_key => self.class.to_s.foreign_key
|
|
15
|
+
}.merge(options)
|
|
16
|
+
|
|
17
|
+
options[:class_name].constantize.send("find_all_by_#{options[:foreign_key]}", id)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def belongs_to(association_id, options = {})
|
|
23
|
+
|
|
24
|
+
options = {
|
|
25
|
+
:class_name => association_id.to_s.classify,
|
|
26
|
+
:foreign_key => association_id.to_s.foreign_key
|
|
27
|
+
}.merge(options)
|
|
28
|
+
|
|
29
|
+
define_method(association_id) do
|
|
30
|
+
options[:class_name].constantize.find(send(options[:foreign_key]))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
define_method("#{association_id}=") do |new_value|
|
|
34
|
+
attributes[ options[:foreign_key].to_sym ] = new_value.id
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
require 'spec/spec_helper'
|
|
2
|
+
|
|
3
|
+
describe ActiveFile::Base do
|
|
4
|
+
before do
|
|
5
|
+
class Country < ActiveFile::Base
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
after do
|
|
10
|
+
Object.send :remove_const, :Country
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe ".filename=" do
|
|
14
|
+
before do
|
|
15
|
+
Country.filename = "foo-izzle"
|
|
16
|
+
|
|
17
|
+
class Bar < ActiveFile::Base
|
|
18
|
+
self.filename = "bar-izzle"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "sets the filename on a per-subclass basis" do
|
|
23
|
+
Country.filename.should == "foo-izzle"
|
|
24
|
+
Bar.filename.should == "bar-izzle"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe ".set_filename" do
|
|
29
|
+
before do
|
|
30
|
+
Country.set_filename "foo-izzle"
|
|
31
|
+
|
|
32
|
+
class Bar < ActiveFile::Base
|
|
33
|
+
set_filename "bar-izzle"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "sets the filename on a per-subclass basis" do
|
|
38
|
+
Country.filename.should == "foo-izzle"
|
|
39
|
+
Bar.filename.should == "bar-izzle"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe ".root_path=" do
|
|
44
|
+
before do
|
|
45
|
+
Country.root_path = "foo-izzle"
|
|
46
|
+
|
|
47
|
+
class Bar < ActiveFile::Base
|
|
48
|
+
self.root_path = "bar-izzle"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "sets the root_path on a per-subclass basis" do
|
|
53
|
+
Country.root_path.should == "foo-izzle"
|
|
54
|
+
Bar.root_path.should == "bar-izzle"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe ".set_root_path" do
|
|
59
|
+
before do
|
|
60
|
+
Country.set_root_path "foo-izzle"
|
|
61
|
+
|
|
62
|
+
class Bar < ActiveFile::Base
|
|
63
|
+
set_root_path "bar-izzle"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "sets the root_path on a per-subclass basis" do
|
|
68
|
+
Country.root_path.should == "foo-izzle"
|
|
69
|
+
Bar.root_path.should == "bar-izzle"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe ".full_path" do
|
|
74
|
+
it "defaults to the directory of the calling file" do
|
|
75
|
+
class Country
|
|
76
|
+
def self.extension() "foo" end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
Country.full_path.should == "#{Dir.pwd}/countries.foo"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
end
|