zilkey-active_hash 0.1.0

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 ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ .idea
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Jeff Dean
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,168 @@
1
+ # ActiveHash
2
+
3
+ ActiveHash is a simple base class that allows you to use a ruby hash as a readonly datasource for an ActiveRecord-like model.
4
+
5
+ ActiveHash assumes that every hash has an :id key, which is what you would probably store in a database. This allows you to seemlessly upgrade from ActiveHash objects to full ActiveRecord objects without having to change any code in your app, or any foreign keys in your database.
6
+
7
+ It also allows you to use #belongs_to in your AR objects.
8
+
9
+ ActiveHash can also be useful to create simple test classes that run without a database - ideal for testing plugins or gems that rely on simple AR behavior, but don't want to deal with databases or migrations for the spec suite.
10
+
11
+ ActiveHash also ships with:
12
+
13
+ * ActiveFile: a base class that will reload data from a flat file every time the flat file is changed
14
+ * ActiveYaml: a base class that will turn YAML into a hash and load the data into an ActiveHash object
15
+
16
+ ## Installation
17
+
18
+ sudo gem install zilkey-active_hash
19
+
20
+ ## Usage
21
+
22
+ To use ActiveHash, you need to:
23
+
24
+ * Inherit from ActiveHash::Base
25
+ * Define your fields
26
+ * Define your data
27
+
28
+ A quick example would be:
29
+
30
+ class Country < ActiveHash::Base
31
+ field :name
32
+ self.data = [
33
+ {:id => 1, :name => "US"},
34
+ {:id => 2, :name => "Canada"}
35
+ ]
36
+ end
37
+
38
+ ## Defining Fields
39
+
40
+ You can define fields in 2 ways, using the :fields method, or using the :field method, which allows you to specify a default value for the field:
41
+
42
+ class Country < ActiveHash::Base
43
+ fields :name, :population
44
+ field :is_axis_of_evil, :default => false
45
+ end
46
+
47
+ ## Defining Data
48
+
49
+ You can define data inside your class or outside. For example, you might have a class like this:
50
+
51
+ # app/models/country.rb
52
+ class Country < ActiveHash::Base
53
+ fields :name, :population
54
+ end
55
+
56
+ # config/initializers/data.rb
57
+ Country.data = [
58
+ {:id => 1, :name => "US"},
59
+ {:id => 2, :name => "Canada"}
60
+ ]
61
+
62
+ If you prefer to store your data in YAML, see below.
63
+
64
+ ## Class Methods
65
+
66
+ ActiveHash gives you ActiveRecord-esque methods like:
67
+
68
+ Country.all # => returns all Country objects
69
+ Country.count # => returns the length of the .data array
70
+ Country.first # => returns the first country object
71
+ Country.last # => returns the last country object
72
+ Country.find 1 # => returns the first country object with that id
73
+ Country.find [1,2] # => returns all Country objects with ids in the array
74
+ Country.find :all # => same as .all
75
+ Country.find :all, args # => the second argument is totally ignored, but allows it to play nicely with AR
76
+ Country.find_by_id 1 # => find the first object that matches the id
77
+
78
+ It also gives you a few dynamic finder methods. For example, if you defined :name as a field, you'd get:
79
+
80
+ Country.find_by_name "foo" # => returns the first object matching that name
81
+ Country.find_all_by_name "foo" # => returns an array of the objects with matching names
82
+
83
+ ## Instance Methods
84
+
85
+ ActiveHash objects implement enough of the ActiveRecord api to satisfy most common needs. For example:
86
+
87
+ Country#id # => returns the numeric id or nil
88
+ Country#quoted_id # => returns the numeric id
89
+ Country#to_param # => returns the id as a string
90
+ Country#new_record? # => false
91
+ Country#readonly? # => true
92
+ Country#hash # => the hash of the id (or the hash of nil)
93
+ Country#eql? # => compares type and id, returns false if id is nil
94
+
95
+ ActiveHash also gives you methods related to the fields you defined. For example, if you defined :name as a field, you'd get:
96
+
97
+ Country#name # => returns the passed in name
98
+ Country#name? # => returns true if the name is not blank
99
+
100
+ ## Integration with Rails
101
+
102
+ You can create .belongs_to associations from rails objects, like so:
103
+
104
+ class Country < ActiveHash::Base
105
+ fields :name, :population
106
+ end
107
+
108
+ class Person < ActiveRecord::Base
109
+ belongs_to :country
110
+ end
111
+
112
+ You can also use standard rails view helpers, like #collection_select:
113
+
114
+ <%= collection_select :person, :country_id, Country.all, :id, :name %>
115
+
116
+ ## ActiveYaml
117
+
118
+ If you want to store your data in YAML files, just inherit from ActiveYaml and specify your path information:
119
+
120
+ class Country < ActiveYaml::Base
121
+ field :name
122
+ end
123
+
124
+ By default, this class will look for a yml file named "countries.yml" in the same directory as the file. You can either change the directory it looks in, the filename it looks for, or both:
125
+
126
+ class Country < ActiveYaml::Base
127
+ set_root_path "/u/data"
128
+ set_filename "sample"
129
+ field :name
130
+ end
131
+
132
+ The above example will look for the file "/u/data/sample.yml".
133
+
134
+ ActiveYaml, as well as ActiveFile, check the mtime of the file you specified, and reloads the data if the mtime has changed. So you can replace the data in the files even if your app is running in production mode in rails.
135
+
136
+ ## ActiveFile
137
+
138
+ If you store encrypted data, or you'd like to store your flat files as CSV or XML or any other format, you can easily extend ActiveHash to parse and load your file. Just add a custom ::load_file method, and define the extension you want the file to use:
139
+
140
+ class Country < ActiveFile::Base
141
+ set_root_path "/u/data"
142
+ set_filename "sample"
143
+ field :name
144
+
145
+ class << self
146
+ def extension
147
+ ".super_secret"
148
+ end
149
+
150
+ def load_file
151
+ MyAwesomeDecoder.load_file(full_path)
152
+ end
153
+ end
154
+ end
155
+
156
+ The two methods you need to implement are load_file, which needs to return a hash, and .extension, which returns the file extension you are using. You have full_path available to you if you wish, or you can provide your own path.
157
+
158
+ ## Authors
159
+
160
+ Written by Mike Dalessio and Jeff Dean
161
+
162
+ ## Development
163
+
164
+ The only thing I think I'd really like to add here is support for typecasting the fields.
165
+
166
+ == Copyright
167
+
168
+ Copyright (c) 2009 Jeff Dean. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "active_hash"
8
+ gem.summary = %Q{An ActiveRecord-like model that uses a hash as a datasource}
9
+ gem.email = "jeff@zilkey.com"
10
+ gem.homepage = "http://github.com/zilkey/active_hash"
11
+ gem.authors = ["Jeff Dean", "Mike Dalessio"]
12
+ gem.add_dependency('activesupport')
13
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
14
+ end
15
+
16
+ rescue LoadError
17
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
18
+ end
19
+
20
+ require 'spec/rake/spectask'
21
+ Spec::Rake::SpecTask.new(:spec) do |spec|
22
+ spec.libs << 'lib' << 'spec'
23
+ spec.spec_files = FileList['spec/**/*_spec.rb']
24
+ end
25
+
26
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
27
+ spec.libs << 'lib' << 'spec'
28
+ spec.pattern = 'spec/**/*_spec.rb'
29
+ spec.rcov = true
30
+ end
31
+
32
+
33
+ task :default => :spec
34
+
35
+ require 'rake/rdoctask'
36
+ Rake::RDocTask.new do |rdoc|
37
+ if File.exist?('VERSION.yml')
38
+ config = YAML.load(File.read('VERSION.yml'))
39
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
40
+ else
41
+ version = ""
42
+ end
43
+
44
+ rdoc.rdoc_dir = 'rdoc'
45
+ rdoc.title = "active_hash #{version}"
46
+ rdoc.rdoc_files.include('README*')
47
+ rdoc.rdoc_files.include('lib/**/*.rb')
48
+ end
49
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,66 @@
1
+ module ActiveFile
2
+
3
+ class Base < ActiveHash::Base
4
+ class_inheritable_accessor :filename, :root_path, :cached_mtime
5
+
6
+ class << self
7
+ def all
8
+ reload
9
+ super
10
+ end
11
+
12
+ def reload
13
+ if should_reload?
14
+ self.data = load_file
15
+ end
16
+ end
17
+
18
+ protected :reload
19
+
20
+ def set_filename(name)
21
+ write_inheritable_attribute :filename, name
22
+ end
23
+
24
+ protected :set_filename
25
+
26
+ def set_root_path(path)
27
+ write_inheritable_attribute :root_path, path
28
+ end
29
+
30
+ protected :set_root_path
31
+
32
+ def load_file
33
+ raise "Override Me"
34
+ end
35
+
36
+ protected :load_file
37
+
38
+ def extension
39
+ raise "Override Me"
40
+ end
41
+
42
+ protected :extension
43
+
44
+ def full_path
45
+ root_path = read_inheritable_attribute(:root_path) || File.dirname(__FILE__)
46
+ filename = read_inheritable_attribute(:filename) || name.tableize
47
+ File.join(root_path, "#{filename}.#{extension}")
48
+ end
49
+
50
+ private :full_path
51
+
52
+ def should_reload?
53
+ if (mtime = File.mtime(full_path)) == read_inheritable_attribute(:cached_mtime)
54
+ false
55
+ else
56
+ write_inheritable_attribute :cached_mtime, mtime
57
+ true
58
+ end
59
+ end
60
+
61
+ private :should_reload?
62
+
63
+ end
64
+ end
65
+
66
+ end
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ require 'activesupport'
3
+ require 'active_hash/base'
4
+ require 'active_file/base'
5
+ require 'active_yaml/base'
@@ -0,0 +1,132 @@
1
+ module ActiveHash
2
+ class Base
3
+ class_inheritable_accessor :data
4
+ class << self
5
+
6
+ def data=(array_of_hashes)
7
+ @records = nil
8
+ write_inheritable_attribute(:data, array_of_hashes)
9
+ end
10
+
11
+ def all
12
+ @records ||= read_inheritable_attribute(:data).collect {|hash| new(hash)}
13
+ end
14
+
15
+ def count
16
+ all.length
17
+ end
18
+
19
+ def find(id, *args)
20
+ case id
21
+ when :all
22
+ all
23
+ when Array
24
+ all.select {|record| id.map(&:to_i).include?(record.id) }
25
+ else
26
+ find_by_id(id)
27
+ end
28
+ end
29
+
30
+ def find_by_id(id)
31
+ all.detect {|record| record.id == id.to_i}
32
+ end
33
+
34
+ delegate :first, :last, :to => :all
35
+
36
+ def fields(*args)
37
+ options = args.extract_options!
38
+ args.each do |field|
39
+ field(field, options)
40
+ end
41
+ end
42
+
43
+ def field(field_name, options = {})
44
+ define_getter_method(field_name, options[:default])
45
+ define_interrogator_method(field_name)
46
+ define_custom_find_method(field_name)
47
+ define_custom_find_all_method(field_name)
48
+ end
49
+
50
+ def define_getter_method(field, default_value)
51
+ define_method field do
52
+ attributes[field] || default_value
53
+ end
54
+ end
55
+
56
+ private :define_getter_method
57
+
58
+ def define_interrogator_method(field)
59
+ define_method "#{field}?" do
60
+ attributes[field].present?
61
+ end
62
+ end
63
+
64
+ private :define_interrogator_method
65
+
66
+ def define_custom_find_method(field_name)
67
+ meta_class.instance_eval do
68
+ define_method "find_by_#{field_name}" do |name|
69
+ all.detect {|record| record.send(field_name) == name }
70
+ end
71
+ end
72
+ end
73
+
74
+ private :define_custom_find_method
75
+
76
+ def define_custom_find_all_method(field_name)
77
+ meta_class.instance_eval do
78
+ define_method "find_all_by_#{field_name}" do |name|
79
+ all.select {|record| record.send(field_name) == name }
80
+ end
81
+ end
82
+ end
83
+
84
+ private :define_custom_find_all_method
85
+
86
+ def meta_class
87
+ class << self
88
+ self
89
+ end
90
+ end
91
+
92
+ private :meta_class
93
+
94
+ end
95
+
96
+ attr_reader :attributes
97
+
98
+ def initialize(options = {})
99
+ options.symbolize_keys!
100
+ @attributes = options
101
+ end
102
+
103
+ def id
104
+ attributes[:id] ? attributes[:id].to_i : nil
105
+ end
106
+
107
+ alias quoted_id id
108
+
109
+ def new_record?
110
+ false
111
+ end
112
+
113
+ def readonly?
114
+ true
115
+ end
116
+
117
+ def to_param
118
+ id.to_s
119
+ end
120
+
121
+ def eql?(other)
122
+ other.instance_of?(self.class) and not id.nil? and (id == other.id)
123
+ end
124
+
125
+ alias == eql?
126
+
127
+ def hash
128
+ id.hash
129
+ end
130
+
131
+ end
132
+ end
@@ -0,0 +1,15 @@
1
+ module ActiveYaml
2
+
3
+ class Base < ActiveFile::Base
4
+ class << self
5
+ def load_file
6
+ YAML.load_file(full_path)
7
+ end
8
+
9
+ def extension
10
+ "yml"
11
+ end
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,142 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe ActiveFile::Base do
4
+
5
+ describe ".filename=" do
6
+ before do
7
+ class Foo < ActiveFile::Base
8
+ self.filename = "foo-izzle"
9
+ end
10
+
11
+ class Bar < ActiveFile::Base
12
+ self.filename = "bar-izzle"
13
+ end
14
+ end
15
+
16
+ it "sets the filename on a per-subclass basis" do
17
+ Foo.filename.should == "foo-izzle"
18
+ Bar.filename.should == "bar-izzle"
19
+ end
20
+ end
21
+
22
+ describe ".set_filename" do
23
+ before do
24
+ class Foo < ActiveFile::Base
25
+ set_filename "foo-izzle"
26
+ end
27
+
28
+ class Bar < ActiveFile::Base
29
+ set_filename "bar-izzle"
30
+ end
31
+ end
32
+
33
+ it "sets the filename on a per-subclass basis" do
34
+ Foo.filename.should == "foo-izzle"
35
+ Bar.filename.should == "bar-izzle"
36
+ end
37
+ end
38
+
39
+ describe ".root_path=" do
40
+ before do
41
+ class Foo < ActiveFile::Base
42
+ self.root_path = "foo-izzle"
43
+ end
44
+
45
+ class Bar < ActiveFile::Base
46
+ self.root_path = "bar-izzle"
47
+ end
48
+ end
49
+
50
+ it "sets the root_path on a per-subclass basis" do
51
+ Foo.root_path.should == "foo-izzle"
52
+ Bar.root_path.should == "bar-izzle"
53
+ end
54
+ end
55
+
56
+ describe ".set_root_path" do
57
+ before do
58
+ class Foo < ActiveFile::Base
59
+ set_root_path "foo-izzle"
60
+ end
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
+ Foo.root_path.should == "foo-izzle"
69
+ Bar.root_path.should == "bar-izzle"
70
+ end
71
+ end
72
+
73
+ describe ".all" do
74
+ before do
75
+ class MyClass
76
+ end
77
+ end
78
+
79
+ it "loads the data from the load_file method" do
80
+ class Foo01 < ActiveFile::Base
81
+ class << self
82
+ def extension
83
+ "myfile"
84
+ end
85
+
86
+ def load_file
87
+ MyClass.load_file(full_path)
88
+ end
89
+ end
90
+ end
91
+
92
+ File.stub!(:mtime).and_return(1234)
93
+ MyClass.should_receive(:load_file).and_return([{:id => 1}, {:id => 2}, {:id => 3}])
94
+
95
+ records = Foo01.all
96
+ records.length.should == 3
97
+ records.should =~ [Foo01.new(:id => 1), Foo01.new(:id => 2), Foo01.new(:id => 3)]
98
+ end
99
+
100
+ it "does not re-fetch the data if the file's mtime has not changed" do
101
+ class SomeSampleClass < ActiveFile::Base
102
+ class << self
103
+ def extension
104
+ "myfile"
105
+ end
106
+
107
+ def load_file
108
+ MyClass.load_file(full_path)
109
+ end
110
+ end
111
+ end
112
+
113
+ File.stub!(:mtime).and_return(1234)
114
+ MyClass.should_receive(:load_file).once.and_return([{:foo => :bar}])
115
+ SomeSampleClass.all
116
+ SomeSampleClass.all
117
+ end
118
+
119
+ it "does re-fetch the data if the yaml file's mtime has changed" do
120
+ class SomeSampleClass2 < ActiveFile::Base
121
+ class << self
122
+ def extension
123
+ "myfile"
124
+ end
125
+
126
+ def load_file
127
+ MyClass.load_file(full_path)
128
+ end
129
+ end
130
+ end
131
+
132
+ MyClass.should_receive(:load_file).twice.and_return([{:foo => :bar}])
133
+
134
+ File.stub!(:mtime).and_return(1234)
135
+ SomeSampleClass2.all
136
+
137
+ File.stub!(:mtime).and_return(3456)
138
+ SomeSampleClass2.all
139
+ end
140
+ end
141
+
142
+ end
@@ -0,0 +1,414 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe ActiveHash::Base do
4
+
5
+ before do
6
+ class Country < ActiveHash::Base
7
+ end
8
+ end
9
+
10
+ describe ".fields" do
11
+ before do
12
+ Country.fields :name, :iso_name
13
+ end
14
+
15
+ it "defines a reader for each field" do
16
+ Country.new.should respond_to(:name)
17
+ Country.new.should respond_to(:iso_name)
18
+ end
19
+
20
+ it "defines interrogator methods for each field" do
21
+ Country.new.should respond_to(:name?)
22
+ Country.new.should respond_to(:iso_name?)
23
+ end
24
+
25
+ it "defines single finder methods for each field" do
26
+ Country.should respond_to(:find_by_name)
27
+ Country.should respond_to(:find_by_iso_name)
28
+ end
29
+
30
+ it "defines array finder methods for each field" do
31
+ Country.should respond_to(:find_all_by_name)
32
+ Country.should respond_to(:find_all_by_iso_name)
33
+ end
34
+ end
35
+
36
+ describe ".data=" do
37
+ before do
38
+ class Region < ActiveHash::Base
39
+ field :description
40
+ end
41
+ end
42
+
43
+ it "populates the object with data" do
44
+ Country.data = [{:name => "US"}, {:name => "Canada"}]
45
+ Country.data.should == [{:name => "US"}, {:name => "Canada"}]
46
+ end
47
+
48
+ it "allows each of it's subclasses to have it's own data" do
49
+ Country.data = [{:name => "US"}, {:name => "Canada"}]
50
+ Region.data = [{:description => "A big region"}, {:description => "A remote region"}]
51
+
52
+ Country.data.should == [{:name => "US"}, {:name => "Canada"}]
53
+ Region.data.should == [{:description => "A big region"}, {:description => "A remote region"}]
54
+ end
55
+ end
56
+
57
+ describe ".all" do
58
+ before do
59
+ Country.field :name
60
+ Country.data = [
61
+ {:id => 1, :name => "US"},
62
+ {:id => 2, :name => "Canada"}
63
+ ]
64
+ end
65
+
66
+ it "returns all data as inflated objects" do
67
+ Country.all.all?{|country| country.should be_kind_of(Country)}
68
+ end
69
+
70
+ it "populates the data correctly" do
71
+ records = Country.all
72
+ records.first.id.should == 1
73
+ records.first.name.should == "US"
74
+ records.last.id.should == 2
75
+ records.last.name.should == "Canada"
76
+ end
77
+
78
+ it "re-populates the records after data= is called" do
79
+ Country.data = [
80
+ {:id => 45, :name => "Canada"}
81
+ ]
82
+ records = Country.all
83
+ records.first.id.should == 45
84
+ records.first.name.should == "Canada"
85
+ records.length.should == 1
86
+ end
87
+ end
88
+
89
+ describe ".count" do
90
+ before do
91
+ Country.data = [
92
+ {:id => 1, :name => "US"},
93
+ {:id => 2, :name => "Canada"}
94
+ ]
95
+ end
96
+
97
+ it "returns the number of elements in the array" do
98
+ Country.count.should == 2
99
+ end
100
+ end
101
+
102
+ describe ".first" do
103
+ before do
104
+ Country.data = [
105
+ {:id => 1, :name => "US"},
106
+ {:id => 2, :name => "Canada"}
107
+ ]
108
+ end
109
+
110
+ it "returns the first object" do
111
+ Country.first.should == Country.new(:id => 1)
112
+ end
113
+ end
114
+
115
+ describe ".last" do
116
+ before do
117
+ Country.data = [
118
+ {:id => 1, :name => "US"},
119
+ {:id => 2, :name => "Canada"}
120
+ ]
121
+ end
122
+
123
+ it "returns the last object" do
124
+ Country.last.should == Country.new(:id => 2)
125
+ end
126
+ end
127
+
128
+ describe ".find" do
129
+ before do
130
+ Country.data = [
131
+ {:id => 1, :name => "US"},
132
+ {:id => 2, :name => "Canada"}
133
+ ]
134
+ end
135
+
136
+ context "with an id" do
137
+ it "finds the record with the specified id" do
138
+ Country.find(2).id.should == 2
139
+ end
140
+
141
+ it "finds the record with the specified id as a string" do
142
+ Country.find("2").id.should == 2
143
+ end
144
+ end
145
+
146
+ context "with :all" do
147
+ it "returns all records" do
148
+ Country.find(:all).should =~ [Country.new(:id => 1), Country.new(:id => 2)]
149
+ end
150
+ end
151
+
152
+ context "with 2 arguments" do
153
+ it "returns the record with the given id and ignores the conditions" do
154
+ Country.find(1, :conditions => "foo=bar").should == Country.new(:id => 1)
155
+ Country.find(:all, :conditions => "foo=bar").length.should == 2
156
+ end
157
+ end
158
+
159
+ context "with an array of ids" do
160
+ before do
161
+ Country.data = [
162
+ {:id => 1},
163
+ {:id => 2},
164
+ {:id => 3}
165
+ ]
166
+ end
167
+
168
+ it "returns all matching ids" do
169
+ Country.find([1,3]).should =~ [Country.new(:id => 1), Country.new(:id => 3)]
170
+ end
171
+ end
172
+ end
173
+
174
+ describe ".find_by_id" do
175
+ before do
176
+ Country.data = [
177
+ {:id => 1, :name => "US"},
178
+ {:id => 2, :name => "Canada"}
179
+ ]
180
+ end
181
+
182
+ context "with an id" do
183
+ it "finds the record with the specified id" do
184
+ Country.find_by_id(2).id.should == 2
185
+ end
186
+ end
187
+
188
+ context "with nil" do
189
+ it "returns nil" do
190
+ Country.find_by_id(nil).should be_nil
191
+ end
192
+ end
193
+
194
+ context "with an id not present" do
195
+ it "returns nil" do
196
+ Country.find_by_id(4567).should be_nil
197
+ end
198
+ end
199
+ end
200
+
201
+ describe "custom finders" do
202
+ before do
203
+ Country.field :name
204
+ Country.data = [
205
+ {:id => 1, :name => "US"},
206
+ {:id => 2, :name => "US"},
207
+ {:id => 3, :name => "Canada"}
208
+ ]
209
+ end
210
+
211
+ describe "find_by_<field_name>" do
212
+ context "with a name" do
213
+ it "returns the first record matching that name" do
214
+ Country.find_by_name("US").id.should == 1
215
+ end
216
+ end
217
+
218
+ context "with nil" do
219
+ it "returns nil" do
220
+ Country.find_by_name(nil).should be_nil
221
+ end
222
+ end
223
+
224
+ context "with a name not present" do
225
+ it "returns nil" do
226
+ Country.find_by_name("foo").should be_nil
227
+ end
228
+ end
229
+ end
230
+
231
+ describe "find_all_by_<field_name>" do
232
+ context "with a name" do
233
+ it "returns the records matching that name" do
234
+ countries = Country.find_all_by_name("US")
235
+ countries.length.should == 2
236
+ countries.first.name.should == "US"
237
+ countries.last.name.should == "US"
238
+ end
239
+ end
240
+
241
+ context "with a name not present" do
242
+ it "returns an empty array" do
243
+ Country.find_all_by_name("foo").should be_empty
244
+ end
245
+ end
246
+ end
247
+ end
248
+
249
+
250
+ describe "#attributes" do
251
+ it "returns the hash passed in the initializer" do
252
+ country = Country.new(:foo => :bar)
253
+ country.attributes.should == {:foo => :bar}
254
+ end
255
+
256
+ it "symbolizes keys" do
257
+ country = Country.new("foo" => :bar)
258
+ country.attributes.should == {:foo => :bar}
259
+ end
260
+ end
261
+
262
+ describe "reader methods" do
263
+ context "for regular fields" do
264
+ before do
265
+ Country.fields :name, :iso_name
266
+ end
267
+
268
+ it "returns the given attribute when present" do
269
+ country = Country.new(:name => "Spain")
270
+ country.name.should == "Spain"
271
+ end
272
+
273
+ it "returns nil when not present" do
274
+ country = Country.new
275
+ country.name.should be_nil
276
+ end
277
+ end
278
+
279
+ context "for fields with default values" do
280
+ before do
281
+ Country.field :name, :default => "foobar"
282
+ end
283
+
284
+ it "returns the given attribute when present" do
285
+ country = Country.new(:name => "Spain")
286
+ country.name.should == "Spain"
287
+ end
288
+
289
+ it "returns the default value when not present" do
290
+ country = Country.new
291
+ country.name.should == "foobar"
292
+ end
293
+ end
294
+ end
295
+
296
+ describe "interrogator methods" do
297
+ before do
298
+ Country.fields :name, :iso_name
299
+ end
300
+
301
+ it "returns true if the given attribute is non-blank" do
302
+ country = Country.new(:name => "Spain")
303
+ country.should be_name
304
+ end
305
+
306
+ it "returns false if the given attribute is blank" do
307
+ country = Country.new(:name => " ")
308
+ country.should_not be_name
309
+ end
310
+
311
+ it "returns false if the given attribute was not passed" do
312
+ country = Country.new
313
+ country.should_not be_name
314
+ end
315
+ end
316
+
317
+ describe "#id" do
318
+ context "when passed an id" do
319
+ it "returns the id as an integer" do
320
+ country = Country.new :id => "1"
321
+ country.id.should == 1
322
+ end
323
+ end
324
+ context "when not passed an id" do
325
+ it "returns nil" do
326
+ country = Country.new
327
+ country.id.should be_nil
328
+ end
329
+ end
330
+ end
331
+
332
+ describe "#new_record?" do
333
+ it "should be false" do
334
+ Country.new.should_not be_new_record
335
+ end
336
+ end
337
+
338
+ describe "#quoted_id" do
339
+ it "should return id" do
340
+ Country.new(:id => 2).quoted_id.should == 2
341
+ end
342
+ end
343
+
344
+ describe "#to_param" do
345
+ it "should return id as a string" do
346
+ Country.new(:id => 2).to_param.should == "2"
347
+ end
348
+ end
349
+
350
+ describe "#eql?" do
351
+ before do
352
+ class Region < ActiveHash::Base
353
+ end
354
+ end
355
+
356
+ it "should return true with the same class and id" do
357
+ Country.new(:id => 23).eql?(Country.new(:id => 23)).should be_true
358
+ end
359
+
360
+ it "should return false with the same class and different ids" do
361
+ Country.new(:id => 24).eql?(Country.new(:id => 23)).should be_false
362
+ end
363
+
364
+ it "should return false with the different classes and the same id" do
365
+ Country.new(:id => 23).eql?(Region.new(:id => 23)).should be_false
366
+ end
367
+
368
+ it "returns false when id is nil" do
369
+ Country.new.eql?(Country.new).should be_false
370
+ end
371
+ end
372
+
373
+ describe "#==" do
374
+ before do
375
+ class Region < ActiveHash::Base
376
+ end
377
+ end
378
+
379
+ it "should return true with the same class and id" do
380
+ Country.new(:id => 23).should == Country.new(:id => 23)
381
+ end
382
+
383
+ it "should return false with the same class and different ids" do
384
+ Country.new(:id => 24).should_not == Country.new(:id => 23)
385
+ end
386
+
387
+ it "should return false with the different classes and the same id" do
388
+ Country.new(:id => 23).should_not == Region.new(:id => 23)
389
+ end
390
+
391
+ it "returns false when id is nil" do
392
+ Country.new.should_not == Country.new
393
+ end
394
+ end
395
+
396
+ describe "#hash" do
397
+ it "returns id for hash" do
398
+ Country.new(:id => 45).hash.should == 45.hash
399
+ Country.new.hash.should == nil.hash
400
+ end
401
+
402
+ it "is hashable" do
403
+ { Country.new(:id => 4) => "bar"}.should == {Country.new(:id => 4) => "bar" }
404
+ { Country.new(:id => 3) => "bar"}.should_not == {Country.new(:id => 4) => "bar" }
405
+ end
406
+ end
407
+
408
+ describe "#readonly?" do
409
+ it "returns true" do
410
+ Country.new.should be_readonly
411
+ end
412
+ end
413
+
414
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe ActiveYaml::Base do
4
+
5
+ describe ".all" do
6
+ it "loads the data from the yml file" do
7
+ class SomeArbitraryClass < ActiveYaml::Base
8
+ set_root_path File.dirname(__FILE__)
9
+ set_filename "sample"
10
+ field :name
11
+ end
12
+
13
+ records = SomeArbitraryClass.all
14
+ records.length.should == 3
15
+ records.should =~ [SomeArbitraryClass.new(:id => 1), SomeArbitraryClass.new(:id => 2), SomeArbitraryClass.new(:id => 3)]
16
+ records.first.name.should == "US"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,6 @@
1
+ - id: 1
2
+ name: US
3
+ - id: 2
4
+ name: Canada
5
+ - id: 3
6
+ name: Mexico
@@ -0,0 +1,9 @@
1
+ require 'spec'
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+ require 'active_hash'
6
+
7
+ Spec::Runner.configure do |config|
8
+
9
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zilkey-active_hash
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jeff Dean
8
+ - Mike Dalessio
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-07-22 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: activesupport
18
+ type: :runtime
19
+ version_requirement:
20
+ version_requirements: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "0"
25
+ version:
26
+ description:
27
+ email: jeff@zilkey.com
28
+ executables: []
29
+
30
+ extensions: []
31
+
32
+ extra_rdoc_files:
33
+ - LICENSE
34
+ - README.md
35
+ files:
36
+ - .document
37
+ - .gitignore
38
+ - LICENSE
39
+ - README.md
40
+ - Rakefile
41
+ - VERSION
42
+ - lib/active_file/base.rb
43
+ - lib/active_hash.rb
44
+ - lib/active_hash/base.rb
45
+ - lib/active_yaml/base.rb
46
+ - spec/active_file/base_spec.rb
47
+ - spec/active_hash/base_spec.rb
48
+ - spec/active_yaml/base_spec.rb
49
+ - spec/active_yaml/sample.yml
50
+ - spec/spec_helper.rb
51
+ has_rdoc: false
52
+ homepage: http://github.com/zilkey/active_hash
53
+ post_install_message:
54
+ rdoc_options:
55
+ - --charset=UTF-8
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ requirements: []
71
+
72
+ rubyforge_project:
73
+ rubygems_version: 1.2.0
74
+ signing_key:
75
+ specification_version: 3
76
+ summary: An ActiveRecord-like model that uses a hash as a datasource
77
+ test_files:
78
+ - spec/active_file/base_spec.rb
79
+ - spec/active_hash/base_spec.rb
80
+ - spec/active_yaml/base_spec.rb
81
+ - spec/spec_helper.rb