whorm 0.4.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 +20 -0
- data/README.rdoc +113 -0
- data/Rakefile +64 -0
- data/VERSION +1 -0
- data/lib/test/macros.rb +20 -0
- data/lib/whorm.rb +15 -0
- data/lib/whorm/adapters/active_record.rb +88 -0
- data/lib/whorm/adapters/data_mapper.rb +66 -0
- data/lib/whorm/adapters/mongo_mapper.rb +64 -0
- data/lib/whorm/model.rb +370 -0
- data/test/active_record_test.rb +0 -0
- data/test/app/config/application.rb +70 -0
- data/test/app/config/database.yml +3 -0
- data/test/app/db/schema.rb +75 -0
- data/test/app/models/active_record/address.rb +4 -0
- data/test/app/models/active_record/data_type.rb +3 -0
- data/test/app/models/active_record/group.rb +4 -0
- data/test/app/models/active_record/house.rb +4 -0
- data/test/app/models/active_record/location.rb +5 -0
- data/test/app/models/active_record/person.rb +4 -0
- data/test/app/models/active_record/user.rb +6 -0
- data/test/app/models/active_record/user_group.rb +4 -0
- data/test/data_mapper_test.rb +0 -0
- data/test/model_test.rb +526 -0
- data/test/mongo_mapper_test.rb +0 -0
- data/test/test_helper.rb +32 -0
- metadata +123 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Chris Scott
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
= mvc
|
2
|
+
|
3
|
+
A simple Model mixin with adapters for various ORM frameworks such as ActiveRecord, DataMapper and MongoMapper. Whorm was originally created as part of the gem extjs-mvc[http://github.com/extjs/mvc] to assist with auto-generating ExtJS Stores (Ext.data.Store). However, it can be useful for a variety of Javascript frameworks for rendering data on the client.
|
4
|
+
|
5
|
+
|
6
|
+
===Installation
|
7
|
+
% sudo gem install gemcutter
|
8
|
+
% sudo gem install whorm
|
9
|
+
|
10
|
+
|
11
|
+
=== An ORM Model mixin: Whorm::Model
|
12
|
+
whorm contains Model mixin named <tt>Whorm::Model</tt> which works for <b>three</b> popular ORM frameworks, ActiveRecord, DataMapper and MongoMapper. The API for each framework is identical and an adapter can be created for just about any
|
13
|
+
ORM in about an hour.
|
14
|
+
|
15
|
+
Simply include the mixin into your model. Use the class-method <tt>whorm_fields</tt> to specify those
|
16
|
+
fields with will be used to render a record to Hash for later JSON-encoding.
|
17
|
+
|
18
|
+
class User < ActiveRecord::Base
|
19
|
+
include Whorm::Model
|
20
|
+
|
21
|
+
whorm_fields :exclude => [:password, :password_confirmation]
|
22
|
+
|
23
|
+
# OR
|
24
|
+
whorm_fields :name, :description
|
25
|
+
|
26
|
+
# OR
|
27
|
+
whorm_fields :only => [:name, :description] # actually the same as above
|
28
|
+
|
29
|
+
# OR
|
30
|
+
whorm_fields :additional => [:computed] # includes all database columns and an additional computed field
|
31
|
+
|
32
|
+
# OR define a column as a Hash
|
33
|
+
whorm_fields :description, :name => {"sortDir" => "ASC"}, :created_at => {"dateFormat" => "c"}
|
34
|
+
|
35
|
+
# OR render associations, association-fields will have their "mapping" property set automatically
|
36
|
+
whorm_fields :name, :description, :company => [:name, :description]
|
37
|
+
|
38
|
+
def computed
|
39
|
+
name.blank? ? login : name
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
After including the model mixin <tt>ExtJS::Model</tt>, try typing the following in <tt>irb</tt> console:
|
44
|
+
>> User.whorm_schema
|
45
|
+
=> { :idProperty=>"id", :fields=>[
|
46
|
+
{:type=>'int', :allowBlank=>true, :name=>"id"},
|
47
|
+
{:type=>'string', :allowBlank=>false, :name=>"first", :defaultValue => nil},
|
48
|
+
{:type=>'string', :allowBlank=>false, :name=>"last", :defaultValue => nil},
|
49
|
+
{:type=>'string', :allowBlank=>false, :name=>"email", :defaultValue => nil}
|
50
|
+
]}
|
51
|
+
|
52
|
+
An auto-generated <tt>Ext.data.JsonReader</tt> configuration!
|
53
|
+
|
54
|
+
|
55
|
+
You can also define different sets of fields for different representations of your model.
|
56
|
+
|
57
|
+
E.g. with the following definition:
|
58
|
+
|
59
|
+
class User < ActiveRecord::Base
|
60
|
+
include ExtJS::Model
|
61
|
+
|
62
|
+
whorm_fieldset :grid, fields => [:name, :description, :company => [:name, :description]]
|
63
|
+
whorm_fieldset :combo, [:full_name]
|
64
|
+
|
65
|
+
def full_name
|
66
|
+
"#{first_name} #{name}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
You can get store configs for both representations with
|
71
|
+
User.whorm_schema(:grid)
|
72
|
+
or
|
73
|
+
User.whorm_schema(:combo)
|
74
|
+
|
75
|
+
And the corresponding data for the representations with
|
76
|
+
User.first.to_record(:grid)
|
77
|
+
or
|
78
|
+
User.first.to_record(:combo)
|
79
|
+
|
80
|
+
=== A Testing Mixin: Whorm::TestMacros
|
81
|
+
The <tt>whorm</tt> Gem includes a small set of testing macros to help unit-test models.
|
82
|
+
This requires the 'Shoulda' gem from thoughtbot. Include this mixin inside the
|
83
|
+
<tt>ActiveSupport::TestCase</tt> class in <tt>test/test_helper.rb</tt>
|
84
|
+
|
85
|
+
==== Usage
|
86
|
+
<tt>test/test_helper.rb</tt>
|
87
|
+
class ActiveSupport::TestCase
|
88
|
+
extend Whorm::TestMacros
|
89
|
+
#...
|
90
|
+
end
|
91
|
+
|
92
|
+
In individual model unit tests:
|
93
|
+
class ModelTest < ActiveSupport::TestCase
|
94
|
+
should_require_whorm_fields :name, :email, :city
|
95
|
+
#...
|
96
|
+
#other tests
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
== Note on Patches/Pull Requests
|
101
|
+
|
102
|
+
* Fork the project.
|
103
|
+
* Make your feature addition or bug fix.
|
104
|
+
* Add tests for it. This is important so I don't break it in a
|
105
|
+
future version unintentionally.
|
106
|
+
* Commit, do not mess with rakefile, version, or history.
|
107
|
+
(if you want to have your own version, that is fine but
|
108
|
+
bump version in a commit by itself I can ignore when I pull)
|
109
|
+
* Send me a pull request. Bonus points for topic branches.
|
110
|
+
|
111
|
+
== Copyright
|
112
|
+
|
113
|
+
Copyright (c) 2009 Chris Scott. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "whorm"
|
8
|
+
gem.summary = %Q{Ruby ORM-inspecting tools to assist with generating JSON representations of database schemas and recordsets.}
|
9
|
+
gem.description = %Q{Whorm contains a Model-mixin named Whorm::Model. Once included, your Model now exposes a class-method named #whorm_schema which will return Hash representation of the Model-schema.}
|
10
|
+
gem.email = "christocracy@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/christocracy/whorm"
|
12
|
+
gem.authors = ["Chris Scott"]
|
13
|
+
gem.add_development_dependency "shoulda"
|
14
|
+
gem.add_development_dependency "mocha"
|
15
|
+
gem.add_development_dependency "extlib"
|
16
|
+
|
17
|
+
gem.test_files = []
|
18
|
+
gem.files = FileList["[A-Z]*", "{bin,generators,lib,test}/**/*", 'lib/jeweler/templates/.gitignore']
|
19
|
+
|
20
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
21
|
+
end
|
22
|
+
Jeweler::GemcutterTasks.new
|
23
|
+
rescue LoadError
|
24
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
Rake::TestTask.new(:test) do |test|
|
30
|
+
test.libs << 'lib' << 'test'
|
31
|
+
test.pattern = 'test/**/*_test.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
|
35
|
+
begin
|
36
|
+
require 'rcov/rcovtask'
|
37
|
+
Rcov::RcovTask.new do |test|
|
38
|
+
test.libs << 'test'
|
39
|
+
test.pattern = 'test/**/*_test.rb'
|
40
|
+
test.verbose = true
|
41
|
+
end
|
42
|
+
rescue LoadError
|
43
|
+
task :rcov do
|
44
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
task :test => :check_dependencies
|
49
|
+
|
50
|
+
task :default => :test
|
51
|
+
|
52
|
+
require 'rake/rdoctask'
|
53
|
+
Rake::RDocTask.new do |rdoc|
|
54
|
+
if File.exist?('VERSION')
|
55
|
+
version = File.read('VERSION')
|
56
|
+
else
|
57
|
+
version = ""
|
58
|
+
end
|
59
|
+
|
60
|
+
rdoc.rdoc_dir = 'rdoc'
|
61
|
+
rdoc.title = "mvc #{version}"
|
62
|
+
rdoc.rdoc_files.include('README*')
|
63
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
64
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.4.0
|
data/lib/test/macros.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module ExtJS
|
2
|
+
module TestMacros
|
3
|
+
##
|
4
|
+
# Asserts that the passed list of fields are specified in the extjs_fields call
|
5
|
+
# in the model class.
|
6
|
+
# @fields {Symbols} fields A list of fields
|
7
|
+
#
|
8
|
+
def should_have_extjs_fields *fields
|
9
|
+
klass = model_class
|
10
|
+
should "have the correct extjs_fields" do
|
11
|
+
fields.each do |field|
|
12
|
+
found_record = klass.extjs_record_fields.find do|record_field|
|
13
|
+
record_field[:name] == field.to_s
|
14
|
+
end
|
15
|
+
assert_not_nil found_record, "extjs field #{field} isn't listed in the #{klass.name} model"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/whorm.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'whorm/model'
|
2
|
+
|
3
|
+
# Detect orm, include appropriate mixin.
|
4
|
+
if defined?(ActiveRecord)
|
5
|
+
require 'whorm/adapters/active_record'
|
6
|
+
elsif defined?(DataMapper)
|
7
|
+
require 'whorm/adapters/data_mapper'
|
8
|
+
elsif defined?(MongoMapper)
|
9
|
+
require 'whorm/adapters/mongo_mapper'
|
10
|
+
end
|
11
|
+
|
12
|
+
module Whorm
|
13
|
+
VERSION = "0.4.0"
|
14
|
+
end
|
15
|
+
|
@@ -0,0 +1,88 @@
|
|
1
|
+
##
|
2
|
+
# ActiveRecord adapter to Whorm::Model mixin.
|
3
|
+
#
|
4
|
+
module Whorm
|
5
|
+
module Model
|
6
|
+
module ClassMethods
|
7
|
+
def whorm_primary_key
|
8
|
+
self.primary_key.to_sym
|
9
|
+
end
|
10
|
+
|
11
|
+
def whorm_column_names
|
12
|
+
self.column_names.map(&:to_sym)
|
13
|
+
end
|
14
|
+
|
15
|
+
def whorm_columns_hash
|
16
|
+
self.columns_hash.symbolize_keys
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# determine if supplied Column object is nullable
|
21
|
+
# @param {ActiveRecord::ConnectionAdapters::Column}
|
22
|
+
# @return {Boolean}
|
23
|
+
#
|
24
|
+
def whorm_allow_blank(col)
|
25
|
+
# if the column is the primary key always allow it to be blank.
|
26
|
+
# Otherwise we could not create new records with whorm because
|
27
|
+
# new records have no id and thus cannot be valid
|
28
|
+
col.name == self.primary_key || col.null
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# returns the default value
|
33
|
+
# @param {ActiveRecord::ConnectionAdapters::Column}
|
34
|
+
# @return {Mixed}
|
35
|
+
#
|
36
|
+
def whorm_default(col)
|
37
|
+
col.default
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# returns the corresponding column name of the type column for a polymorphic association
|
42
|
+
# @param {String/Symbol} the id column name for this association
|
43
|
+
# @return {Symbol}
|
44
|
+
def whorm_polymorphic_type(id_column_name)
|
45
|
+
id_column_name.to_s.gsub(/_id\Z/, '_type').to_sym
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# determine datatype of supplied Column object
|
50
|
+
# @param {ActiveRecord::ConnectionAdapters::Column}
|
51
|
+
# @return {String}
|
52
|
+
#
|
53
|
+
def whorm_type(col)
|
54
|
+
type = col.type.to_s
|
55
|
+
case type
|
56
|
+
when "datetime", "date", "time", "timestamp"
|
57
|
+
type = "date"
|
58
|
+
when "text"
|
59
|
+
type = "string"
|
60
|
+
when "integer"
|
61
|
+
type = "int"
|
62
|
+
when "decimal"
|
63
|
+
type = "float"
|
64
|
+
end
|
65
|
+
type
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# return a simple, normalized list of AR associations having the :name, :type and association class
|
70
|
+
# @return {Array}
|
71
|
+
#
|
72
|
+
def whorm_associations
|
73
|
+
@whorm_associations ||= self.reflections.inject({}) do |memo, (key, assn)|
|
74
|
+
type = (assn.macro === :has_many || assn.macro === :has_and_belongs_to_many) ? :many : assn.macro
|
75
|
+
memo[key.to_sym] = {
|
76
|
+
:name => key.to_sym,
|
77
|
+
:type => type,
|
78
|
+
:class => assn.options[:polymorphic] ? nil : assn.class_name.constantize,
|
79
|
+
:foreign_key => assn.association_foreign_key.to_sym,
|
80
|
+
:is_polymorphic => !!assn.options[:polymorphic]
|
81
|
+
}
|
82
|
+
memo
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
@@ -0,0 +1,66 @@
|
|
1
|
+
##
|
2
|
+
# DataMapper adapter for Whorm::Model mixin
|
3
|
+
#
|
4
|
+
|
5
|
+
module Whorm
|
6
|
+
module Model
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
def whorm_primary_key
|
10
|
+
self.key.first.name
|
11
|
+
end
|
12
|
+
|
13
|
+
def whorm_column_names
|
14
|
+
self.properties.collect {|p| p.name.to_s }
|
15
|
+
end
|
16
|
+
|
17
|
+
def whorm_columns_hash
|
18
|
+
if @whorm_columns_hash.nil?
|
19
|
+
@whorm_columns_hash = {}
|
20
|
+
self.properties.each do |p|
|
21
|
+
@whorm_columns_hash[p.name] = p
|
22
|
+
end
|
23
|
+
end
|
24
|
+
@whorm_columns_hash
|
25
|
+
end
|
26
|
+
|
27
|
+
def whorm_allow_blank(col)
|
28
|
+
(col === self.key.first) ? true : col.nullable?
|
29
|
+
end
|
30
|
+
|
31
|
+
def whorm_type(col)
|
32
|
+
type = ((col.type.respond_to?(:primitive)) ? col.type.primitive : col.type).to_s
|
33
|
+
case type
|
34
|
+
when "DateTime", "Date", "Time"
|
35
|
+
type = :date
|
36
|
+
when "String"
|
37
|
+
type = :string
|
38
|
+
when "Float"
|
39
|
+
type = :float
|
40
|
+
when "Integer", "BigDecimal"
|
41
|
+
type = :int
|
42
|
+
else
|
43
|
+
type = "auto"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def whorm_associations
|
48
|
+
if @whorm_associations.nil?
|
49
|
+
@whorm_associations = {}
|
50
|
+
self.relationships.keys.each do |key|
|
51
|
+
assn = self.relationships[key]
|
52
|
+
@whorm_associations[key.to_sym] = {
|
53
|
+
:name => key,
|
54
|
+
:type => type = (assn.options[:max].nil? && assn.options[:min].nil?) ? :belongs_to : (assn.options[:max] > 1) ? :many : nil ,
|
55
|
+
:class => assn.parent_model,
|
56
|
+
:foreign_key => assn.child_key.first.name,
|
57
|
+
:is_polymorphic => false # <-- No impl. for DM is_polymorphic. Anyone care to implement this?
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
@whorm_associations
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
##
|
2
|
+
# MongoMapper adapter to Whorm::Model mixin
|
3
|
+
#
|
4
|
+
|
5
|
+
module Whorm
|
6
|
+
module Model
|
7
|
+
##
|
8
|
+
# ClassMethods
|
9
|
+
#
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
def whorm_primary_key
|
13
|
+
:id
|
14
|
+
end
|
15
|
+
|
16
|
+
def whorm_column_names
|
17
|
+
self.column_names
|
18
|
+
end
|
19
|
+
|
20
|
+
def whorm_columns_hash
|
21
|
+
self.keys
|
22
|
+
end
|
23
|
+
|
24
|
+
def whorm_associations
|
25
|
+
@whorm_associations ||= self.associations.inject({}) do |memo, (key, assn)|
|
26
|
+
memo[key.to_sym] = {
|
27
|
+
:name => key.to_sym,
|
28
|
+
:type => assn.type,
|
29
|
+
:class => assn.class_name.constantize,
|
30
|
+
:foreign_key => assn.foreign_key,
|
31
|
+
:is_polymorphic => false
|
32
|
+
}
|
33
|
+
memo
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def whorm_type(col)
|
38
|
+
type = col.type.to_s
|
39
|
+
case type
|
40
|
+
when "DateTime", "Date", "Time"
|
41
|
+
type = :date
|
42
|
+
when "String"
|
43
|
+
type = :string
|
44
|
+
when "Float"
|
45
|
+
type = :float
|
46
|
+
when "Integer", "BigDecimal"
|
47
|
+
type = :int
|
48
|
+
else
|
49
|
+
type = "auto"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def whorm_allow_blank(col)
|
54
|
+
(col.name == '_id') || (col.options[:required] != true)
|
55
|
+
end
|
56
|
+
|
57
|
+
def whorm_default(col)
|
58
|
+
col.default_value
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|