sencha-model 0.5.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/.document +5 -0
- data/.gitignore +6 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +35 -0
- data/LICENSE +20 -0
- data/README.rdoc +113 -0
- data/Rakefile +26 -0
- data/VERSION +1 -0
- data/lib/sencha-model.rb +17 -0
- data/lib/sencha-model/adapters/active_record.rb +88 -0
- data/lib/sencha-model/adapters/data_mapper.rb +66 -0
- data/lib/sencha-model/adapters/mongo_mapper.rb +64 -0
- data/lib/sencha-model/model.rb +373 -0
- data/lib/sencha-model/version.rb +5 -0
- data/rails/init.rb +1 -0
- data/sencha-model.gemspec +99 -0
- data/shoulda_macros/macros.rb +20 -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 +147 -0
data/.document
ADDED
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
sencha-model (0.5.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
activemodel (3.0.7)
|
10
|
+
activesupport (= 3.0.7)
|
11
|
+
builder (~> 2.1.2)
|
12
|
+
i18n (~> 0.5.0)
|
13
|
+
activerecord (3.0.7)
|
14
|
+
activemodel (= 3.0.7)
|
15
|
+
activesupport (= 3.0.7)
|
16
|
+
arel (~> 2.0.2)
|
17
|
+
tzinfo (~> 0.3.23)
|
18
|
+
activesupport (3.0.7)
|
19
|
+
arel (2.0.10)
|
20
|
+
builder (2.1.2)
|
21
|
+
extlib (0.9.15)
|
22
|
+
i18n (0.5.0)
|
23
|
+
mocha (0.9.12)
|
24
|
+
shoulda (2.11.3)
|
25
|
+
tzinfo (0.3.27)
|
26
|
+
|
27
|
+
PLATFORMS
|
28
|
+
ruby
|
29
|
+
|
30
|
+
DEPENDENCIES
|
31
|
+
activerecord (>= 3.0.0)
|
32
|
+
extlib
|
33
|
+
mocha
|
34
|
+
sencha-model!
|
35
|
+
shoulda
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 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
|
+
= Sencha::Model
|
2
|
+
|
3
|
+
A simple Model mixin with adapters for various ORM frameworks such as ActiveRecord, DataMapper and MongoMapper. Sencha::Model 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 sencha-model
|
9
|
+
|
10
|
+
|
11
|
+
=== An ORM Model mixin: Sencha::Model
|
12
|
+
sencha-model contains Model mixin named <tt>Sencha::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>sencha_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 Sencha::Model
|
20
|
+
|
21
|
+
sencha_fields :exclude => [:password, :password_confirmation]
|
22
|
+
|
23
|
+
# OR
|
24
|
+
sencha_fields :name, :description
|
25
|
+
|
26
|
+
# OR
|
27
|
+
sencha_fields :only => [:name, :description] # actually the same as above
|
28
|
+
|
29
|
+
# OR
|
30
|
+
sencha_fields :additional => [:computed] # includes all database columns and an additional computed field
|
31
|
+
|
32
|
+
# OR define a column as a Hash
|
33
|
+
sencha_fields :description, :name => {"sortDir" => "ASC"}, :created_at => {"dateFormat" => "c"}
|
34
|
+
|
35
|
+
# OR render associations, association-fields will have their "mapping" property set automatically
|
36
|
+
sencha_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>Sencha::Model</tt>, try typing the following in <tt>irb</tt> console:
|
44
|
+
>> User.sencha_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 schema. This field-names were originally designed to be consumed by an <tt>Ext.data.Store</tt> from the {Ext JS Framework}[http://extjs.com]. TODO: make the field-names configurable.
|
53
|
+
|
54
|
+
You can also define different sets of fields for different representations of your model.
|
55
|
+
|
56
|
+
E.g. with the following definition:
|
57
|
+
|
58
|
+
class User < ActiveRecord::Base
|
59
|
+
include ExtJS::Model
|
60
|
+
|
61
|
+
sencha_fieldset :grid, [
|
62
|
+
:name,
|
63
|
+
:description,
|
64
|
+
{:company => [:name, :description]}
|
65
|
+
]
|
66
|
+
|
67
|
+
sencha_fieldset :combo, [:full_name]
|
68
|
+
|
69
|
+
##
|
70
|
+
# computed field
|
71
|
+
#
|
72
|
+
def full_name
|
73
|
+
"#{first_name} #{name}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
You can get store configs for both representations with
|
78
|
+
User.sencha_schema(:grid)
|
79
|
+
or
|
80
|
+
User.sencha_schema(:combo)
|
81
|
+
|
82
|
+
And the corresponding data for the representations with
|
83
|
+
User.first.to_record(:grid)
|
84
|
+
or
|
85
|
+
User.first.to_record(:combo)
|
86
|
+
|
87
|
+
=== A Testing Mixin: Sencha::TestMacros
|
88
|
+
The <tt>sencha</tt> Gem includes a small set of testing macros to help unit-test models.
|
89
|
+
Using this macro requires the 'Shoulda' gem from thoughtbot
|
90
|
+
|
91
|
+
==== Usage
|
92
|
+
In individual model unit tests:
|
93
|
+
class ModelTest < ActiveSupport::TestCase
|
94
|
+
should_have_sencha_fields_for_fieldset :fieldset_name, [: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,26 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
|
5
|
+
require 'rake/testtask'
|
6
|
+
Rake::TestTask.new(:test) do |test|
|
7
|
+
test.libs << 'lib' << 'test'
|
8
|
+
test.pattern = 'test/**/*_test.rb'
|
9
|
+
test.verbose = true
|
10
|
+
end
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'rcov/rcovtask'
|
14
|
+
Rcov::RcovTask.new do |test|
|
15
|
+
test.libs << 'test'
|
16
|
+
test.pattern = 'test/**/*_test.rb'
|
17
|
+
test.verbose = true
|
18
|
+
end
|
19
|
+
rescue LoadError
|
20
|
+
task :rcov do
|
21
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
task :default => :test
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.4.0
|
data/lib/sencha-model.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
module Sencha
|
4
|
+
module Model
|
5
|
+
require 'sencha-model/model'
|
6
|
+
|
7
|
+
# Detect orm, include appropriate mixin.
|
8
|
+
if defined?(ActiveRecord)
|
9
|
+
require 'sencha-model/adapters/active_record'
|
10
|
+
elsif defined?(DataMapper)
|
11
|
+
require 'sencha-model/adapters/data_mapper'
|
12
|
+
elsif defined?(MongoMapper)
|
13
|
+
require 'sencha-model/adapters/mongo_mapper'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,88 @@
|
|
1
|
+
##
|
2
|
+
# ActiveRecord adapter to Whorm::Model mixin.
|
3
|
+
#
|
4
|
+
module Sencha
|
5
|
+
module Model
|
6
|
+
module ClassMethods
|
7
|
+
def sencha_primary_key
|
8
|
+
self.primary_key.to_sym
|
9
|
+
end
|
10
|
+
|
11
|
+
def sencha_column_names
|
12
|
+
self.column_names.map(&:to_sym)
|
13
|
+
end
|
14
|
+
|
15
|
+
def sencha_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 sencha_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 sencha 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 sencha_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 sencha_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 sencha_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 sencha_associations
|
73
|
+
@sencha_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 Sencha
|
6
|
+
module Model
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
def sencha_primary_key
|
10
|
+
self.key.first.name
|
11
|
+
end
|
12
|
+
|
13
|
+
def sencha_column_names
|
14
|
+
self.properties.collect {|p| p.name.to_s }
|
15
|
+
end
|
16
|
+
|
17
|
+
def sencha_columns_hash
|
18
|
+
if @sencha_columns_hash.nil?
|
19
|
+
@sencha_columns_hash = {}
|
20
|
+
self.properties.each do |p|
|
21
|
+
@sencha_columns_hash[p.name] = p
|
22
|
+
end
|
23
|
+
end
|
24
|
+
@sencha_columns_hash
|
25
|
+
end
|
26
|
+
|
27
|
+
def sencha_allow_blank(col)
|
28
|
+
(col === self.key.first) ? true : col.nullable?
|
29
|
+
end
|
30
|
+
|
31
|
+
def sencha_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 sencha_associations
|
48
|
+
if @sencha_associations.nil?
|
49
|
+
@sencha_associations = {}
|
50
|
+
self.relationships.keys.each do |key|
|
51
|
+
assn = self.relationships[key]
|
52
|
+
@sencha_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
|
+
@sencha_associations
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
##
|
2
|
+
# MongoMapper adapter to Sencha::Model mixin
|
3
|
+
#
|
4
|
+
|
5
|
+
module Sencha
|
6
|
+
module Model
|
7
|
+
##
|
8
|
+
# ClassMethods
|
9
|
+
#
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
def sencha_primary_key
|
13
|
+
:id
|
14
|
+
end
|
15
|
+
|
16
|
+
def sencha_column_names
|
17
|
+
self.column_names
|
18
|
+
end
|
19
|
+
|
20
|
+
def sencha_columns_hash
|
21
|
+
self.keys
|
22
|
+
end
|
23
|
+
|
24
|
+
def sencha_associations
|
25
|
+
@sencha_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 sencha_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 sencha_allow_blank(col)
|
54
|
+
(col.name == '_id') || (col.options[:required] != true)
|
55
|
+
end
|
56
|
+
|
57
|
+
def sencha_default(col)
|
58
|
+
col.default_value
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|