kabuki-heresy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 kabuki
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.
@@ -0,0 +1,50 @@
1
+ = heresy
2
+
3
+ Heresy is a schema free wrapper around your database, heavily inspired by both CouchDB and FriendFeed[1]. You create Heresy models that work with generic schema-less tables.
4
+
5
+ class Entry < Heresy::Model
6
+ fields do |f|
7
+ f.string :title
8
+ end
9
+ end
10
+
11
+ Entry.schema.create # create the table in the database
12
+
13
+ This class will use a table that looks like this:
14
+
15
+ create_table :entries do |t|
16
+ t.primary_key :id, :int
17
+ t.column :uuid, :binary
18
+ t.column :updated_at, :timestamp
19
+ t.column :body, :blob
20
+ end
21
+
22
+ Now you can create and retrieve models:
23
+
24
+ @entry = Entry.new :title => 'testing'
25
+ @entry.save
26
+
27
+ @entry = Entry.find('some_entry_uuid')
28
+ @entry.body = "updated"
29
+ @entry.save
30
+
31
+ UUIDs are automatically generated and used as the main ID for each record.
32
+
33
+ 1: http://bret.appspot.com/entry/how-friendfeed-uses-mysql
34
+
35
+ == TODO
36
+
37
+ - Indexes
38
+ - Associations
39
+ - Timezone conversions
40
+
41
+ == NOT TODO
42
+
43
+ - Validations
44
+ - Callbacks
45
+
46
+ Use Validateable, ActiveSupport::Callbacks, ActiveModel, etc
47
+
48
+ == Copyright
49
+
50
+ Copyright (c) 2009 kabuki. See LICENSE for details.
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 1
4
+ :patch: 0
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ gem 'activesupport', '~> 2.2.0'
3
+ gem 'sequel', '~> 2.10.0'
4
+ gem 'uuid', '~> 2.0.1'
5
+ require 'heresy'
@@ -0,0 +1,18 @@
1
+ $LOAD_PATH << File.join(File.dirname(__FILE__))
2
+
3
+ %w(array/extract_options).each { |lib| require "active_support/core_ext/#{lib}" }
4
+ class Array #:nodoc:
5
+ include ActiveSupport::CoreExtensions::Array::ExtractOptions
6
+ end
7
+
8
+ %w(basic_object time_with_zone values/time_zone inflector core_ext/object core_ext/duplicable core_ext/blank core_ext/class core_ext/module core_ext/date core_ext/numeric core_ext/time duration).each { |lib| require "active_support/#{lib}" }
9
+ %w(uuid sequel_core tzinfo heresy/schema heresy/schema/column heresy/schema/fields heresy/index heresy/model).each { |lib| require lib }
10
+
11
+ module Heresy
12
+ class << self
13
+ attr_accessor :time_zone
14
+ attr_accessor :db
15
+ end
16
+
17
+ Time.zone = self.time_zone = "UTC"
18
+ end
@@ -0,0 +1,28 @@
1
+ require 'json'
2
+ require 'zlib'
3
+ require 'stringio'
4
+
5
+ module Heresy
6
+ module Formatting
7
+ module Json
8
+ DATE_REGEX = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/
9
+
10
+ extend self
11
+ def encode(body)
12
+ s = StringIO.new
13
+ z = Zlib::GzipWriter.new(s)
14
+ z.write body.to_json
15
+ z.close
16
+ s.string
17
+ end
18
+
19
+ def decode(body)
20
+ s = StringIO.new(body)
21
+ z = Zlib::GzipReader.new(s)
22
+ hash = JSON.parse(z.read)
23
+ z.close
24
+ hash
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,135 @@
1
+ module Heresy
2
+ # This class is responsible for defining the index tables that models use.
3
+ # Since model data is stored as an encoded string, it is impossible to perform
4
+ # any searches or queries on it. Therefore, separate tables with these data
5
+ # relationships must be created.
6
+ #
7
+ # @index = Index.new(Heresy::Model.db, :entry_titles, :entry_id)
8
+ # @index.field :title, :varchar, :size => 255
9
+ # @index.create
10
+ #
11
+ # This will create a simple table that looks something like this:
12
+ #
13
+ # create_table :entry_titles do |t|
14
+ # t.column :entry_id, :binary
15
+ # t.column :title, :varchar
16
+ # t.primary_key [:title, :entry_id]
17
+ # end
18
+ #
19
+ # You can then fill the index as models are saved:
20
+ #
21
+ # @entry.save
22
+ # @index << {:entry_id => @entry.uuid, :title => @entry.title}
23
+ #
24
+ # Once the index is filled, you can perform searches:
25
+ #
26
+ # uuids = @index.find(:title => "Test")
27
+ # entries = Entry.find(uuids)
28
+ #
29
+ class Index
30
+ attr_accessor :name, :key
31
+ attr_reader :fields, :field_names, :uuid_fields
32
+
33
+ def initialize(db, name, key)
34
+ @db = db
35
+ @name = name
36
+ @key = Heresy::Schema::Column.new(key, :uuid, :unique => true)
37
+ @fields = []
38
+ @field_names = {}
39
+ @uuid_fields = [@key.name]
40
+ yield self if block_given?
41
+ end
42
+
43
+ # Inserts the given data into the index. The columns should probably
44
+ # match the Index's schema, or you'll probably see an error.
45
+ #
46
+ # @index << {:entry_id => @entry.uuid, :title => @entry.title}
47
+ #
48
+ def insert(data)
49
+ @uuid_fields.each do |name|
50
+ if data.key?(name)
51
+ data[name] = data[name].to_a.pack("H*")
52
+ end
53
+ end
54
+ dataset << data
55
+ end
56
+
57
+ alias << insert
58
+
59
+ # Clears all data from the index.
60
+ def clear
61
+ dataset.delete
62
+ end
63
+
64
+ # Counts the number of indexed entries.
65
+ def count
66
+ dataset.count
67
+ end
68
+
69
+ # Returns all the UUIDs from a given query. The query is first passed to #filter.
70
+ def find(params)
71
+ filter(params).to_a.map! { |row| row[@key.name].unpack("H*").first }
72
+ end
73
+
74
+ # Filters the Sequel dataset object with the given query. The resulting dataset
75
+ # can be modified further to introduce ordering or more advanced filtering if desired.
76
+ def filter(options)
77
+ dataset.filter(options).select(@key.name)
78
+ end
79
+
80
+ # Index schema methods
81
+
82
+ # Adds a field to the index. By default, a :uuid type is used. All
83
+ # model-to-model associations are done via UUID.
84
+ def field(name, type = :uuid, options = {})
85
+ col = Heresy::Schema::Column.new(name, type, options)
86
+ @fields << col
87
+ @uuid_fields << col.name if col.type == :uuid
88
+ @field_names[name] = col
89
+ end
90
+
91
+ # Index database creation/deletion
92
+
93
+ # Checks if the Index table exists in the database.
94
+ def exists?
95
+ @db.table_exists?(@name)
96
+ end
97
+
98
+ # Creates the Index table
99
+ def create
100
+ to_sql.each { |sql| @db.execute_ddl(sql) }
101
+ end
102
+
103
+ # Drops the index table
104
+ def drop
105
+ @db.drop_table @name
106
+ end
107
+
108
+ # Returns the SQL that will be used to create the Index table.
109
+ def to_sql
110
+ @db.create_table_sql_list @name, *generate.create_info
111
+ end
112
+
113
+ def inspect
114
+ "<Heresy::Index:#{@name} #{@fields.map { |f| f.name } * ", "}>"
115
+ end
116
+
117
+ protected
118
+ def dataset
119
+ @dataset ||= @db[@name].select(@key.name)
120
+ end
121
+
122
+ def generate
123
+ gen = Sequel::Schema::Generator.new(@db)
124
+ @key.create(gen)
125
+ keys = []
126
+ @fields.each do |field|
127
+ keys << field.name
128
+ field.create(gen)
129
+ end
130
+ keys << @key.name
131
+ gen.primary_key keys
132
+ gen
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,188 @@
1
+ module Heresy
2
+ # This class is responsible for storing and retrieving the model data in a schema-free manner.
3
+ class Model
4
+ class << self
5
+ attr_writer :db, :schema, :formatter
6
+
7
+ # Accesses the database schema for this model. The basic schema will look something like this:
8
+ #
9
+ # create_table :entries do |t|
10
+ # t.primary_key :id, :int
11
+ # t.column :uuid, :binary
12
+ # t.column :updated_at, :timestamp
13
+ # t.column :body, :blob
14
+ # end
15
+ #
16
+ # You can modify any of those from the schema directly.
17
+ #
18
+ # class Entry < Heresy::Model
19
+ # schema :custom_table_name do |s|
20
+ # s.id.type = :medium_int
21
+ # s.id.options[:size] = 11
22
+ # s.body.type = :medium_blob
23
+ # end
24
+ # end
25
+ #
26
+ # The types are any valid type from the Sequel ruby library. There are a few convenience types, however.
27
+ # (See Heresy::Schema::Column)
28
+ #
29
+ # All models have the same 4 database fields:
30
+ # - an auto-incremented ID and updated_at timestamp
31
+ # - a 32 character hex UUID that gets stored as a 16 character binary string. This is how items are retrieved.
32
+ # - a BLOB body character for storing the model's data.
33
+ #
34
+ def schema(table_name = name.demodulize.underscore.pluralize)
35
+ @schema ||= Heresy::Schema.new(db, table_name.to_sym)
36
+ yield @schema if block_given?
37
+ @schema
38
+ end
39
+
40
+ # Yields a Schema::Fields object for specifying fields for this model. These fields get assembled into
41
+ # a hash and stored in the BODY attribute of the schema.
42
+ #
43
+ # By default, fields are all strings. Other types such as integers and dates can be specified for automatic
44
+ # conversions.
45
+ #
46
+ # class Entry < Heresy::Model
47
+ # fields do |f|
48
+ # f.string :title
49
+ # f.integer :comments_count
50
+ # f.datetime :published_at
51
+ # end
52
+ # end
53
+ #
54
+ def fields
55
+ yield Schema::Fields.new(self)
56
+ end
57
+
58
+ # This is a hash of all specified fields and their type converters. If a type converter is set (see #fields),
59
+ # values are parsed in the field attribute writers, and encoded when saving to the database.
60
+ def body_fields
61
+ @body_fields ||= {}
62
+ end
63
+
64
+ # Finds a single or an array of entries by UUID.
65
+ def find(uuid)
66
+ if uuid.is_a?(Array)
67
+ rows = dataset.filter(:uuid => uuid.map { |u| u.to_a.pack("H*") }).to_a
68
+ rows.map! { |row| retrieve(row) }
69
+ else
70
+ new(:uuid => uuid).reload
71
+ end
72
+ end
73
+
74
+ # Reference to the Sequel DB object
75
+ def db
76
+ @db = Heresy.db
77
+ end
78
+
79
+ # Reference to the Sequel dataset for this model's table.
80
+ def dataset
81
+ @dataset ||= db[schema.name]
82
+ end
83
+
84
+ # The formatter is what converts the assembled model hash data into the string
85
+ # that gets saved in the database. The default formatter stores zlib compressed
86
+ # JSON hashes.
87
+ def formatter
88
+ @formatter ||= \
89
+ if superclass.respond_to?(:formatter)
90
+ superclass.formatter
91
+ else
92
+ :json
93
+ end
94
+
95
+ if @formatter.is_a?(Symbol)
96
+ require "heresy/formatting/#{@formatter}"
97
+ @formatter = Heresy::Formatting.const_get(@formatter.to_s.capitalize)
98
+ end
99
+
100
+ @formatter
101
+ end
102
+
103
+ protected
104
+ # Used by #find to fill up an empty model record with data using a row from the db.
105
+ def retrieve(row)
106
+ record = new(:uuid => row[:uuid].unpack("H*").first)
107
+ record.retrieve(row)
108
+ end
109
+ end
110
+
111
+ attr_reader :id
112
+ attr_reader :updated_at
113
+
114
+ def initialize(attributes = {})
115
+ set_attributes attributes
116
+ end
117
+
118
+ def uuid
119
+ @uuid ||= UUID.generate(:compact)
120
+ end
121
+
122
+ # Saves the record in the database.
123
+ def save
124
+ if new?
125
+ self.class.dataset << {:uuid => uuid.to_a.pack("H*"), :body => self.class.formatter.encode(assemble_body)}
126
+ reload
127
+ else
128
+ rowset.update({:updated_at => nil})
129
+ reload
130
+ end
131
+ true
132
+ end
133
+
134
+ def new?
135
+ @id.nil?
136
+ end
137
+
138
+ # Reloads a model's data from the database using the existing UUID.
139
+ def reload
140
+ retrieve(rowset.select(:id, :body, :updated_at).first)
141
+ end
142
+
143
+ def ==(other)
144
+ other.class == self.class && other.uuid == uuid
145
+ end
146
+
147
+ def inspect
148
+ "<#{self.class.name} (#{uuid}) #{self.class.body_fields.keys.map { |k| "@#{k}=#{send(k).inspect}" } * ', '}>"
149
+ end
150
+
151
+ def retrieve(row)
152
+ @id = row[:id]
153
+ @updated_at = row[:updated_at]
154
+ set_attributes self.class.formatter.decode(row[:body])
155
+ self
156
+ end
157
+
158
+ protected
159
+ # Assembles the model data into an encoded hash, ready to be formatted and stored.
160
+ def assemble_body
161
+ hash = {}
162
+ self.class.body_fields.each do |key, type|
163
+ if value = instance_variable_get("@#{key}")
164
+ hash[key] = type ? type.encode(value) : value
165
+ end
166
+ end
167
+ hash
168
+ end
169
+
170
+ # Performs a mass assignment of the model's data.
171
+ def set_attributes(attributes)
172
+ attributes.each do |key, value|
173
+ next if key.blank?
174
+ key = key.to_sym
175
+ if key == :uuid
176
+ @uuid = value
177
+ elsif self.class.body_fields.key?(key)
178
+ send "#{key}=", value
179
+ end
180
+ end
181
+ end
182
+
183
+ # A Sequel filter for the current row in the database.
184
+ def rowset
185
+ @rowset ||= self.class.dataset.filter(:uuid => uuid.to_a.pack("H*"))
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,48 @@
1
+ module Heresy
2
+ # This class is responsible for defining and creating the table used to store model data.
3
+ class Schema
4
+ attr_accessor :name
5
+ attr_reader :id, :uuid, :updated_at, :body
6
+
7
+ def initialize(db, name)
8
+ @db = db
9
+ @name = name
10
+ @id = Column.new(:id, :int, :size => 11, :unsigned => true)
11
+ @uuid = Column.new(:uuid, :uuid, :unique => true)
12
+ @updated_at = Column.new(:updated_at, :timestamp, :default => 'CURRENT_TIMESTAMP'.lit, :index => true)
13
+ @body = Column.new(:body, :blob)
14
+ end
15
+
16
+ # Checks whether the model table exists or not.
17
+ def exists?
18
+ @db.table_exists?(@name)
19
+ end
20
+
21
+ # Creates the model table.
22
+ def create
23
+ to_sql.each { |sql| @db.execute_ddl(sql) }
24
+ end
25
+
26
+ # Drops the model table.
27
+ def drop
28
+ @db.drop_table @name
29
+ end
30
+
31
+ # Returns an array of the SQL statements used to create the table and any indexes.
32
+ def to_sql
33
+ @db.create_table_sql_list @name, *generate.create_info
34
+ end
35
+
36
+ def inspect
37
+ "<Heresy::Schema:#{@name}>"
38
+ end
39
+
40
+ protected
41
+ def generate
42
+ gen = Sequel::Schema::Generator.new(@db)
43
+ @id.create(gen, :primary_key)
44
+ [@uuid, @updated_at, @body].each { |c| c.create(gen) }
45
+ gen
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,34 @@
1
+ module Heresy
2
+ class Schema
3
+ # This class defines a single column in the database, using the same options
4
+ # as the Sequel library. There are a few convenience types though:
5
+ #
6
+ # - :uuid: Creates a binary(16) database field
7
+ #
8
+ class Column
9
+ attr_accessor :name, :type, :options
10
+
11
+ # The actual database type. This may be different if the given type is a
12
+ # Heresy convenience type, or if the database supports the type under a different name.
13
+ attr_reader :db_type
14
+
15
+ def initialize(name, type, options = {})
16
+ if type == :uuid
17
+ @db_type = :binary
18
+ options.update(:size => 16, :null => false)
19
+ else
20
+ @db_type = type
21
+ end
22
+ @name = name
23
+ @type = type
24
+ @options = options
25
+ end
26
+
27
+ # Creates the column using the Sequel schema generator. By default, the #column
28
+ # method is used. An alternative method like :primary_key can be specified.
29
+ def create(gen, method = :column)
30
+ gen.send(method, @name, @db_type, @options)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,66 @@
1
+ module Heresy
2
+ class Schema
3
+ # This class is a helper for setting up fields in the model. This is likely called
4
+ # from the model class and never used directly:
5
+ #
6
+ # class Entry < Heresy::Model
7
+ # fields do |f|
8
+ # f.field :title, :string
9
+ # f.string :body # Uses #string convenience method
10
+ # end
11
+ # end
12
+ #
13
+ class Fields
14
+ module Integer
15
+ extend self
16
+ def parse(input) input.to_i end
17
+ def encode(input) input end
18
+ end
19
+
20
+ module Float
21
+ extend self
22
+ def parse(input) input.to_f end
23
+ def encode(input) input end
24
+ end
25
+
26
+ module DateTime
27
+ extend self
28
+ def parse(input)
29
+ case input
30
+ when Time then input
31
+ when String then Time.parse(input)
32
+ else input.to_time
33
+ end
34
+ end
35
+ def encode(input) input.xmlschema end
36
+ end
37
+
38
+ @@types = {}
39
+ def self.types() @@types end
40
+ def self.type(key, type = nil)
41
+ class_eval "def #{key}(name) field(name, :#{key}) end"
42
+ @@types[key] = type
43
+ end
44
+
45
+ type :string
46
+ type :integer, Integer
47
+ type :float, Float
48
+ type :datetime, DateTime
49
+
50
+ def initialize(model)
51
+ @model = model
52
+ end
53
+
54
+ # Adds a field of the given type to the model by creating an attribute reader and writer.
55
+ def field(name, type_name)
56
+ if type = self.class.types[type_name]
57
+ @model.send(:attr_reader, name)
58
+ @model.class_eval "def #{name}=(v) @#{name} = self.class.body_fields[:#{name}].parse(v) end"
59
+ else
60
+ @model.send(:attr_accessor, name)
61
+ end
62
+ @model.body_fields[name] = type
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,88 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
2
+
3
+ module HeresyTest
4
+ class FieldsTest < Heresy::TestCase
5
+ describe "field types from mass assignment" do
6
+ before :all do
7
+ @entry = Entry.new :title => 'whoa', :a => 'ignored', :comments_count => "3.0", :rating => '5', :published_at => Time.utc(2008, 1, 1)
8
+ end
9
+
10
+ it "sets a string" do
11
+ @entry.title.should == 'whoa'
12
+ end
13
+
14
+ it "sets an integer" do
15
+ @entry.comments_count.should == 3
16
+ end
17
+
18
+ it "sets a float" do
19
+ @entry.rating.should == 5.0
20
+ @entry.rating.to_s.should == "5.0"
21
+ end
22
+
23
+ it "sets a time" do
24
+ @entry.published_at.should == Time.utc(2008, 1, 1)
25
+ end
26
+ end
27
+
28
+ describe "field types from individual set" do
29
+ before :all do
30
+ @entry = Entry.new
31
+ end
32
+
33
+ it "sets a string" do
34
+ @entry.title = 'whoa'
35
+ @entry.title.should == 'whoa'
36
+ end
37
+
38
+ it "sets an integer" do
39
+ @entry.comments_count = '3.0'
40
+ @entry.comments_count.should == 3
41
+ end
42
+
43
+ it "sets a float" do
44
+ @entry.rating = 5
45
+ @entry.rating.should == 5.0
46
+ @entry.rating.to_s.should == "5.0"
47
+ end
48
+
49
+ it "sets a time from an xml schema" do
50
+ @entry.published_at = Time.utc(2008, 1, 1).xmlschema
51
+ @entry.published_at.should == Time.utc(2008, 1, 1)
52
+ end
53
+
54
+ it "sets a time from a time" do
55
+ @entry.published_at = Time.utc(2008, 1, 1)
56
+ @entry.published_at.should == Time.utc(2008, 1, 1)
57
+ end
58
+ end
59
+
60
+ describe "field types from retrieved model" do
61
+ before :all do
62
+ @entry = Entry.new :title => 'whoa', :a => 'ignored', :comments_count => "3.0", :rating => '5', :published_at => Time.utc(2008, 1, 1)
63
+ @entry.save
64
+ @entry = Entry.find(@entry.uuid)
65
+ end
66
+
67
+ it "sets a string" do
68
+ @entry.title = 'whoa'
69
+ @entry.title.should == 'whoa'
70
+ end
71
+
72
+ it "sets an integer" do
73
+ @entry.comments_count = '3.0'
74
+ @entry.comments_count.should == 3
75
+ end
76
+
77
+ it "sets a float" do
78
+ @entry.rating = 5
79
+ @entry.rating.should == 5.0
80
+ @entry.rating.to_s.should == "5.0"
81
+ end
82
+
83
+ it "sets a time" do
84
+ @entry.published_at.should == Time.utc(2008, 1, 1)
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,15 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
2
+ require 'heresy/formatting/json'
3
+
4
+ module HeresyTest::Formatting
5
+ class JsonTest < Heresy::TestCase
6
+ before :all do
7
+ @formatter = Heresy::Formatting::Json
8
+ end
9
+
10
+ it "encodes and decodes hash with strings" do
11
+ input = {:a => 1, :b => 'two'}
12
+ @formatter.decode(@formatter.encode(input)).should == {'a' => 1, 'b' => 'two'}
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,74 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
2
+
3
+ module HeresyTest
4
+ class IndexTest < Heresy::TestCase
5
+ describe "Index creation" do
6
+ before :all do
7
+ @index = Heresy::Index.new(Heresy.db, :foo_bar_baz, :foo_id) { |idx| idx.field(:name, :varchar, :size => 255) }
8
+ end
9
+
10
+ after do
11
+ @index.drop rescue nil
12
+ end
13
+
14
+ it "generates CREATE TABLE sql" do
15
+ @index.to_sql.first.should =~ /CREATE TABLE/
16
+ end
17
+
18
+ it "detects existence of table" do
19
+ assert !@index.exists?
20
+ end
21
+
22
+ it "creates table" do
23
+ assert !@index.exists?
24
+ @index.create
25
+ assert @index.exists?
26
+ end
27
+
28
+ it "drops table" do
29
+ assert !@index.exists?
30
+ @index.create
31
+ assert @index.exists?
32
+ @index.drop
33
+ assert !@index.exists?
34
+ end
35
+ end
36
+
37
+ describe "Index" do
38
+ before :all do
39
+ @index = Heresy::Index.new(Heresy.db, :foo_bar_baz, :foo_id) { |idx| idx.field(:name, :varchar, :size => 255) }
40
+ @index.create
41
+ end
42
+
43
+ it "allows data to be inserted" do
44
+ old = @index.count
45
+ @index << {:foo_id => UUID.generate(:compact), :name => 'bob'}
46
+ @index.count.should == old + 1
47
+ end
48
+
49
+ it "clears data" do
50
+ @index << {:foo_id => UUID.generate(:compact), :name => 'bob'}
51
+ @index.clear
52
+ @index.count.should == 0
53
+ end
54
+
55
+ describe "(data retrieval)" do
56
+ before :all do
57
+ @index.clear
58
+ @uuid1 = UUID.generate(:compact)
59
+ @uuid2 = UUID.generate(:compact)
60
+ @index << {:foo_id => @uuid1, :name => 'bob'}
61
+ @index << {:foo_id => @uuid2, :name => 'bob'}
62
+ end
63
+
64
+ it "#find fetches data" do
65
+ @index.find(:name => 'bob').should == [@uuid1, @uuid2]
66
+ end
67
+ end
68
+
69
+ after :all do
70
+ @index.drop rescue nil
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,78 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
2
+
3
+ module HeresyTest
4
+ class ModelTest < Heresy::TestCase
5
+ before :all do
6
+ @entry = Entry.new(:title => 'whoa', :body => 'test')
7
+ @entry.save
8
+ end
9
+
10
+ describe "#save on new entry" do
11
+ before :all do
12
+ @new = Entry.new :title => 'whoa', :a => 'ignored', :comments_count => "3.0", :rating => '5'
13
+ @new.save
14
+ end
15
+
16
+ it "sets id" do
17
+ @new.id.should_not == nil
18
+ end
19
+
20
+ it "sets uuid" do
21
+ @new.uuid.should_not == nil
22
+ end
23
+
24
+ it "sets updated_at" do
25
+ @new.updated_at.class.should == Time
26
+ end
27
+
28
+ it "sets title" do
29
+ @new.title.should == 'whoa'
30
+ end
31
+
32
+ it "sets comments_count" do
33
+ @new.comments_count.should == 3
34
+ end
35
+
36
+ it "sets rating" do
37
+ @new.rating.should == 5.0
38
+ end
39
+ end
40
+
41
+ describe "#save on existing entry" do
42
+ before :all do
43
+ sleep 1
44
+ @old_id = @entry.id
45
+ @old_uuid = @entry.uuid
46
+ @old_updated = @entry.updated_at
47
+ @entry.save
48
+ end
49
+
50
+ it "keeps id" do
51
+ @entry.id.should == @old_id
52
+ end
53
+
54
+ it "keeps uuid" do
55
+ @entry.uuid.should == @old_uuid
56
+ end
57
+
58
+ it "sets updated_at" do
59
+ @entry.updated_at.should_not == @old_updated
60
+ end
61
+ end
62
+
63
+ describe "#find" do
64
+ before :all do
65
+ @entry2 = Entry.new(:title => 'whoa', :body => 'test')
66
+ @entry2.save
67
+ end
68
+
69
+ it "fetches by uuid" do
70
+ Entry.find(@entry.uuid).should == @entry
71
+ end
72
+
73
+ it "fetches by array uuids" do
74
+ Entry.find([@entry.uuid, @entry2.uuid]).should == [@entry, @entry2]
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,43 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
2
+
3
+ module HeresyTest
4
+ class SchemaTest < Heresy::TestCase
5
+ describe "Heresy Model Schema instance" do
6
+ it "sets model schema name to plural underscored class name" do
7
+ Entry.schema.name.should == :entries
8
+ end
9
+ end
10
+
11
+ describe "Schema" do
12
+ before :all do
13
+ @schema = Heresy::Schema.new(Heresy.db, :foo_bar_baz)
14
+ end
15
+
16
+ after do
17
+ @schema.drop rescue nil
18
+ end
19
+
20
+ it "generates CREATE TABLE sql" do
21
+ @schema.to_sql.first.should =~ /CREATE TABLE/
22
+ end
23
+
24
+ it "detects existence of table" do
25
+ assert !@schema.exists?
26
+ end
27
+
28
+ it "creates table" do
29
+ assert !@schema.exists?
30
+ @schema.create
31
+ assert @schema.exists?
32
+ end
33
+
34
+ it "drops table" do
35
+ assert !@schema.exists?
36
+ @schema.create
37
+ assert @schema.exists?
38
+ @schema.drop
39
+ assert !@schema.exists?
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,51 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
6
+
7
+ require 'rubygems'
8
+ gem 'jeremymcanally-matchy'
9
+ gem 'jeremymcanally-context'
10
+ gem 'rr'
11
+
12
+ require 'context'
13
+ require 'matchy'
14
+ require 'rr'
15
+ require 'logger'
16
+ require 'heresy-with-gems'
17
+
18
+ # TODO: remove this once it gets fixed in context
19
+ class String
20
+ def to_method_name
21
+ self.downcase.gsub(/[\s:',;!#()\.]+/,'_')
22
+ end
23
+ end
24
+
25
+ module Heresy
26
+ class TestCase < Test::Unit::TestCase
27
+ include RR::Adapters::TestUnit
28
+ end
29
+ end
30
+
31
+ module HeresyTest
32
+ class Entry < Heresy::Model
33
+ fields do |f|
34
+ f.string :title
35
+ f.string :body
36
+ f.integer :comments_count
37
+ f.float :rating
38
+ f.datetime :published_at
39
+ end
40
+ end
41
+ end
42
+
43
+ Heresy.db = Sequel.connect("mysql://root@localhost/heresy_test")
44
+ HeresyTest::Entry.schema.drop rescue nil
45
+ HeresyTest::Entry.schema.create
46
+
47
+ begin
48
+ require 'ruby-debug'
49
+ Debugger.start
50
+ rescue LoadError
51
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kabuki-heresy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - kabuki
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-03-29 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activesupport
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: 2.2.0
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: sequel
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 2.10.0
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: json
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 1.1.3
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: uuid
47
+ type: :runtime
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 2.0.1
54
+ version:
55
+ description:
56
+ email: kabukiruby@gmail.com
57
+ executables: []
58
+
59
+ extensions: []
60
+
61
+ extra_rdoc_files:
62
+ - README.rdoc
63
+ - LICENSE
64
+ files:
65
+ - README.rdoc
66
+ - VERSION.yml
67
+ - lib/heresy
68
+ - lib/heresy/formatting
69
+ - lib/heresy/formatting/json.rb
70
+ - lib/heresy/index.rb
71
+ - lib/heresy/model.rb
72
+ - lib/heresy/schema
73
+ - lib/heresy/schema/column.rb
74
+ - lib/heresy/schema/fields.rb
75
+ - lib/heresy/schema.rb
76
+ - lib/heresy-with-gems.rb
77
+ - lib/heresy.rb
78
+ - test/fields_test.rb
79
+ - test/formatting
80
+ - test/formatting/json_test.rb
81
+ - test/index_test.rb
82
+ - test/model_test.rb
83
+ - test/schema_test.rb
84
+ - test/test_helper.rb
85
+ - LICENSE
86
+ has_rdoc: true
87
+ homepage: http://github.com/kabuki/heresy
88
+ post_install_message:
89
+ rdoc_options:
90
+ - --inline-source
91
+ - --charset=UTF-8
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: "0"
99
+ version:
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: "0"
105
+ version:
106
+ requirements: []
107
+
108
+ rubyforge_project:
109
+ rubygems_version: 1.2.0
110
+ signing_key:
111
+ specification_version: 2
112
+ summary: TODO
113
+ test_files: []
114
+