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 ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ *.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in shopify-api-limits.gemspec
4
+ gemspec
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
@@ -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
+