quirkey-static_model 0.2.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/History.txt ADDED
@@ -0,0 +1,26 @@
1
+ == 0.2.0 2008-10-27
2
+
3
+ * 2 major enhancements
4
+
5
+ * StaticModel generator
6
+ * 'class_attributes' and ability to set default for record attributes
7
+ * Real documentation on the website: http://quirkey.rubyforge.org/static_model
8
+
9
+ == 0.1.5 2008-09-18
10
+
11
+ * 1 minor enhancement
12
+
13
+ * better handling of empty YAML files (ienders)
14
+
15
+ == 0.1.0 2008-08-27
16
+
17
+ * 1 minor enchancement
18
+
19
+ * fixed the dynamic finders to match AR's behavior AKA find_by === find_first_by
20
+
21
+ == 0.1.0 2008-07-09
22
+
23
+ * 1 major enhancement:
24
+ * Initial release
25
+ - Basic functionality
26
+ - Full test suite using shoulda
data/License.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Aaron Quint, Quirkey NYC, LLC
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/Manifest.txt ADDED
@@ -0,0 +1,17 @@
1
+ History.txt
2
+ License.txt
3
+ Manifest.txt
4
+ README.rdoc
5
+ generators/static_model/USAGE
6
+ generators/static_model/static_model_generator.rb
7
+ generators/static_model/templates/model.rb.erb
8
+ generators/static_model/templates/models.yml.erb
9
+ lib/static_model.rb
10
+ lib/static_model/active_record.rb
11
+ lib/static_model/associations.rb
12
+ lib/static_model/base.rb
13
+ lib/static_model/comparable.rb
14
+ lib/static_model/errors.rb
15
+ lib/static_model/rails.rb
16
+ lib/static_model/scope.rb
17
+ lib/static_model/version.rb
data/README.rdoc ADDED
@@ -0,0 +1,53 @@
1
+ = static_model
2
+
3
+ http://quirkey.rubyforge.org/static_model/
4
+
5
+ == DESCRIPTION:
6
+
7
+ ActiveRecord like functionalities for reading from YAML with a simple class implementation
8
+
9
+ == SYNOPSIS:
10
+
11
+ Use like ActiveRecord::Base, except no database, just a YAML file. The YAML should contain an array of records.
12
+
13
+ For usage and setup check out the project's site:
14
+
15
+ http://quirkey.rubyforge.org/static_model/
16
+
17
+ == REQUIREMENTS:
18
+
19
+ YAML
20
+ activesupport >= 2.1.0
21
+
22
+ == INSTALL:
23
+
24
+ sudo gem install static_model
25
+
26
+ The source is at github:
27
+
28
+ http://github.com/quirkey/static_model/
29
+
30
+ == LICENSE:
31
+
32
+ (The MIT License)
33
+
34
+ Copyright (c) 2008 Aaron Quint, Quirkey NYC, LLC
35
+
36
+ Permission is hereby granted, free of charge, to any person obtaining
37
+ a copy of this software and associated documentation files (the
38
+ 'Software'), to deal in the Software without restriction, including
39
+ without limitation the rights to use, copy, modify, merge, publish,
40
+ distribute, sublicense, and/or sell copies of the Software, and to
41
+ permit persons to whom the Software is furnished to do so, subject to
42
+ the following conditions:
43
+
44
+ The above copyright notice and this permission notice shall be
45
+ included in all copies or substantial portions of the Software.
46
+
47
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
48
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
49
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
50
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
51
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
52
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
53
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,8 @@
1
+ Description:
2
+
3
+ Generates an empty StaticModel class/model
4
+
5
+ Usage:
6
+
7
+ ./script/generate static_model ModelName --data=config/data/ --lib=app/models/
8
+
@@ -0,0 +1,57 @@
1
+ class StaticModelGenerator < RubiGen::Base
2
+
3
+ default_options :author => nil
4
+
5
+ attr_reader :name, :lib_dir, :data_dir
6
+
7
+ def initialize(runtime_args, runtime_options = {})
8
+ super
9
+ usage if args.empty?
10
+ @name = args.shift
11
+ extract_options
12
+ end
13
+
14
+ def manifest
15
+ record do |m|
16
+ # Ensure appropriate folder(s) exists
17
+ m.directory data_dir
18
+ m.directory lib_dir
19
+ m.template 'model.rb.erb', File.join(lib_dir, name.underscore + ".rb")
20
+ m.template 'models.yml.erb', File.join(data_dir, table_name + ".yml")
21
+ end
22
+ end
23
+
24
+ protected
25
+ def banner
26
+ <<-EOS
27
+ Creates a ...
28
+
29
+ USAGE: #{$0} #{spec.name} ModelName
30
+ EOS
31
+ end
32
+
33
+ def class_name
34
+ @name.classify
35
+ end
36
+
37
+ def table_name
38
+ @name.tableize
39
+ end
40
+
41
+ def add_options!(opts)
42
+ # opts.separator ''
43
+ # opts.separator 'Options:'
44
+ # For each option below, place the default
45
+ # at the top of the file next to "default_options"
46
+ # opts.on("-a", "--author=\"Your Name\"", String,
47
+ # "Some comment about this option",
48
+ # "Default: none") { |options[:author]| }
49
+ opts.on("-l", "--lib=your/path/", String, "Put the model.rb in a specific lib dir (default = app/models/)") {|o| options[:lib_dir] = o }
50
+ opts.on("-d", "--data=your/path/", String, "Put the models.yml data file in a specific data dir (default = config/data/)") {|o| options[:data_dir] = o }
51
+ end
52
+
53
+ def extract_options
54
+ @lib_dir = options[:lib_dir] || File.join('app', 'models')
55
+ @data_dir = options[:data_dir] || File.join('config', 'data')
56
+ end
57
+ end
@@ -0,0 +1,4 @@
1
+ class <%= class_name %> < StaticModel::Base
2
+ set_data_file File.join('<%= data_dir %>', '<%= table_name %>.yml')
3
+
4
+ end
@@ -0,0 +1,6 @@
1
+ ---
2
+ records:
3
+ - id: 1
4
+ name: One
5
+ - id: 2
6
+ name: Two
@@ -0,0 +1,11 @@
1
+ module StaticModel
2
+ module ActiveRecord
3
+ # Stubbing out ActiveRecord methods so that associations work
4
+
5
+ def new_record?
6
+ false
7
+ end
8
+
9
+
10
+ end
11
+ end
@@ -0,0 +1,82 @@
1
+ module StaticModel
2
+ module Associations
3
+
4
+ def self.included(klass)
5
+ klass.extend MacroMethods
6
+ end
7
+
8
+ module MacroMethods
9
+
10
+ def associations
11
+ @associations ||= {}
12
+ end
13
+
14
+ def has_many(association_name, options = {})
15
+ self.associations[association_name.to_sym] = HasManyAssociation.new(self, association_name.to_sym, {:foreign_key => "#{self.to_s.foreign_key}"}.merge(options))
16
+ end
17
+
18
+ def belongs_to(association_name, options = {})
19
+ self.associations[association_name.to_sym] = BelongsToAssociation.new(self, association_name.to_sym, {:foreign_key => "#{association_name.to_s.foreign_key}"}.merge(options))
20
+ end
21
+
22
+ end
23
+
24
+
25
+ class Association
26
+ attr_reader :klass, :name, :options, :foreign_key
27
+
28
+ def initialize(klass, name, options = {})
29
+ @klass = klass
30
+ @name = name
31
+ @options = options
32
+ @reflection_klass_name = @options[:class_name] || @name.to_s.classify
33
+ @foreign_key = @options[:foreign_key]
34
+ define_association_methods
35
+ end
36
+
37
+ def reflection_klass
38
+ Object.const_get(@reflection_klass_name)
39
+ rescue
40
+ eval <<-EOT
41
+ class #{@reflection_klass_name}; end;
42
+ #{@reflection_klass_name}
43
+ EOT
44
+ end
45
+
46
+ def define_association_methods
47
+ raise 'Should only use descendants of Association'
48
+ end
49
+ end
50
+
51
+ class HasManyAssociation < Association
52
+
53
+ def define_association_methods
54
+ if reflection_klass.respond_to?(:scoped)
55
+ klass.module_eval <<-EOT
56
+ def #{name}
57
+ #{reflection_klass}.scoped(:conditions => {:#{foreign_key} => id})
58
+ end
59
+ EOT
60
+ else
61
+ klass.module_eval <<-EOT
62
+ def #{name}
63
+ #{reflection_klass}.send(:find_all_by_#{foreign_key}, id)
64
+ end
65
+ EOT
66
+ end
67
+ end
68
+ end
69
+
70
+ class BelongsToAssociation < Association
71
+
72
+ def define_association_methods
73
+ klass.module_eval <<-EOT
74
+ def #{name}
75
+ #{reflection_klass}.send(:find, #{foreign_key})
76
+ end
77
+ EOT
78
+ end
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,169 @@
1
+ module StaticModel
2
+ class Base
3
+ include StaticModel::Associations
4
+ include StaticModel::ActiveRecord
5
+ include StaticModel::Comparable
6
+
7
+ @@load_path = File.join('config', 'data')
8
+
9
+ attr_reader :id
10
+
11
+
12
+ def initialize(attribute_hash = {})
13
+ raise(StaticModel::BadOptions, "Initializing a model is done with a Hash {} given #{attribute_hash.inspect}") unless attribute_hash.is_a?(Hash)
14
+ @id = attribute_hash.delete('id') || attribute_hash.delete(:id) || (self.class.count + 1)
15
+ self.attributes = attribute_hash
16
+ end
17
+
18
+ def to_s
19
+ self.inspect
20
+ end
21
+
22
+ def attributes
23
+ @attributes ||= HashWithIndifferentAccess.new
24
+ end
25
+
26
+ def attributes=(attribute_hash)
27
+ attribute_hash.each {|k,v| set_attribute(k,v) }
28
+ end
29
+
30
+ def attribute_names
31
+ (attributes.keys | self.class.class_attributes.keys).collect {|k| k.to_s }
32
+ end
33
+
34
+ class << self
35
+
36
+ def find(what, *args, &block)
37
+ case what
38
+ when Symbol
39
+ send("find_#{what}")
40
+ when Integer
41
+ find_by_id(what)
42
+ end
43
+ end
44
+
45
+ def find_by_id(id)
46
+ record = records.detect {|r| r.id == id }
47
+ raise(StaticModel::RecordNotFound, "Could not find record with id = #{id}") unless record
48
+ record
49
+ end
50
+
51
+ def [](id)
52
+ find_by_id(id)
53
+ end
54
+
55
+ def find_all
56
+ records
57
+ end
58
+ alias_method :all, :find_all
59
+
60
+ def find_first
61
+ records[0]
62
+ end
63
+ alias_method :first, :find_first
64
+
65
+ def find_all_by(attribute, value)
66
+ records.find_all {|r| r.send(attribute) == value }
67
+ end
68
+
69
+ def find_first_by(attribute, value)
70
+ records.find {|r| r.send(attribute) == value }
71
+ end
72
+ alias_method :find_by, :find_first_by
73
+
74
+ def load(reload = false)
75
+ return if loaded? && !reload
76
+ raise(StaticModel::DataFileNotFound, "You must set a data file to load from") unless File.readable?(data_file)
77
+ data = YAML::load_file(data_file)
78
+ records = []
79
+ if data.is_a?(Hash) && data.has_key?('records')
80
+ records = data.delete('records')
81
+ @class_attributes = HashWithIndifferentAccess.new(data)
82
+ elsif data.is_a?(Array)
83
+ records = data
84
+ end
85
+ @records = records && !records.empty? ? records.dup.collect {|r| new(r) } : []
86
+ @loaded = true
87
+ # rescue
88
+ # raise(StaticModel::BadDataFile, "The data file you specified '#{data_file}' was not in a readable format.")
89
+ end
90
+
91
+ def loaded?
92
+ @loaded ||= false
93
+ end
94
+
95
+ def data_file
96
+ @data_file ||= default_data_file_path
97
+ end
98
+
99
+ def set_data_file(file_path)
100
+ raise(StaticModel::DataFileNotFound, "Could not find data file #{file_path}") unless File.readable?(file_path)
101
+ @data_file = file_path
102
+ # force reload
103
+ @loaded = false
104
+ @records = nil
105
+ end
106
+
107
+ def count
108
+ records.length
109
+ end
110
+
111
+ def class_attributes
112
+ load
113
+ @class_attributes ||= {}
114
+ end
115
+
116
+ def class_attribute(name)
117
+ class_attributes[name]
118
+ end
119
+
120
+ def records
121
+ load
122
+ @records
123
+ end
124
+
125
+ protected
126
+ def default_data_file_path
127
+ File.join(@@load_path, "#{self.to_s.tableize}.yml")
128
+ end
129
+
130
+ private
131
+ def method_missing(meth, *args)
132
+ meth_name = meth.to_s
133
+ if meth_name =~ /^find_(all_by|first_by|by)_(.+)/
134
+ attribute_name = meth_name.gsub(/^find_(all_by|first_by|by)_/, '')
135
+ finder = meth_name.gsub(/_#{attribute_name}/, '')
136
+ return self.send(finder, attribute_name, *args)
137
+ elsif class_attributes.has_key? meth_name
138
+ return class_attributes[meth_name]
139
+ end
140
+ super
141
+ end
142
+ end
143
+
144
+ protected
145
+ def set_attribute(name, value)
146
+ self.attributes[name] = value
147
+ end
148
+
149
+ def get_attribute(name)
150
+ attributes.has_key?(name) ? attributes[name] : self.class.class_attribute(name)
151
+ end
152
+
153
+ private
154
+ def method_missing(meth, *args)
155
+ attribute_name = meth.to_s.gsub(/=$/,'')
156
+ if attribute_names.include?(attribute_name)
157
+ if meth.to_s =~ /=$/
158
+ # set
159
+ return set_attribute(attribute_name, args[0])
160
+ else
161
+ # get
162
+ return get_attribute(attribute_name)
163
+ end
164
+ end
165
+ super
166
+ end
167
+
168
+ end
169
+ end
@@ -0,0 +1,14 @@
1
+ module StaticModel
2
+ module Comparable
3
+ include ::Comparable
4
+
5
+ def <=>(other)
6
+ if self.class == other.class
7
+ self.id <=> other.id
8
+ else
9
+ -1
10
+ end
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ module StaticModel
2
+ class Error < RuntimeError; end;
3
+ class RecordNotFound < Error; end;
4
+ class DataFileNotFound < Error; end;
5
+ class BadDataFile < Error; end;
6
+ class BadOptions < Error; end;
7
+ end
@@ -0,0 +1,7 @@
1
+ if defined?(Rails)
2
+ module StaticModel
3
+ class Base
4
+ @@load_path = File.join(Rails.root,'config', 'data')
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,15 @@
1
+ module StaticModel
2
+ module Scope
3
+
4
+ private
5
+ class << self
6
+ def with_scope
7
+
8
+ end
9
+
10
+ def scoped_conditions
11
+ end
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ module StaticModel #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 2
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'yaml'
5
+
6
+ require 'active_support/inflector'
7
+ require 'active_support/core_ext/hash'
8
+ require 'active_support/core_ext/string'
9
+
10
+
11
+ require 'static_model/errors'
12
+ require 'static_model/associations'
13
+ require 'static_model/active_record'
14
+ require 'static_model/comparable'
15
+ require 'static_model/base'
16
+ require 'static_model/rails'
@@ -0,0 +1,29 @@
1
+ begin
2
+ require File.dirname(__FILE__) + '/test_helper'
3
+ rescue LoadError
4
+ require 'test/unit'
5
+ end
6
+ require 'fileutils'
7
+
8
+ # Must set before requiring generator libs.
9
+ TMP_ROOT = File.dirname(__FILE__) + "/tmp" unless defined?(TMP_ROOT)
10
+ PROJECT_NAME = "myproject" unless defined?(PROJECT_NAME)
11
+ app_root = File.join(TMP_ROOT, PROJECT_NAME)
12
+ if defined?(APP_ROOT)
13
+ APP_ROOT.replace(app_root)
14
+ else
15
+ APP_ROOT = app_root
16
+ end
17
+ if defined?(RAILS_ROOT)
18
+ RAILS_ROOT.replace(app_root)
19
+ else
20
+ RAILS_ROOT = app_root
21
+ end
22
+
23
+ begin
24
+ require 'rubigen'
25
+ rescue LoadError
26
+ require 'rubygems'
27
+ require 'rubigen'
28
+ end
29
+ require 'rubigen/helpers/generator_test_helper'
@@ -0,0 +1,80 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+ require 'shoulda'
4
+ require 'mocha'
5
+
6
+ require File.dirname(__FILE__) + '/../lib/static_model'
7
+
8
+ class Book < StaticModel::Base
9
+ set_data_file File.join(File.dirname(__FILE__), 'data', 'books.yml')
10
+ end
11
+
12
+ unless defined?(ActiveRecord::Base)
13
+ module ActiveRecord
14
+ class Base
15
+ def self.scoped(*args)
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ class Article < ActiveRecord::Base
22
+ end
23
+
24
+ class Publisher < StaticModel::Base
25
+ set_data_file File.join(File.dirname(__FILE__), 'data', 'publishers.yml')
26
+
27
+ has_many :authors
28
+ end
29
+
30
+ class Author < StaticModel::Base
31
+ set_data_file File.join(File.dirname(__FILE__), 'data','authors.yml')
32
+ has_many :books
33
+ has_many :articles
34
+ belongs_to :publisher
35
+ end
36
+
37
+ class Page < StaticModel::Base
38
+ set_data_file File.join(File.dirname(__FILE__), 'data', 'pages.yml')
39
+
40
+ end
41
+
42
+
43
+
44
+
45
+ class Test::Unit::TestCase
46
+
47
+ def assert_all(collection)
48
+ collection.each do |one|
49
+ assert yield(one), "#{one} is not true"
50
+ end
51
+ end
52
+
53
+
54
+ def assert_any(collection, &block)
55
+ has = collection.any? do |one|
56
+ yield(one)
57
+ end
58
+ assert has
59
+ end
60
+
61
+ def assert_ordered(array_of_ordered_items, message = nil, &block)
62
+ raise "Parameter must be an Array" unless array_of_ordered_items.is_a?(Array)
63
+ message ||= "Items were not in the correct order"
64
+ i = 0
65
+ # puts array_of_ordered_items.length
66
+ while i < (array_of_ordered_items.length - 1)
67
+ # puts "j"
68
+ a, b = array_of_ordered_items[i], array_of_ordered_items[i+1]
69
+ comparison = yield(a,b)
70
+ # raise "#{comparison}"
71
+ assert(comparison, message + " - #{a}, #{b}")
72
+ i += 1
73
+ end
74
+ end
75
+
76
+ def assert_set_of(klass, set)
77
+ assert set.respond_to?(:each), "#{set} is not a set (does not include Enumerable)"
78
+ assert_all(set) {|a| a.is_a?(klass) }
79
+ end
80
+ end
@@ -0,0 +1,350 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class TestStaticModel < Test::Unit::TestCase
4
+
5
+ context "StaticModel" do
6
+ context "A class that inherits from Base" do
7
+ context "an instance" do
8
+
9
+ setup do
10
+ @book = Book.new(book_params)
11
+ end
12
+
13
+ context "initialization" do
14
+
15
+ should "set attributes with a hash" do
16
+ assert @book.id
17
+ end
18
+
19
+ should "raise error if passed anything but hash" do
20
+ assert_raise(StaticModel::BadOptions) do
21
+ Book.new("im bad")
22
+ end
23
+ end
24
+ end
25
+
26
+ context "attributes" do
27
+ should "get attributes by methods" do
28
+ assert_equal book_params[:title], @book.title
29
+ assert_equal book_params[:genre], @book.genre
30
+ end
31
+
32
+ should "set attributes by methods" do
33
+ @book.title = 'New Title'
34
+ assert_equal 'New Title', @book.title
35
+ end
36
+ end
37
+
38
+ context "to_s" do
39
+ should "inspect" do
40
+ assert_equal @book.inspect, @book.to_s
41
+ end
42
+ end
43
+
44
+ context "comparing" do
45
+
46
+ should "be equal to an instance of the same class with same id" do
47
+ assert_equal @book, Book.new(book_params)
48
+ end
49
+
50
+ should "not be equal to an instance with the same class with different ids" do
51
+ assert_not_equal Book[1], @book
52
+ end
53
+
54
+ should "not be equal to an instance with different classes and the same ids" do
55
+ assert_not_equal Book[1], Author[1]
56
+ end
57
+ end
58
+
59
+ end
60
+
61
+ context "on the class" do
62
+ context "set data file" do
63
+ context "After the class is defined" do
64
+
65
+ setup do
66
+ @book = Book.find(1)
67
+ @author = Author.find(1)
68
+ @original_data_file = Book.data_file
69
+ @data_file = File.join(File.dirname(__FILE__), 'data', 'authors.yml')
70
+ Book.set_data_file @data_file
71
+ @new_book = Book.find(1)
72
+ end
73
+
74
+ should "set the @data_file" do
75
+ assert_equal @data_file, Book.data_file
76
+ end
77
+
78
+ should "reload with next find" do
79
+ assert @author.attributes, @new_book.attributes
80
+ end
81
+
82
+ teardown do
83
+ Book.set_data_file @original_data_file
84
+ end
85
+ end
86
+ end
87
+
88
+ context "find" do
89
+
90
+ context "with an integer" do
91
+ setup do
92
+ @book = Book.find(1)
93
+ end
94
+
95
+ should "set loaded?" do
96
+ assert Book.loaded?
97
+ end
98
+
99
+ should "load by id" do
100
+ assert_equal 1, @book.id
101
+ end
102
+
103
+ should "return instance of klass" do
104
+ assert @book.is_a?(Book)
105
+ end
106
+
107
+ should "raise error if cant find record with id" do
108
+ assert_raise(StaticModel::RecordNotFound) do
109
+ @book = Book.find(1000)
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ context "[]" do
116
+ should "be an alias for find by id" do
117
+ assert_equal Book.find(1), Book[1]
118
+ end
119
+ end
120
+
121
+ context "find(:all)" do
122
+ should "be an alias for find_all" do
123
+ assert_equal Book.find_all, Book.find(:all)
124
+ end
125
+ end
126
+ context "find(:first)" do
127
+ should "be an alias for find_first" do
128
+ assert_equal Book.find_first, Book.find(:first)
129
+ end
130
+ end
131
+
132
+ context "all" do
133
+ should "be an alias for find_all" do
134
+ assert_equal Book.all, Book.find(:all)
135
+ end
136
+ end
137
+ context "first" do
138
+ should "be an alias for find_first" do
139
+ assert_equal Book.first, Book.find(:first)
140
+ end
141
+ end
142
+
143
+ context "find_first" do
144
+ setup do
145
+ @book = Book.find_first
146
+ end
147
+
148
+ should "return the first instance from all records" do
149
+ assert_equal Book.find_all.first, @book
150
+ end
151
+
152
+ should "return instance of klass" do
153
+ assert @book.is_a?(Book)
154
+ end
155
+ end
156
+
157
+ context "find_all" do
158
+ setup do
159
+ @books = Book.find_all
160
+ end
161
+
162
+ should "return an array" do
163
+ assert @books.is_a?(Array)
164
+ end
165
+
166
+ should "return all records" do
167
+ assert_equal 4, @books.length
168
+ end
169
+
170
+ should "return set of klass instances" do
171
+ @books.each do |book|
172
+ assert book.is_a?(Book)
173
+ end
174
+ end
175
+
176
+ context "with an empty data file" do
177
+ setup do
178
+ @original_data_file = Book.data_file
179
+ @data_file = File.join(File.dirname(__FILE__), 'data', 'empty.yml')
180
+ Book.set_data_file @data_file
181
+ end
182
+
183
+ should "return an empty array" do
184
+ assert_equal [], Book.find_all
185
+ end
186
+
187
+ teardown do
188
+ Book.set_data_file @original_data_file
189
+ end
190
+
191
+ end
192
+ end
193
+
194
+ context "find_first_by" do
195
+ setup do
196
+ @author = 'Michael Pollan'
197
+ @book = Book.find_first_by(:author,@author)
198
+ end
199
+
200
+ should "return an instance of klass" do
201
+ assert @book.is_a?(Book)
202
+ end
203
+
204
+ should "return record matching search" do
205
+ assert @author, @book.author
206
+ end
207
+
208
+ context "when there is no match" do
209
+ should "return nil" do
210
+ assert_nil Book.find_first_by(:author,'Aaron Quint')
211
+ end
212
+ end
213
+ end
214
+
215
+ context "find_by" do
216
+ setup do
217
+ @author = 'Michael Pollan'
218
+ @book = Book.find_by(:author,@author)
219
+ end
220
+
221
+ should "return an instance of klass" do
222
+ assert @book.is_a?(Book)
223
+ end
224
+
225
+ should "return record matching search" do
226
+ assert @author, @book.author
227
+ end
228
+
229
+ context "when there is no match" do
230
+ should "return nil" do
231
+ assert_nil Book.find_by(:author,'Aaron Quint')
232
+ end
233
+ end
234
+
235
+ end
236
+
237
+ context "find_all_by" do
238
+ setup do
239
+ @author = 'Michael Pollan'
240
+ @books = Book.find_all_by(:author,@author)
241
+ end
242
+
243
+ should "return an array" do
244
+ assert @books.is_a?(Array)
245
+ end
246
+
247
+ should "return all records that match search" do
248
+ @books.each {|b| assert_equal @author, b.author}
249
+ end
250
+
251
+ should "return set of klass instances" do
252
+ @books.each {|b| assert b.is_a?(Book) }
253
+ end
254
+
255
+ context "when there is no match" do
256
+ should "return an empty array" do
257
+ assert_equal [], Book.find_all_by(:author,'Aaron Quint')
258
+ end
259
+ end
260
+ context "when there is only one match" do
261
+ should "return an array" do
262
+ assert_equal [Book.find(3)], Book.find_all_by(:author,'Piers Anthony')
263
+ end
264
+ end
265
+ end
266
+
267
+ context "dynamic finders" do
268
+ setup do
269
+ @book = Book.first
270
+ end
271
+
272
+ context "find_by_*attribute*" do
273
+ should "be equivalent to find_first_by(attribute,)" do
274
+ assert_equal @book, Book.find_first_by(:genre, 'Non-Fiction')
275
+ assert_equal Book.find_first_by(:genre, 'Non-Fiction'), Book.find_by_genre('Non-Fiction')
276
+ end
277
+ end
278
+
279
+ context "find_first_by_*attribute*" do
280
+ should "be equivalent to find_first_by(attribute,)" do
281
+ assert_equal @book, Book.find_first_by(:genre, 'Non-Fiction')
282
+ assert_equal Book.find_first_by(:genre, 'Non-Fiction'), Book.find_first_by_genre('Non-Fiction')
283
+ end
284
+ end
285
+
286
+ context "find_all_by_*attribute*" do
287
+ should "be equivalent to find_all_by(attribute,)" do
288
+ assert_equal Book.find_all_by(:genre, 'Non-Fiction'), Book.find_all_by_genre('Non-Fiction')
289
+ end
290
+ end
291
+ end
292
+
293
+ context "count" do
294
+ should "return the count of all records" do
295
+ assert_equal Book.all.length, Book.count
296
+ end
297
+ end
298
+
299
+ context "with a class with yaml class vars" do
300
+ setup do
301
+ @pages = Page.all
302
+ end
303
+
304
+ should "load :records into @records" do
305
+ assert_set_of Page, @pages
306
+ assert Page.loaded?
307
+ end
308
+
309
+ should "give access to top level attributes as class methods" do
310
+ assert_equal 'http://www.quirkey.com', Page.url
311
+ assert_equal 'The Best Ever', Page.title
312
+ end
313
+
314
+ should "return a hash for class attribute" do
315
+ assert Page.settings.is_a?(Hash)
316
+ assert_equal 'test', Page.settings['username']
317
+ end
318
+
319
+ should "have class attributes appear as record accessor defaults if none exist" do
320
+ assert_equal 'http://www.quirkey.com', Page[1].url
321
+ end
322
+
323
+ should "not overwrite record specific methods" do
324
+ assert_equal 'http://github.com', Page[2].url
325
+ end
326
+
327
+ context "class_attributes" do
328
+ setup do
329
+ @attributes = Page.class_attributes
330
+ end
331
+
332
+ should "return a hash of all top level settings" do
333
+ assert @attributes.is_a?(Hash)
334
+ assert_equal 3, @attributes.length
335
+ assert_equal 'http://www.quirkey.com', @attributes['url']
336
+ end
337
+ end
338
+ end
339
+
340
+ end
341
+ end
342
+ end
343
+
344
+ protected
345
+ def book_params
346
+ {:id => 15, :title => 'Lord of the Rings', :author => 'J.R. Tolkien', :genre => 'Fantasy'}
347
+ end
348
+
349
+
350
+ end
@@ -0,0 +1,93 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class TestStaticModelAssociations < Test::Unit::TestCase
4
+
5
+ context "a static model class" do
6
+
7
+ context "with a has many association" do
8
+ context "to another static model" do
9
+ # Author has many books
10
+ setup do
11
+ @author = Author.find(1)
12
+ end
13
+
14
+ should "respond to association name" do
15
+ assert @author.books
16
+ end
17
+
18
+ should "return an array of association instances if association is a StaticModel" do
19
+ assert_set_of Book, @author.books
20
+ end
21
+
22
+ should "find books by foreign_key" do
23
+ assert_equal Book.find_all_by_author_id(@author.id), @author.books
24
+ end
25
+
26
+ should "add association to associations" do
27
+ assert Author.associations.has_key?(:books)
28
+ end
29
+
30
+ should "have HasManyAssociation in associations" do
31
+ assert Author.associations[:books].is_a?(StaticModel::Associations::HasManyAssociation)
32
+ end
33
+ end
34
+
35
+ context "to an active record model" do
36
+ setup do
37
+ @author = Author.find(1)
38
+ Article.expects(:scoped).returns([Article.new, Article.new])
39
+ end
40
+
41
+ should "respond to association name" do
42
+ assert @author.articles
43
+ end
44
+
45
+ should "return an array of association instances if association is a StaticModel" do
46
+ assert_set_of Article, @author.articles
47
+ end
48
+
49
+ end
50
+ end
51
+
52
+
53
+ context "with a belongs to association" do
54
+ context "to another static model" do
55
+ # Author belongs to publisher
56
+ setup do
57
+ @author = Author.find(1)
58
+ end
59
+
60
+ should "respond to association name" do
61
+ assert @author.publisher
62
+ end
63
+
64
+ should "return a single instance" do
65
+ assert @author.publisher.is_a?(Publisher)
66
+ end
67
+
68
+ should "find publisher by foreign key" do
69
+ assert_equal Publisher.find(1), @author.publisher
70
+ end
71
+
72
+ should "add association to associations" do
73
+ assert Author.associations.has_key?(:publisher)
74
+ end
75
+
76
+ should "have BelongsTo association in associations" do
77
+ assert Author.associations[:publisher].is_a?(StaticModel::Associations::BelongsToAssociation)
78
+ end
79
+
80
+ context "when foreign key is nil" do
81
+ setup do
82
+ @author = Author.find(2)
83
+ end
84
+
85
+ should "respond to association name and return nil" do
86
+ assert_nil @author.publisher
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ end
93
+ end
@@ -0,0 +1,48 @@
1
+ require File.join(File.dirname(__FILE__), "test_generator_helper.rb")
2
+
3
+
4
+ class TestStaticModelGenerator < Test::Unit::TestCase
5
+ include RubiGen::GeneratorTestHelper
6
+
7
+ def setup
8
+ bare_setup
9
+ end
10
+
11
+ def teardown
12
+ bare_teardown
13
+ end
14
+
15
+ # Some generator-related assertions:
16
+ # assert_generated_file(name, &block) # block passed the file contents
17
+ # assert_directory_exists(name)
18
+ # assert_generated_class(name, &block)
19
+ # assert_generated_module(name, &block)
20
+ # assert_generated_test_for(name, &block)
21
+ # The assert_generated_(class|module|test_for) &block is passed the body of the class/module within the file
22
+ # assert_has_method(body, *methods) # check that the body has a list of methods (methods with parentheses not supported yet)
23
+ #
24
+ # Other helper methods are:
25
+ # app_root_files - put this in teardown to show files generated by the test method (e.g. p app_root_files)
26
+ # bare_setup - place this in setup method to create the APP_ROOT folder for each test
27
+ # bare_teardown - place this in teardown method to destroy the TMP_ROOT or APP_ROOT folder after each test
28
+
29
+ def test_generator_with_name_as_a_class_name
30
+ name = "StaticPage"
31
+ run_generator('static_model', [name], sources)
32
+ assert_directory_exists 'config/data'
33
+ assert_generated_file 'config/data/static_pages.yml'
34
+ assert_generated_file 'app/models/static_page.rb' do |content|
35
+ assert_match(/class StaticPage/, content)
36
+ end
37
+ end
38
+
39
+ private
40
+ def sources
41
+ [RubiGen::PathSource.new(:test, File.join(File.dirname(__FILE__),"..", generator_path))
42
+ ]
43
+ end
44
+
45
+ def generator_path
46
+ "generators"
47
+ end
48
+ end
@@ -0,0 +1,14 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class TestStaticModelScope < Test::Unit::TestCase
4
+
5
+ # context "a static model class" do
6
+ # context "A class with scope" do
7
+ #
8
+ # end
9
+ # end
10
+
11
+ def test_truth
12
+ assert true
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: quirkey-static_model
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Aaron Quint
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-11-28 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activesupport
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 2.1.0
23
+ version:
24
+ - !ruby/object:Gem::Dependency
25
+ name: hoe
26
+ version_requirement:
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 1.8.0
32
+ version:
33
+ description: ActiveRecord like functionalities for reading from YAML with a simple class implementation
34
+ email:
35
+ - aaron@quirkey.com
36
+ executables: []
37
+
38
+ extensions: []
39
+
40
+ extra_rdoc_files:
41
+ - History.txt
42
+ - License.txt
43
+ - Manifest.txt
44
+ - README.rdoc
45
+ files:
46
+ - History.txt
47
+ - License.txt
48
+ - Manifest.txt
49
+ - README.rdoc
50
+ - generators/static_model/USAGE
51
+ - generators/static_model/static_model_generator.rb
52
+ - generators/static_model/templates/model.rb.erb
53
+ - generators/static_model/templates/models.yml.erb
54
+ - lib/static_model.rb
55
+ - lib/static_model/active_record.rb
56
+ - lib/static_model/associations.rb
57
+ - lib/static_model/base.rb
58
+ - lib/static_model/comparable.rb
59
+ - lib/static_model/errors.rb
60
+ - lib/static_model/rails.rb
61
+ - lib/static_model/scope.rb
62
+ - lib/static_model/version.rb
63
+ - test/test_generator_helper.rb
64
+ - test/test_helper.rb
65
+ - test/test_static_model.rb
66
+ - test/test_static_model_associations.rb
67
+ - test/test_static_model_generator.rb
68
+ - test/test_static_model_scope.rb
69
+ has_rdoc: true
70
+ homepage: http://quirkey.rubyforge.org
71
+ post_install_message: ""
72
+ rdoc_options:
73
+ - --main
74
+ - README.rdoc
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: "0"
82
+ version:
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: "0"
88
+ version:
89
+ requirements: []
90
+
91
+ rubyforge_project: quirkey
92
+ rubygems_version: 1.2.0
93
+ signing_key:
94
+ specification_version: 2
95
+ summary: ActiveRecord like functionalities for reading from YAML with a simple class implementation
96
+ test_files:
97
+ - test/test_generator_helper.rb
98
+ - test/test_helper.rb
99
+ - test/test_static_model.rb
100
+ - test/test_static_model_associations.rb
101
+ - test/test_static_model_generator.rb
102
+ - test/test_static_model_scope.rb