whorm 0.4.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 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.
@@ -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.
@@ -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
@@ -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
@@ -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
+