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 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
- % gem sources -a http://gems.github.com (you only have to do this once)
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
- In <tt>environment.rb</tt>
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 ActiveRecord mixin: ExtJS::Model
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 it in your model. Use the class-method <tt>extjs_fields</tt> to specify those
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 "parent__name" and "parent__date")
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 script/console
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.6
1
+ 0.2.7
@@ -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
- def extjs_render_column(col)
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
- {:name => col.name, :allowBlank => (col.primary) ? true : col.null, :type => type}
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] = {:name => key, :type => type}
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
- def to_record
19
- pk = self.class.extjs_primary_key
20
- assns = self.class.extjs_associations
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
- data = {pk => self.send(pk)}
23
- self.class.extjs_record_fields.each do |f|
24
- if refl = assns[f]
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(f)
27
- data[f] = (assn) ? assn.to_record : {} # <-- a thing was requested, give emtpy thing.
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[f] = self.send(f).collect {|r| r.to_record} #CAREFUL!!!!!!!!!!!!1
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
- data[f] = self.send(f)
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.extjs_record_fields = self.extjs_column_names
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
- return {
65
- "fields" => self.extjs_record_fields.collect {|f|
66
- if columns[f.to_sym] || columns[f.to_s]
67
- field = self.extjs_render_column(columns[f.to_sym] || columns[f.to_s])
68
- field[:dateFormat] = "c" if field[:type] === :date # <-- ugly hack for date
69
- field
70
- elsif assn = associations[f]
71
- field = {:name => f, :allowBlank => true, :type => 'auto'}
72
- elsif f.is_a? Array
73
- field = {:name => f.join('__'), :type => 'auto'}
74
- else # property is a method?
75
- field = {:name => f, :allowBlank => true, :type => 'auto'}
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
- self.extjs_record_fields = self.extjs_column_names.reject {|c| excludes.find {|ex| c === ex.to_s}}.collect {|c| c}
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
- self.extjs_record_fields = only
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
- self.extjs_record_fields.concat(params) if !params.empty?
96
-
97
- #Append primary key if it's not included
98
- # I don't think we want to automatically include :id
99
- # Chris
100
- #self.extjs_record_fields << self.primary_key.to_sym if !self.extjs_record_fields.include?(self.primary_key.to_sym)
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
- # Prepare the config for fields with '.' in their names
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 process_association_fields(options)
107
- results = []
108
- options.each do |assoc, fields|
109
- fields.each do |field|
110
- results << [assoc, field]
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
- results
222
+ @extjs_used_associations
114
223
  end
115
224
  end
116
225
  end
@@ -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 extjs_render_column(col)
24
- pk = self.key.first
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
- type = (assn.options[:max].nil? && assn.options[:min].nil?) ? :belongs_to : (assn.options[:max] > 1) ? :many : nil
47
- @extjs_associations[key.to_sym] = {:name => key, :type => type}
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
@@ -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
- "id"
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] = {:name => key, :type => self.associations[key].type}
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 extjs_render_column(col)
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
- {:name => col.name, :type => type, :allowBlank => (col.name === '_id') ? true : (col.options[:required] === true) ? false : true }
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.6
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-17 00:00:00 -05:00
12
+ date: 2009-11-19 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency