extjs-mvc 0.2.6 → 0.2.7
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/README.rdoc +22 -12
- data/VERSION +1 -1
- data/lib/model/active_record.rb +32 -4
- data/lib/model/base.rb +163 -54
- data/lib/model/data_mapper.rb +15 -6
- data/lib/model/mongo_mapper.rb +16 -5
- metadata +2 -2
data/README.rdoc
CHANGED
@@ -1,37 +1,47 @@
|
|
1
1
|
= mvc
|
2
2
|
|
3
3
|
|
4
|
-
A collection of helpers, MVC mixins and PORs (plain-old-ruby-object) to assist with auto-generating ExtJS javascript component definitions.
|
4
|
+
A collection of helpers, MVC mixins and PORs (plain-old-ruby-object) to assist with auto-generating ExtJS Stores (Ext.data.Store) including its associated DataReader (Ext.data.JsonReader, Ext.data.XmlReader) and DataWriter (Ext.data.JsonWriter, Ext.data.XmlWriter). Also contains a helper for rendering javascript component definitions via partials.
|
5
5
|
|
6
6
|
|
7
7
|
===Installation
|
8
|
-
|
9
|
-
|
8
|
+
% sudo gem install gemcutter
|
9
|
+
% gem tumble (only have to do this once, adds gemcutter as primary gem-source)
|
10
10
|
% sudo gem install extjs-mvc
|
11
11
|
|
12
|
-
|
12
|
+
<p><b>Rails Installation:</b></p>
|
13
|
+
In <tt>environment.rb</tt>,
|
13
14
|
|
14
15
|
Rails::Initializer.run do |config|
|
15
16
|
config.gem "extjs-mvc"
|
16
17
|
end
|
17
18
|
|
19
|
+
<p><b>Merb installation:</b></p>
|
20
|
+
In <tt>config/dependencies.rb</tt>, Add extjs-mvc as a new dependency
|
21
|
+
|
22
|
+
dependency "extjs-mvc"
|
18
23
|
|
19
|
-
=== An
|
24
|
+
=== An ORM Model mixin: ExtJS::Model
|
25
|
+
extjs-mvc contains Model mixin named <tt>ExtJS::Model</tt> which works for <b>three</b> popular ORM frameworks, ActiveRecord, DataMapper and MongoMapper. The API for each framework is identical.
|
20
26
|
|
21
|
-
include
|
27
|
+
Simply include the mixin into your model. Use the class-method <tt>extjs_fields</tt> to specify those
|
22
28
|
fields with will be used to render the <tt>Ext.data.Record.create</tt> field-def'n.
|
29
|
+
|
23
30
|
class User < ActiveRecord::Base
|
24
31
|
include ExtJS::Model
|
25
32
|
|
26
33
|
extjs_fields :exclude => [:password, :password_confirmation]
|
27
|
-
# OR
|
34
|
+
# OR
|
28
35
|
extjs_fields :name, :description
|
29
|
-
# OR
|
36
|
+
# OR define a column as a Hash
|
37
|
+
extjs_fields :description, :name => {"sortDir" => "ASC"}, :created_at => {"dateFormat" => "c"}
|
38
|
+
#
|
30
39
|
extjs_fields :name, :description, :parent => [:name, :date]
|
31
|
-
# this would configure associated columns with names like
|
40
|
+
# this would configure associated columns with names like:
|
41
|
+
[...{name: "parent_name", mapping: "parent.name"}, {name: "parent_date", mapping: "parent.date"}...]
|
32
42
|
end
|
33
43
|
|
34
|
-
In
|
44
|
+
In <tt>irb</tt> console:
|
35
45
|
>> User.extjs_fields
|
36
46
|
>> User.extjs_record
|
37
47
|
=> { "idProperty"=>"id", "fields"=>[
|
@@ -42,13 +52,13 @@ In script/console
|
|
42
52
|
]}
|
43
53
|
|
44
54
|
=== An ActionController mixin: ExtJS::Controller
|
45
|
-
|
55
|
+
The <tt>extjs-mvc</tt> Gem includes a framework agnostic Controller mixin which works with both Rails and Merb.
|
46
56
|
<b>usage:</b>
|
47
57
|
|
48
58
|
class UsersController < ActionController::Base
|
49
59
|
include ExtJS::Controller
|
50
60
|
end
|
51
|
-
|
61
|
+
|
52
62
|
=== View Helper: ExtJS::Helpers::Component
|
53
63
|
|
54
64
|
<b>usage:</b>
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.7
|
data/lib/model/active_record.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
|
+
##
|
2
|
+
# ActiveRecord adapter to ExtJS::Model mixin.
|
3
|
+
#
|
1
4
|
module ExtJS
|
2
5
|
module Model
|
3
6
|
module ClassMethods
|
4
7
|
|
5
8
|
def extjs_primary_key
|
6
|
-
self.primary_key
|
9
|
+
self.primary_key.to_sym
|
7
10
|
end
|
8
11
|
|
9
12
|
def extjs_column_names
|
@@ -14,7 +17,21 @@ module ExtJS
|
|
14
17
|
self.columns_hash
|
15
18
|
end
|
16
19
|
|
17
|
-
|
20
|
+
##
|
21
|
+
# determine if supplied Column object is nullable
|
22
|
+
# @param {ActiveRecord::ConnectionAdapters::Column}
|
23
|
+
# @return {Boolean}
|
24
|
+
#
|
25
|
+
def extjs_allow_blank(col)
|
26
|
+
col.null
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# determine datatype of supplied Column object
|
31
|
+
# @param {ActiveRecord::ConnectionAdapters::Column}
|
32
|
+
# @return {Symbol}
|
33
|
+
#
|
34
|
+
def extjs_type(col)
|
18
35
|
type = col.type
|
19
36
|
case type
|
20
37
|
when :datetime || :date || :time || :timestamp
|
@@ -23,17 +40,28 @@ module ExtJS
|
|
23
40
|
type = :string
|
24
41
|
when :integer
|
25
42
|
type = :int
|
43
|
+
when :decimal
|
44
|
+
type = :float
|
26
45
|
end
|
27
|
-
|
46
|
+
type
|
28
47
|
end
|
29
48
|
|
49
|
+
##
|
50
|
+
# return a simple, normalized list of AR associations having the :name, :type and association class
|
51
|
+
# @return {Array}
|
52
|
+
#
|
30
53
|
def extjs_associations
|
31
54
|
if @extjs_associations.nil?
|
32
55
|
@extjs_associations = {}
|
33
56
|
self.reflections.keys.each do |key|
|
34
57
|
assn = self.reflections[key]
|
35
58
|
type = (assn.macro === :has_many) ? :many : assn.macro
|
36
|
-
@extjs_associations[key.to_sym] = {
|
59
|
+
@extjs_associations[key.to_sym] = {
|
60
|
+
:name => key,
|
61
|
+
:type => type,
|
62
|
+
:class => assn.class_name.constantize,
|
63
|
+
:foreign_key => assn.association_foreign_key
|
64
|
+
}
|
37
65
|
end
|
38
66
|
end
|
39
67
|
@extjs_associations
|
data/lib/model/base.rb
CHANGED
@@ -5,9 +5,19 @@ module ExtJS
|
|
5
5
|
model.send(:extend, ClassMethods)
|
6
6
|
model.send(:include, InstanceMethods)
|
7
7
|
model.class_eval do
|
8
|
+
##
|
9
|
+
# @config {Array} List of DataReader fields to render. This should probably not be a cattr_accessor
|
10
|
+
# since it's only used internally. The user adds fields via the Class method extjs_fields instead.
|
11
|
+
#
|
8
12
|
cattr_accessor :extjs_record_fields
|
13
|
+
##
|
14
|
+
# @config {String} extjs_mapping_template This a template used to render mapped field-names.
|
15
|
+
# One could use the Rails standard "{association}[{property}]" as well.
|
16
|
+
#
|
17
|
+
cattr_accessor :extjs_mapping_template
|
9
18
|
end
|
10
19
|
model.extjs_record_fields = []
|
20
|
+
model.extjs_mapping_template = "_{property}"
|
11
21
|
end
|
12
22
|
|
13
23
|
##
|
@@ -15,28 +25,39 @@ module ExtJS
|
|
15
25
|
#
|
16
26
|
module InstanceMethods
|
17
27
|
|
18
|
-
|
19
|
-
|
20
|
-
|
28
|
+
##
|
29
|
+
# Converts a model instance to a record compatible with ExtJS
|
30
|
+
# @params {Mixed} params A list of fields to use instead of this Class's extjs_record_fields
|
31
|
+
#
|
32
|
+
def to_record(*params)
|
33
|
+
|
34
|
+
fields = (params.empty?) ? self.class.extjs_record_fields : self.class.process_fields(*params)
|
35
|
+
assns = self.class.extjs_associations
|
36
|
+
pk = self.class.extjs_primary_key
|
21
37
|
|
22
|
-
|
23
|
-
self.
|
24
|
-
|
38
|
+
# build the initial field data-hash
|
39
|
+
data = {pk => self.send(pk)}
|
40
|
+
|
41
|
+
fields.each do |field|
|
42
|
+
if refl = assns[field[:name]] || assns[field[:name].to_sym]
|
25
43
|
if refl[:type] === :belongs_to
|
26
|
-
assn = self.send(
|
27
|
-
|
44
|
+
assn = self.send(field[:name])
|
45
|
+
if assn.respond_to?(:to_record)
|
46
|
+
data[field[:name]] = assn.send(:to_record, *[field[:fields]])
|
47
|
+
elsif (field[:fields])
|
48
|
+
data[field[:name]] = {}
|
49
|
+
field[:fields].each do |property|
|
50
|
+
data[field[:name]][property] = assn.send(property) if assn.respond_to?(property)
|
51
|
+
end
|
52
|
+
else
|
53
|
+
data[field[:name]] = {} # belongs_to assn that doesn't respond to to_record and no fields list
|
54
|
+
end
|
28
55
|
elsif refl[:type] === :many
|
29
|
-
data[
|
56
|
+
data[field[:name]] = self.send(field[:name]).collect {|r| r.to_record} #CAREFUL!!!!!!!!!!!!1
|
30
57
|
end
|
31
|
-
elsif f.is_a? Array
|
32
|
-
value = self
|
33
|
-
f.each do |method|
|
34
|
-
value = value.send(method)
|
35
|
-
break if value.nil?
|
36
|
-
end
|
37
|
-
data[f.join('__')] = value
|
38
58
|
else
|
39
|
-
|
59
|
+
value = self.send(field[:name])
|
60
|
+
data[field[:name]] = value.respond_to?(:to_record) ? value.to_record : value
|
40
61
|
end
|
41
62
|
end
|
42
63
|
data
|
@@ -51,66 +72,154 @@ module ExtJS
|
|
51
72
|
# render AR columns to Ext.data.Record.create format
|
52
73
|
# eg: {name:'foo', type: 'string'}
|
53
74
|
#
|
54
|
-
def extjs_record
|
55
|
-
|
75
|
+
def extjs_record(*fields)
|
56
76
|
if self.extjs_record_fields.empty?
|
57
|
-
self.
|
77
|
+
self.extjs_fields(*self.extjs_column_names)
|
58
78
|
end
|
59
|
-
|
60
|
-
pk = self.extjs_primary_key
|
61
|
-
columns = self.extjs_columns_hash
|
62
|
-
associations = self.extjs_associations
|
63
79
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
80
|
+
associations = self.extjs_associations
|
81
|
+
columns = self.extjs_columns_hash
|
82
|
+
fields = fields.empty? ? self.extjs_record_fields : self.process_fields(*fields)
|
83
|
+
pk = self.extjs_primary_key
|
84
|
+
rs = []
|
85
|
+
|
86
|
+
fields.each do |field|
|
87
|
+
field = field.dup
|
88
|
+
|
89
|
+
if col = columns[field[:name]] || columns[field[:name].to_sym] # <-- column on this model
|
90
|
+
rs << self.extjs_field(field, col)
|
91
|
+
elsif assn = associations[field[:name]] || associations[field[:name].to_sym]
|
92
|
+
assn_fields = field.delete(:fields) || nil
|
93
|
+
if assn[:class].respond_to?(:extjs_record) # <-- exec extjs_record on assn Model.
|
94
|
+
record = assn[:class].send(:extjs_record, *[assn_fields])
|
95
|
+
rs.concat(record["fields"].collect {|assn_field|
|
96
|
+
extjs_field(assn_field, :mapping => field[:name])
|
97
|
+
})
|
98
|
+
elsif assn_fields # <-- :parent => [:id, :name]
|
99
|
+
rs.concat(assn_fields.collect {|assn_field|
|
100
|
+
extjs_field(assn_field, :mapping => field[:name])
|
101
|
+
})
|
102
|
+
else
|
103
|
+
rs << extjs_field(field)
|
104
|
+
end
|
105
|
+
|
106
|
+
# attach association's foreign_key if not already included.
|
107
|
+
if (col = columns[assn[:foreign_key]] || columns[assn[:foreign_key].to_s]) && !rs.include?({:name => assn[:foreign_key].to_s})
|
108
|
+
rs << extjs_field({:name => assn[:foreign_key]}, col)
|
76
109
|
end
|
77
|
-
|
110
|
+
|
111
|
+
else # property is a method?
|
112
|
+
rs << extjs_field(field)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
return {
|
117
|
+
"fields" => rs,
|
78
118
|
"idProperty" => pk
|
79
119
|
}
|
80
120
|
end
|
81
121
|
|
122
|
+
##
|
123
|
+
# meant to be used within a Model to define the extjs record fields.
|
124
|
+
# eg:
|
125
|
+
# class User
|
126
|
+
# extjs_fields :first, :last, :email => {"sortDir" => "ASC"}, :company => [:id, :name]
|
127
|
+
# end
|
128
|
+
#
|
82
129
|
def extjs_fields(*params)
|
130
|
+
self.extjs_record_fields = self.process_fields(*params)
|
131
|
+
end
|
132
|
+
|
133
|
+
##
|
134
|
+
# Prepare a field configuration list into a normalized array of Hashes, {:name => "field_name"}
|
135
|
+
# @param {Mixed} params
|
136
|
+
# @return Array
|
137
|
+
#
|
138
|
+
def process_fields(*params)
|
139
|
+
params = [] if params.first.nil?
|
83
140
|
options = params.extract_options!
|
141
|
+
|
142
|
+
# Return immediately if pre-processed fields are detected.
|
143
|
+
# ie: [ [{:name => 'foo'}, {:name => 'bar'}] ]
|
144
|
+
# This is to handle the case where extjs_record and to_record are called recursively, in which case
|
145
|
+
# these fields have already been processed.
|
146
|
+
#
|
147
|
+
if params.length === 1 && params.first.kind_of?(Array) && !params.first.empty?
|
148
|
+
return params.first
|
149
|
+
end
|
150
|
+
|
151
|
+
fields = []
|
84
152
|
if !options.keys.empty?
|
85
153
|
if excludes = options.delete(:exclude)
|
86
|
-
|
154
|
+
fields = self.process_fields(self.extjs_column_names.reject {|c| excludes.find {|ex| c === ex.to_s}}.collect {|c| c})
|
87
155
|
elsif only = options.delete(:only)
|
88
|
-
|
156
|
+
fields = self.process_fields(only)
|
157
|
+
else
|
158
|
+
options.keys.each do |k| # <-- :email => {"sortDir" => "ASC"}
|
159
|
+
if options[k].is_a? Hash
|
160
|
+
options[k][:name] = k.to_s
|
161
|
+
fields << options[k]
|
162
|
+
elsif options[k].is_a? Array # <-- :parent => [:id, :name]
|
163
|
+
fields << {
|
164
|
+
:name => k.to_s,
|
165
|
+
:fields => process_fields(*options[k])
|
166
|
+
}
|
167
|
+
end
|
168
|
+
end
|
89
169
|
end
|
90
|
-
self.extjs_record_fields.concat(process_association_fields(options))
|
91
170
|
elsif params.empty?
|
92
171
|
return self.extjs_record_fields
|
93
172
|
end
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
173
|
+
|
174
|
+
unless params.empty?
|
175
|
+
fields.concat(params.collect {|f|
|
176
|
+
{:name => f.to_s}
|
177
|
+
})
|
178
|
+
end
|
179
|
+
fields
|
101
180
|
end
|
102
181
|
|
103
182
|
##
|
104
|
-
#
|
183
|
+
# Render a column-config object
|
184
|
+
# @param {Hash/Column} field Field-configuration Hash, probably has :name already set and possibly Ext.data.Field options.
|
185
|
+
# @param {ORM Column Object from AR, DM or MM}
|
186
|
+
#
|
187
|
+
def extjs_field(field, config=nil)
|
188
|
+
if config.kind_of? Hash
|
189
|
+
if mapping = config.delete(:mapping)
|
190
|
+
field.update( # <-- We use a template for rendering mapped field-names.
|
191
|
+
:name => mapping + self.extjs_mapping_template.gsub(/\{property\}/, field[:name]),
|
192
|
+
"mapping" => "#{mapping.to_s}.#{field[:name]}"
|
193
|
+
)
|
194
|
+
end
|
195
|
+
field.update(config) unless config.keys.empty?
|
196
|
+
elsif !config.nil? # <-- Hopfully an ORM Column object.
|
197
|
+
field.update(
|
198
|
+
"allowBlank" => self.extjs_allow_blank(config),
|
199
|
+
"type" => self.extjs_type(config)
|
200
|
+
)
|
201
|
+
field["dateFormat"] = "c" if field["type"] === :date && field["dateFormat"].nil? # <-- ugly hack for date
|
202
|
+
end
|
203
|
+
field.update("type" => "auto") if field["type"].nil?
|
204
|
+
field
|
205
|
+
end
|
206
|
+
|
207
|
+
##
|
208
|
+
# Returns an array of symbolized association names that will be referenced by a call to to_record
|
209
|
+
# i.e. [:parent1, :parent2]
|
105
210
|
#
|
106
|
-
def
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
211
|
+
def extjs_used_associations
|
212
|
+
if @extjs_used_associations.nil?
|
213
|
+
assoc = []
|
214
|
+
self.extjs_record_fields.each do |f|
|
215
|
+
#This needs to be the first condition because the others will break if f is an Array
|
216
|
+
if extjs_associations[f[:name]]
|
217
|
+
assoc << f[:name]
|
218
|
+
end
|
111
219
|
end
|
220
|
+
@extjs_used_associations = assoc.uniq
|
112
221
|
end
|
113
|
-
|
222
|
+
@extjs_used_associations
|
114
223
|
end
|
115
224
|
end
|
116
225
|
end
|
data/lib/model/data_mapper.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
##
|
2
|
+
# DataMapper adapter for ExtJS::Model mixin
|
3
|
+
#
|
1
4
|
module ExtJS
|
2
5
|
module Model
|
3
6
|
module ClassMethods
|
@@ -20,8 +23,11 @@ module ExtJS
|
|
20
23
|
@extjs_columns_hash
|
21
24
|
end
|
22
25
|
|
23
|
-
def
|
24
|
-
|
26
|
+
def extjs_allow_blank(col)
|
27
|
+
(col === self.key.first) ? true : col.nullable?
|
28
|
+
end
|
29
|
+
|
30
|
+
def extjs_type(col)
|
25
31
|
type = ((col.type.respond_to?(:primitive)) ? col.type.primitive : col.type).to_s
|
26
32
|
case type
|
27
33
|
when "DateTime", "Date", "Time"
|
@@ -34,8 +40,7 @@ module ExtJS
|
|
34
40
|
type = :int
|
35
41
|
else
|
36
42
|
type = "auto"
|
37
|
-
end
|
38
|
-
{:name => col.name.to_s, :type => type, :allowBlank => (col === pk) ? true : col.nullable? }
|
43
|
+
end
|
39
44
|
end
|
40
45
|
|
41
46
|
def extjs_associations
|
@@ -43,8 +48,12 @@ module ExtJS
|
|
43
48
|
@extjs_associations = {}
|
44
49
|
self.relationships.keys.each do |key|
|
45
50
|
assn = self.relationships[key]
|
46
|
-
|
47
|
-
|
51
|
+
@extjs_associations[key.to_sym] = {
|
52
|
+
:name => key,
|
53
|
+
:type => type = (assn.options[:max].nil? && assn.options[:min].nil?) ? :belongs_to : (assn.options[:max] > 1) ? :many : nil ,
|
54
|
+
:class => assn.parent_model,
|
55
|
+
:foreign_key => assn.child_key.first.name
|
56
|
+
}
|
48
57
|
end
|
49
58
|
end
|
50
59
|
@extjs_associations
|
data/lib/model/mongo_mapper.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
##
|
2
|
+
# MongoMapper adapter to ExtJS::Model mixin
|
3
|
+
#
|
1
4
|
module ExtJS
|
2
5
|
module Model
|
3
6
|
##
|
@@ -6,7 +9,7 @@ module ExtJS
|
|
6
9
|
module ClassMethods
|
7
10
|
|
8
11
|
def extjs_primary_key
|
9
|
-
|
12
|
+
:_id
|
10
13
|
end
|
11
14
|
|
12
15
|
def extjs_column_names
|
@@ -21,13 +24,18 @@ module ExtJS
|
|
21
24
|
if @extjs_associations.nil?
|
22
25
|
@extjs_associations = {}
|
23
26
|
self.associations.keys.each do |key|
|
24
|
-
@extjs_associations[key.to_sym] = {
|
27
|
+
@extjs_associations[key.to_sym] = {
|
28
|
+
:name => key,
|
29
|
+
:type => self.associations[key].type,
|
30
|
+
:class => self.associations[key].class_name.constantize,
|
31
|
+
:foreign_key => self.associations[key].foreign_key
|
32
|
+
}
|
25
33
|
end
|
26
34
|
end
|
27
35
|
@extjs_associations
|
28
36
|
end
|
29
37
|
|
30
|
-
def
|
38
|
+
def extjs_type(col)
|
31
39
|
type = col.type.to_s
|
32
40
|
case type
|
33
41
|
when "DateTime", "Date", "Time"
|
@@ -40,8 +48,11 @@ module ExtJS
|
|
40
48
|
type = :int
|
41
49
|
else
|
42
50
|
type = "auto"
|
43
|
-
end
|
44
|
-
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def extjs_allow_blank(col)
|
55
|
+
(col.name === '_id') ? true : (col.options[:required] === true) ? false : true
|
45
56
|
end
|
46
57
|
end
|
47
58
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: extjs-mvc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Scott
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-11-
|
12
|
+
date: 2009-11-19 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|