kabuki-heresy 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/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
+