norman 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ module Norman
2
+ module Version
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ TINY = 0
6
+ BUILD = nil
7
+ STRING = [MAJOR, MINOR, TINY, BUILD].compact.join('.')
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ require "rack/contrib"
2
+
3
+ module Rack
4
+ # Rack::Norman is a middleware that allows you to store a Norman datbase
5
+ # in a cookie.
6
+ # @see Norman::Adapters::Cookie
7
+ class Norman
8
+ def initialize(app, options = {})
9
+ @app = app
10
+ @norman = ::Norman::Adapters::Cookie.new(options.merge(:sync => true))
11
+ end
12
+
13
+ def call(env)
14
+ @norman.data = env["rack.cookies"]["norman_data"]
15
+ @norman.load_database
16
+ status, headers, body = @app.call(env)
17
+ env["rack.cookies"]["norman_data"] = @norman.export_data
18
+ [status, headers, body]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,25 @@
1
+ require File.expand_path("../lib/norman/version", __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.authors = "Norman Clarke"
5
+ s.email = "norman@njclarke.com"
6
+ s.files = `git ls-files`.split("\n").reject {|f| f =~ /^\./}
7
+ s.has_rdoc = true
8
+ s.homepage = "http://github.com/norman/norman"
9
+ s.name = "norman"
10
+ s.platform = Gem::Platform::RUBY
11
+ s.rubyforge_project = "[none]"
12
+ s.summary = "An ActiveModel-compatible ORM-like library for storing model instances in an in-memory Hash."
13
+ s.test_files = Dir.glob "test/**/*_test.rb"
14
+ s.version = Norman::Version::STRING
15
+ s.description = <<-EOD
16
+ Norman is not an ORM, man! It's a database and ORM replacement for (mostly)
17
+ static models and small datasets. It provides ActiveModel compatibility, and
18
+ flexible searching and storage.
19
+ EOD
20
+ s.add_development_dependency "ffaker"
21
+ s.add_development_dependency "minitest", "~> 2.2.2"
22
+ s.add_development_dependency "mocha"
23
+ s.add_development_dependency "activesupport", "~> 3.0"
24
+ s.add_development_dependency "activemodel", "~> 3.0"
25
+ end
@@ -0,0 +1,115 @@
1
+ require File.expand_path("../spec_helper", __FILE__)
2
+ require "norman/active_model"
3
+
4
+ class Book
5
+ extend Norman::Model
6
+ extend Norman::ActiveModel
7
+ field :slug, :title, :author
8
+ validates_presence_of :slug
9
+ validates_uniqueness_of :slug, :title
10
+ before_save :save_callback_fired
11
+ before_destroy :destroy_callback_fired
12
+
13
+ def save_callback_fired
14
+ @save_callback_fired = true
15
+ end
16
+
17
+ def destroy_callback_fired
18
+ @destroy_callback_fired = true
19
+ end
20
+ end
21
+
22
+ module ActiveModuleSupportSpecHelper
23
+ def valid_book
24
+ {:slug => "war-and-peace", :title => "War and Peace", :author => "Leo Tolstoy"}
25
+ end
26
+
27
+ def load_fixtures
28
+ Norman.adapters.clear
29
+ Norman::Adapter.new :name => :main
30
+ Book.use :main
31
+ @model = Book.create! valid_book
32
+ end
33
+ end
34
+
35
+ describe Norman::ActiveModel do
36
+
37
+ before { load_fixtures }
38
+
39
+ include ActiveModuleSupportSpecHelper
40
+ include ActiveModel::Lint::Tests
41
+
42
+ describe ".model_name" do
43
+ it "should return an ActiveModel::Name" do
44
+ assert_kind_of ::ActiveModel::Name, Book.model_name
45
+ end
46
+ end
47
+
48
+ describe "#keys" do
49
+ it "should return an array of attribute names" do
50
+ assert @model.keys.include?(:slug), "@model.keys should include :slug"
51
+ end
52
+ end
53
+
54
+ describe "#save!" do
55
+ it "should raise an exception if the model is not valid" do
56
+ assert_raises Norman::NormanError do
57
+ Book.new.save!
58
+ end
59
+ end
60
+ end
61
+
62
+ describe "#to_json" do
63
+ it "should serialize" do
64
+ json = @model.to_json
65
+ refute_nil @model.to_json
66
+ assert_match /"author":"Leo Tolstoy"/, json
67
+ end
68
+ end
69
+
70
+ describe "#to_xml" do
71
+ it "should serialize to XML" do
72
+ xml = @model.to_xml
73
+ refute_nil xml
74
+ assert_match /<author>Leo Tolstoy<\/author>/, xml
75
+ end
76
+ end
77
+
78
+ describe "#valid?" do
79
+ it "should do validation" do
80
+ book = Book.new
81
+ refute book.valid?
82
+ book.slug = "hello-world"
83
+ assert book.valid?
84
+ end
85
+ end
86
+
87
+ describe "callbacks" do
88
+ it "should fire save callbacks" do
89
+ book = Book.new valid_book
90
+ book.save
91
+ assert book.instance_variable_defined? :@save_callback_fired
92
+ end
93
+
94
+ it "should fire destroy callbacks" do
95
+ @model.destroy
96
+ assert @model.instance_variable_defined? :@destroy_callback_fired
97
+ end
98
+ end
99
+
100
+ describe ".validates_uniqueness_of" do
101
+ it "should validate on id attribute" do
102
+ @book = Book.new valid_book.merge(:title => "War and Peace II")
103
+ refute @book.valid?
104
+ @book.slug = "war-and-peace-2"
105
+ assert @book.valid?
106
+ end
107
+
108
+ it "should validate on non-id attribute" do
109
+ @book = Book.new valid_book.merge(:slug => "war-and-peace-2")
110
+ refute @book.valid?
111
+ @book.title = "War and Peace II"
112
+ assert @book.valid?
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,48 @@
1
+ require File.expand_path("../spec_helper", __FILE__)
2
+
3
+ describe Norman::Adapter do
4
+
5
+ before { Norman.adapters.clear }
6
+ after { Norman.adapters.clear }
7
+
8
+ describe "#initialize" do
9
+
10
+ it "should register itself" do
11
+ Norman::Adapter.new :name => :an_adapter
12
+ assert_equal :an_adapter, Norman.adapters.keys.first
13
+ end
14
+
15
+ it "should use a default name if none given" do
16
+ assert_equal Norman.default_adapter_name, Norman::Adapter.new.name
17
+ end
18
+
19
+ it "should raise error if a duplicate name is used" do
20
+ assert_raises Norman::NormanError do
21
+ 2.times {Norman::Adapter.new(:name => :test_adapter)}
22
+ end
23
+ end
24
+
25
+ it "should set an empty hash as the db" do
26
+ assert_equal Hash.new, Norman::Adapter.new.db
27
+ end
28
+ end
29
+
30
+ describe "#db_for" do
31
+
32
+ before { load_fixtures }
33
+
34
+ it "should return a instance of Hash" do
35
+ adapter = Norman.adapters[:main]
36
+ assert_kind_of Hash, adapter.db_for(Person)
37
+ end
38
+ end
39
+
40
+ describe "stubbed io operations" do
41
+ it "should return true" do
42
+ adapter = Norman::Adapter.new
43
+ [:export_data, :import_data, :save_database].each do |method|
44
+ assert adapter.send method
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,81 @@
1
+ require File.expand_path("../spec_helper", __FILE__)
2
+ require "norman/adapters/cookie"
3
+
4
+ class User
5
+ extend Norman::Model
6
+ field :email, :name
7
+ end
8
+
9
+ module CookieAdapterSpecHelpers
10
+ def secret
11
+ "ssssshh... this is a secret!"
12
+ end
13
+
14
+ def sample_data
15
+ # hash = {"User" => {valid_user[:email] => valid_user}}
16
+ # p ActiveSupport::MessageVerifier.new(secret).generate(Zlib::Deflate.deflate(Marshal.dump(hash)))
17
+ "BAgiTnicY+GoZvNU4gwtTi1is2JzDQHxhLPyUx2KkzNy81P1kvNz2awZQqrZrTjzEnNTPZX4" +
18
+ "vfJTFYLBkiAJK67U3MTMHKyaAJGaGlk=--08913fe1c677e4bb0dd34ef90fb22f9027e587f4"
19
+ end
20
+
21
+ def valid_user
22
+ @valid_user ||= {:name => "Joe Schmoe", :email => "joe@schmoe.com"}
23
+ end
24
+
25
+ def load_fixtures
26
+ Norman.adapters.clear
27
+ @adapter = Norman::Adapters::Cookie.new \
28
+ :name => :cookie,
29
+ :secret => secret
30
+ User.use :cookie, :sync => true
31
+ end
32
+ end
33
+
34
+ describe Norman::Adapters::Cookie do
35
+
36
+ include CookieAdapterSpecHelpers
37
+
38
+ before { load_fixtures }
39
+ after { Norman.adapters.clear }
40
+
41
+ describe Norman::Adapters::Cookie do
42
+
43
+ describe "#initialize" do
44
+ it "should decode signed data if given" do
45
+ adapter = Norman::Adapters::Cookie.new \
46
+ :secret => secret,
47
+ :data => sample_data
48
+ assert_kind_of Hash, adapter.db["User"]
49
+ assert_equal "joe@schmoe.com", adapter.db["User"].keys.first
50
+ end
51
+
52
+ it "should load properly with nil or blank data" do
53
+ [nil, ""].each_with_index do |arg, index|
54
+ adapter = Norman::Adapters::Cookie.new \
55
+ :secret => secret,
56
+ :data => arg,
57
+ :name => :"main_#{index}"
58
+ assert_instance_of Hash, adapter.db
59
+ end
60
+ end
61
+ end
62
+
63
+ describe "#export_data" do
64
+ it "should encode and sign the database" do
65
+ User.create \
66
+ :name => Faker::Name.name,
67
+ :email => Faker::Internet.email
68
+ refute_nil @adapter.export_data
69
+ end
70
+ end
71
+
72
+ describe "#save_database" do
73
+ it "should raise a NormanError if signed data exceeds max data length" do
74
+ Norman::Adapters::Cookie.stubs(:max_data_length).returns(1)
75
+ assert_raises Norman::NormanError do
76
+ User.create valid_user
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,48 @@
1
+ # encoding: utf-8
2
+ require File.expand_path("../spec_helper", __FILE__)
3
+
4
+ classes = [Norman::Adapters::File, Norman::Adapters::YAML]
5
+
6
+ classes.each do |klass|
7
+
8
+ describe klass.to_s do
9
+
10
+ before do
11
+ Norman.adapters.clear
12
+ @path = File.expand_path("../file_adapter_test", __FILE__)
13
+ @adapter = klass.new(:file => @path)
14
+ @adapter.instance_variable_set :@db, {
15
+ "Class" => {
16
+ :a => :b,
17
+ :unicode => "ü"
18
+ }
19
+ }
20
+ end
21
+
22
+ after do
23
+ Norman.adapters.clear
24
+ FileUtils.rm_f @path
25
+ end
26
+
27
+ describe "#export_data" do
28
+ it "should be a string" do
29
+ assert_kind_of String, @adapter.export_data
30
+ end
31
+ end
32
+
33
+ describe "#save_database" do
34
+ it "should write the data to disk" do
35
+ assert @adapter.save_database
36
+ assert File.exists? @path
37
+ end
38
+ end
39
+
40
+ describe "#load_database" do
41
+ it "should load the data from the filesystem" do
42
+ @adapter.save_database
43
+ a2 = @adapter.class.new(:name => "a2", :file => @path)
44
+ assert_equal @adapter.db["Class"][:unicode].bytes.entries, a2.db["Class"][:unicode].bytes.entries
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,18 @@
1
+ ---
2
+ Person:
3
+ moe@3stooges.com:
4
+ :name: Moe Howard
5
+ :email: moe@3stooges.com
6
+ shemp@3stooges.com:
7
+ :name: Shemp Howard
8
+ :email: shemp@3stooges.com
9
+ curly@3stooges.com:
10
+ :name: Curly Howard
11
+ :email: curly@3stooges.com
12
+ larry@3stooges.com:
13
+ :name: Larry Fine
14
+ :email: larry@3stooges.com
15
+ "MyModule::Animal":
16
+ Canis Familaris:
17
+ :species: Canis Familaris
18
+ :common_name: Dog
@@ -0,0 +1,104 @@
1
+ require File.expand_path("../spec_helper", __FILE__)
2
+
3
+ describe Norman::AbstractKeySet do
4
+
5
+ before { load_fixtures }
6
+ after { Norman.adapters.clear }
7
+
8
+ describe "#+" do
9
+ it "should add two key sets" do
10
+ key_set = Person.find {|p| p.name =~ /Curly/} + Person.find {|p| p.name =~ /Larry/}
11
+ assert_equal 2, key_set.length
12
+ end
13
+
14
+ it "should not duplicate entries" do
15
+ key_set = Person.find {|p| p.name =~ /Curly/} + Person.find {|p| p.name =~ /Curly/}
16
+ assert_equal 1, key_set.length
17
+ end
18
+ end
19
+
20
+ describe "#-" do
21
+ it "should subtract a key set" do
22
+ a = Person.find
23
+ b = Person.find {|p| p.name =~ /Larry|Ted/}
24
+ key_set = a - b
25
+ assert_equal 3, key_set.length
26
+ end
27
+ end
28
+
29
+ describe "#&" do
30
+ it "should get set intersection" do
31
+ key_set = Person.find & Person.find {|p| p.name =~ /Larry/}
32
+ assert_equal 1, key_set.length
33
+ end
34
+ end
35
+
36
+ describe "#first" do
37
+ it "should return the first matching instance when called with a block" do
38
+ assert_equal "Curly Howard", Person.first {|p| p.name =~ /Curly/}.name
39
+ end
40
+
41
+ it "should return the first instance when not called with a block" do
42
+ assert_kind_of Person, Person.first
43
+ end
44
+ end
45
+
46
+ describe "#count" do
47
+ it "should count matching instances when called with a block" do
48
+ assert_equal 3, Person.count {|p| p.name =~ /Howard/}
49
+ end
50
+
51
+ it "should count all keys when called without a block" do
52
+ assert_equal 4, Person.count
53
+ end
54
+ end
55
+
56
+ describe "#find" do
57
+ it "should return a KeySet of matching keys when called with a block" do
58
+ key_set = Person.find {|p| p.name =~ /Larry/}
59
+ assert_kind_of Norman::AbstractKeySet, key_set
60
+ assert_equal 1, key_set.size
61
+ end
62
+
63
+ it "should return a KeySet of all keys when called with no block" do
64
+ assert_equal 4, Person.find.size
65
+ end
66
+
67
+ it "should yield an instance of HashProxy to the block" do
68
+ Person.find {|x| assert_kind_of Norman::HashProxy, x}
69
+ end
70
+
71
+ it "should be chainable" do
72
+ assert_equal "Larry Fine", Person.stooges.non_howards.first.name
73
+ end
74
+
75
+ it "should raise error when trying to chain nonexistant method" do
76
+ assert_raises NoMethodError do
77
+ Person.stooges.foobar
78
+ end
79
+ end
80
+ end
81
+
82
+ describe "#find_by_key" do
83
+ it "should yield a key to the block" do
84
+ Person.find_by_key {|x| assert_kind_of String, x}
85
+ end
86
+
87
+ it "should return a KeySet of all keys when called with no block" do
88
+ assert_equal 4, Person.find_by_key.size
89
+ end
90
+ end
91
+
92
+ describe "#sort" do
93
+ it "should sort" do
94
+ assert_equal "Curly Howard", Person.find.sort {|a, b| a.name <=> b.name}.first.name
95
+ assert_equal "Shemp Howard", Person.find.sort {|b, a| a.name <=> b.name}.first.name
96
+ end
97
+ end
98
+
99
+ describe "#limit" do
100
+ it "should limit" do
101
+ assert_equal 2, Person.find.limit(2).count
102
+ end
103
+ end
104
+ end