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 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
+