├── .document
├── .gitignore
├── LICENSE
├── README
├── README.rdoc
├── Rakefile
├── VERSION
├── demo-app.tar.gz
├── extjs-mvc.gemspec
├── lib
├── controller
│ └── controller.rb
├── core_ext
│ └── array
│ │ └── extract_options.rb
├── extjs-mvc.rb
├── extjs
│ ├── component.rb
│ └── data
│ │ └── store.rb
├── helpers
│ ├── component.rb
│ └── store.rb
├── model
│ ├── active_record.rb
│ ├── base.rb
│ ├── data_mapper.rb
│ └── mongo_mapper.rb
└── test
│ └── macros.rb
├── rails
└── init.rb
├── shoulda_macros
└── macros.rb
└── test
├── active_record_test.rb
├── app
├── config
│ ├── application.rb
│ └── database.yml
├── db
│ └── schema.rb
└── models
│ └── active_record
│ ├── address.rb
│ ├── data_type.rb
│ ├── group.rb
│ ├── house.rb
│ ├── location.rb
│ ├── person.rb
│ ├── user.rb
│ └── user_group.rb
├── component_test.rb
├── controller_test.rb
├── data_mapper_test.rb
├── model_test.rb
├── mongo_mapper_test.rb
├── store_test.rb
└── test_helper.rb
/.document:
--------------------------------------------------------------------------------
1 | README.rdoc
2 | lib/**/*.rb
3 | bin/*
4 | features/**/*.feature
5 | LICENSE
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.sw?
2 | .DS_Store
3 | coverage
4 | rdoc
5 | pkg
6 | *.log
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
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.
21 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | demo-app.tar.gz is the Rails app behind the tutorial at:
2 |
3 | unzip it then:
4 | >rake db:migrate
5 | >script/server
6 |
7 | In your browser, open:
8 | localhost/projects/step1 (through step9)
9 |
10 |
11 | http://www.extjs.com/blog/2009/09/30/ext-js-on-rails-a-comprehensivetutorial/
12 |
13 |
--------------------------------------------------------------------------------
/README.rdoc:
--------------------------------------------------------------------------------
1 | = mvc
2 |
3 | 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.
4 |
5 | See tutorial http://www.extjs.com/blog/2009/09/30/ext-js-on-rails-a-comprehensivetutorial/
6 |
7 | ===Installation
8 | % sudo gem install gemcutter
9 | % gem tumble (only have to do this once, adds gemcutter as primary gem-source)
10 | % sudo gem install extjs-mvc
11 |
12 | Rails Installation:
13 | In environment.rb,
14 |
15 | Rails::Initializer.run do |config|
16 | config.gem "extjs-mvc"
17 | end
18 |
19 | Merb installation:
20 | In config/dependencies.rb, Add extjs-mvc as a new dependency
21 |
22 | dependency "extjs-mvc"
23 |
24 | === An ORM Model mixin: ExtJS::Model
25 | extjs-mvc contains Model mixin named ExtJS::Model which works for three popular ORM frameworks, ActiveRecord, DataMapper and MongoMapper. The API for each framework is identical.
26 |
27 | Simply include the mixin into your model. Use the class-method extjs_fields to specify those
28 | fields with will be used to render the Ext.data.Record.create field-def'n.
29 |
30 | class User < ActiveRecord::Base
31 | include ExtJS::Model
32 |
33 | extjs_fields :exclude => [:password, :password_confirmation]
34 |
35 | # OR
36 | extjs_fields :name, :description
37 |
38 | # OR
39 | extjs_fields :only => [:name, :description] # actually the same as above
40 |
41 | # OR
42 | extjs_fields :additional => [:computed] # includes all database columns and an additional computed field
43 |
44 | # OR define a column as a Hash
45 | extjs_fields :description, :name => {"sortDir" => "ASC"}, :created_at => {"dateFormat" => "c"}
46 |
47 | # OR render associations, association-fields will have their "mapping" property set automatically
48 | extjs_fields :name, :description, :company => [:name, :description]
49 |
50 | def computed
51 | name.blank? ? login : name
52 | end
53 | end
54 |
55 | After including the model mixin ExtJS::Model, try typing the following in irb console:
56 | >> User.extjs_record
57 | => { :idProperty=>"id", :fields=>[
58 | {:type=>'int', :allowBlank=>true, :name=>"id"},
59 | {:type=>'string', :allowBlank=>false, :name=>"first", :defaultValue => nil},
60 | {:type=>'string', :allowBlank=>false, :name=>"last", :defaultValue => nil},
61 | {:type=>'string', :allowBlank=>false, :name=>"email", :defaultValue => nil}
62 | ]}
63 |
64 | An auto-generated Ext.data.JsonReader configuration!
65 |
66 |
67 | You can also define different sets of fields for different representations of your model.
68 |
69 | E.g. with the following definition:
70 |
71 | class User < ActiveRecord::Base
72 | include ExtJS::Model
73 |
74 | extjs_fieldset :grid, fields => [:name, :description, :company => [:name, :description]]
75 | extjs_fieldset :combo, [:full_name]
76 |
77 | def full_name
78 | "#{first_name} #{name}"
79 | end
80 | end
81 |
82 | You can get store configs for both representations with
83 | User.extjs_record(:grid)
84 | or
85 | User.extjs_record(:combo)
86 |
87 | And the corresponding data for the representations with
88 | User.first.to_record(:grid)
89 | or
90 | User.first.to_record(:combo)
91 |
92 |
93 | === An ActionController mixin: ExtJS::Controller
94 | The extjs-mvc Gem includes a framework agnostic Controller mixin which works with both Rails and Merb. Include this mixin into any controller which will need to generate an Ext.data.Store.
95 | usage:
96 |
97 | class UsersController < ActionController::Base
98 | include ExtJS::Controller
99 | end
100 |
101 | === View Helper: ExtJS::Helpers::Component
102 |
103 | usage:
104 |
105 | class UserController < ActionController::Base
106 | include ExtJS::Controller
107 | helper ExtJS::Helpers::Component
108 | end
109 |
110 | Now render Ext components using helper method extjs_component
111 |
112 | @viewport = extjs_component(
113 | "xtype" => "viewport",
114 | "frame" => true,
115 | "layout" => "border")
116 | @viewport.add("xtype" => "panel", "contentEl" => "hd", "region" => "north", "height" => 30)
117 | @viewport.add(:partial => "/users/grid", "itemId" => "users-grid", "region" => "west")
118 | @viewport.add(:partial => "/tasks/grid", "itemId" => "tasks-grid", "region" => "center")
119 | @viewport.add("xtype" => "panel", "contentEl" => "ft", "region" => "south", "height" => 20)
120 |
121 | Note how it can also render partials. Partials will be invoked with a local-variable named "container", a reference to the
122 | parent Ext::Component instance which added the partial. If no "container" is specified, it would be expected that your partial
123 | would provide its own "renderTo" or "contentEl" property, just as in Ext.Component from ExtJS javascript library.
124 |
125 |
126 | === View Helper: ExtJS::Helpers::Store
127 |
128 | Renders an Ext.data.Store with helper method extjs_store
129 |
130 | class UserController < ActionController::Base
131 | include ExtJS::Controller
132 | helper ExtJS::Helpers::Store
133 | end
134 |
135 | Now render a store in an erb template:
136 |
137 | @store = extjs_store(
138 | :controller => "users",
139 | :fieldset => :grid, # <-- Specify a particular fieldset as defined in the Model (used to render DataReader)
140 | :proxy => "http" # <-- default
141 | :format => "json" # <-- default
142 | :model => "user", # <-- default: controller_name.singularize
143 | :writer => {:encode => false},
144 | :config => { # <-- standard Ext.data.Store config-params
145 | "autoLoad" => true
146 | "autoSave" => true
147 | }
148 | )
149 |
150 | %= @store.render %
151 |
152 | === A Testing Mixin: ExtJS::TestMacros
153 | The extjs-mvc Gem includes a small set of testing macros to help unit-test models.
154 | This requires the 'Shoulda' gem from thoughtbot. Include this mixin inside the
155 | ActiveSupport::TestCase class in test/test_helper.rb
156 |
157 | ==== Usage
158 | test/test_helper.rb
159 | class ActiveSupport::TestCase
160 | extend ExtJS::TestMacros
161 | #...
162 | end
163 |
164 | In individual model unit tests:
165 | class ModelTest < ActiveSupport::TestCase
166 | should_require_extjs_fields :name, :email, :city
167 | #...
168 | #other tests
169 | end
170 |
171 |
172 | == Note on Patches/Pull Requests
173 |
174 | * Fork the project.
175 | * Make your feature addition or bug fix.
176 | * Add tests for it. This is important so I don't break it in a
177 | future version unintentionally.
178 | * Commit, do not mess with rakefile, version, or history.
179 | (if you want to have your own version, that is fine but
180 | bump version in a commit by itself I can ignore when I pull)
181 | * Send me a pull request. Bonus points for topic branches.
182 |
183 | == Copyright
184 |
185 | Copyright (c) 2009 Chris Scott. See LICENSE for details.
186 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'rake'
3 |
4 | begin
5 | require 'jeweler'
6 | Jeweler::Tasks.new do |gem|
7 | gem.name = "extjs-mvc"
8 | gem.summary = %Q{Ruby ORM tools to assist with rendering Ext.data.Store}
9 | gem.description = %Q{MVC tools to assist with ExtJS development in Rails and Merb}
10 | gem.email = "christocracy@gmail.com"
11 | gem.homepage = "http://github.com/extjs/mvc"
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
65 |
--------------------------------------------------------------------------------
/VERSION:
--------------------------------------------------------------------------------
1 | 0.3.10
2 |
--------------------------------------------------------------------------------
/demo-app.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/extjs/mvc/b1972ca38457380e2acc1bfba113c74318881a69/demo-app.tar.gz
--------------------------------------------------------------------------------
/extjs-mvc.gemspec:
--------------------------------------------------------------------------------
1 | # Generated by jeweler
2 | # DO NOT EDIT THIS FILE DIRECTLY
3 | # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4 | # -*- encoding: utf-8 -*-
5 |
6 | Gem::Specification.new do |s|
7 | s.name = %q{extjs-mvc}
8 | s.version = "0.3.9"
9 |
10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11 | s.authors = ["Chris Scott"]
12 | s.date = %q{2010-03-02}
13 | s.description = %q{MVC tools to assist with ExtJS development in Rails and Merb}
14 | s.email = %q{christocracy@gmail.com}
15 | s.extra_rdoc_files = [
16 | "LICENSE",
17 | "README",
18 | "README.rdoc"
19 | ]
20 | s.files = [
21 | "LICENSE",
22 | "README",
23 | "README.rdoc",
24 | "Rakefile",
25 | "VERSION",
26 | "lib/controller/controller.rb",
27 | "lib/core_ext/array/extract_options.rb",
28 | "lib/extjs-mvc.rb",
29 | "lib/extjs/component.rb",
30 | "lib/extjs/data/store.rb",
31 | "lib/helpers/component.rb",
32 | "lib/helpers/store.rb",
33 | "lib/model/active_record.rb",
34 | "lib/model/base.rb",
35 | "lib/model/data_mapper.rb",
36 | "lib/model/mongo_mapper.rb",
37 | "lib/test/macros.rb",
38 | "test/active_record_test.rb",
39 | "test/app/config/application.rb",
40 | "test/app/config/database.yml",
41 | "test/app/db/schema.rb",
42 | "test/app/models/active_record/address.rb",
43 | "test/app/models/active_record/data_type.rb",
44 | "test/app/models/active_record/group.rb",
45 | "test/app/models/active_record/house.rb",
46 | "test/app/models/active_record/location.rb",
47 | "test/app/models/active_record/person.rb",
48 | "test/app/models/active_record/user.rb",
49 | "test/app/models/active_record/user_group.rb",
50 | "test/component_test.rb",
51 | "test/controller_test.rb",
52 | "test/data_mapper_test.rb",
53 | "test/debug.log",
54 | "test/model_test.rb",
55 | "test/mongo_mapper_test.rb",
56 | "test/store_test.rb",
57 | "test/test_helper.rb"
58 | ]
59 | s.homepage = %q{http://github.com/extjs/mvc}
60 | s.rdoc_options = ["--charset=UTF-8"]
61 | s.require_paths = ["lib"]
62 | s.rubygems_version = %q{1.3.6}
63 | s.summary = %q{Ruby ORM tools to assist with rendering Ext.data.Store}
64 |
65 | if s.respond_to? :specification_version then
66 | current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
67 | s.specification_version = 3
68 |
69 | if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
70 | s.add_development_dependency(%q, [">= 0"])
71 | s.add_development_dependency(%q, [">= 0"])
72 | s.add_development_dependency(%q, [">= 0"])
73 | else
74 | s.add_dependency(%q, [">= 0"])
75 | s.add_dependency(%q, [">= 0"])
76 | s.add_dependency(%q, [">= 0"])
77 | end
78 | else
79 | s.add_dependency(%q, [">= 0"])
80 | s.add_dependency(%q, [">= 0"])
81 | s.add_dependency(%q, [">= 0"])
82 | end
83 | end
84 |
85 |
--------------------------------------------------------------------------------
/lib/controller/controller.rb:
--------------------------------------------------------------------------------
1 | module ExtJS::Controller
2 |
3 | def self.included(controller)
4 | controller.send(:extend, ClassMethods)
5 | end
6 |
7 | ##
8 | # Controller class methods
9 | #
10 | module ClassMethods
11 |
12 | def extjs_root(value=nil)
13 | ExtJS::MVC.root = value unless value.nil?
14 | ExtJS::MVC.root
15 | end
16 |
17 | def extjs_success_property(value=nil)
18 | ExtJS::MVC.success_property = value unless value.nil?
19 | ExtJS::MVC.success_property
20 | end
21 |
22 | def extjs_message_property(value=nil)
23 | ExtJS::MVC.message_property = value unless value.nil?
24 | ExtJS::MVC.message_property
25 | end
26 |
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/core_ext/array/extract_options.rb:
--------------------------------------------------------------------------------
1 | ##
2 | # add Rails-style Array#extract_options! method
3 | #
4 | module ExtJS
5 | module CoreExtensions
6 | module Array
7 | module ExtractOptions
8 | def extract_options!
9 | last.is_a?(::Hash) ? pop : {}
10 | end
11 | end
12 | end
13 | end
14 | end
15 | Array.send(:include, ExtJS::CoreExtensions::Array::ExtractOptions)
--------------------------------------------------------------------------------
/lib/extjs-mvc.rb:
--------------------------------------------------------------------------------
1 | module ExtJS
2 | class MVC
3 | @@success_property = :success
4 | @@message_property = :message
5 | @@root = :data
6 | cattr_accessor :success_property
7 | cattr_accessor :message_property
8 | cattr_accessor :root
9 |
10 | require 'model/base'
11 |
12 | # Detect orm, include appropriate mixin.
13 | if defined?(ActiveRecord)
14 | require 'model/active_record'
15 | elsif defined?(DataMapper)
16 | require 'model/data_mapper'
17 | elsif defined?(MongoMapper)
18 | require 'model/mongo_mapper'
19 | else
20 | raise StandardError.new("extjs-mvc could not detect an ORM framework. Be sure to include your ORM framework before initializing extjs-mvc Gem.")
21 | end
22 |
23 | # Rails-style Array#extract_options! used heavily
24 | if defined?(Merb)
25 | require 'core_ext/array/extract_options'
26 | end
27 |
28 | # ExtJS Component and Store wrappers
29 | require 'extjs/component'
30 | require 'extjs/data/store'
31 |
32 | # Component/Store view-helpers
33 | require 'helpers/component'
34 | require 'helpers/store'
35 |
36 | # Controller mixin. Works for both Rails and Merb.
37 | require 'controller/controller'
38 | end
39 | end
40 |
41 |
--------------------------------------------------------------------------------
/lib/extjs/component.rb:
--------------------------------------------------------------------------------
1 | ##
2 | # @class ExtJS::Component
3 | #
4 | class ExtJS::Component
5 | attr_accessor :config
6 | def initialize(params)
7 | @config = params#params.extract_options!
8 | @controller = @config.delete(:controller) unless @config[:controller].nil?
9 |
10 | @config["items"] = [] if config["items"].nil?
11 |
12 | if container = @config.delete(:container)
13 | container.add(self)
14 | end
15 | @partial_config = nil
16 | end
17 |
18 | def apply(params)
19 | @config.merge!(params)
20 | end
21 |
22 | ##
23 | # Adds a config {} or ExtJS::Component instance to this component's items collection.
24 | # NOTE: When :partial option is used a String will of course be returned. Otherwise an ExtJS::Component
25 | # instance will be returned.
26 | # @return {String/ExtJS::Component}
27 | def add(*config)
28 |
29 | options = config.extract_options!
30 | if !options.keys.empty?
31 | if url = options.delete(:partial)
32 | # rendering a partial, cache the config until partial calls #add method. @see else.
33 | @partial_config = options
34 | if (@controller.respond_to?(:partial))
35 | # Merb
36 | return @controller.partial(url, :with => self, :as => :container)
37 | else
38 | # Rails
39 | return @controller.render(:partial => url, :locals => {:container => self})
40 | end
41 | else
42 | options.merge!(@partial_config) unless @partial_config.nil?
43 | options[:controller] = @controller unless @controller.nil?
44 | cmp = ExtJS::Component.new(options)
45 | @partial_config = nil
46 | @config["items"] << cmp
47 | return cmp
48 | end
49 | elsif !config.empty? && config.first.kind_of?(ExtJS::Component)
50 | cmp = config.first
51 | cmp.apply(@partial_config) unless @partial_config.nil?
52 | @partial_config = nil
53 | @config["items"] << cmp.config
54 | return cmp
55 | end
56 | end
57 |
58 | def to_json
59 | config.to_json
60 | end
61 |
62 | def render
63 | @config.delete("items") if @config["items"].empty?
64 |
65 | # If there are any listeners attached in json, we have to get rid of double-quotes in order to expose
66 | # the javascript object.
67 | # eg: "listeners":"SomeController.listeners.grid" -> {"listeners":SomeController.listeners.grid, ...}
68 | json = @config.to_json.gsub(/\"(listeners|handler|scope)\":\s?\"([a-zA-Z\.\[\]\(\)]+)\"/, '"\1":\2')
69 | "Ext.ComponentMgr.create(#{json});"
70 | end
71 | end
72 |
--------------------------------------------------------------------------------
/lib/extjs/data/store.rb:
--------------------------------------------------------------------------------
1 | ##
2 | # ExtJS::Data::Store
3 | #
4 | module ExtJS::Data
5 | class Store
6 | attr_accessor :id, :format, :type, :controller, :model
7 |
8 | def initialize(*params)
9 | options = params.extract_options!
10 |
11 | @config = options[:config] || {}
12 | @format = options[:format] || 'json'
13 | @fieldset = options[:fieldset] || :default
14 | @schema = options[:schema]
15 | @proxy = options[:proxy] || 'http'
16 | @writer = options[:writer]
17 | @type = (options[:type].nil?) ? @proxy === 'direct' ? 'Ext.data.DirectStore' : "Ext.data.#{@format.capitalize}Store" : options[:type]
18 |
19 | @controller = self.class.get_controller(options[:controller])
20 | @model = self.class.get_model(options[:controller], options[:model])
21 |
22 | # Merge Reader/Proxy config
23 | @config.merge!(reader)
24 | @config.merge!(proxy)
25 |
26 | @config["baseParams"] = {} if @config["baseParams"].nil?
27 | @config["baseParams"].update("fieldset" => @fieldset)
28 |
29 | @config["format"] = @format
30 |
31 | # Set storeId implicitly based upon Model name if not set explicitly
32 | @id = @config["storeId"] = @model.to_s.downcase unless @config["storeId"]
33 | end
34 |
35 | ##
36 | # pre-load a store with data. Not yet tested. In theory, this *should* work.
37 | #
38 | def load(*params)
39 | #@config["loadData"] = @model.all(params).collect {|rec| rec.to_record }
40 | end
41 |
42 | ##
43 | # renders the configured store
44 | # @param {Boolean} script_tag [true] Not yet implemented. Always renders tags.
45 | def render(script_tag = true)
46 | script = ''
47 | # ugly hack for DirectProxy API. Have to add an Ext.onReady() after the Store constructor to set API
48 | if @proxy === 'direct'
49 | auto_load = @config.delete("autoLoad")
50 | cname = @controller.controller_name.capitalize
51 | script = "Ext.onReady(function() { var s = Ext.StoreMgr.get('#{@config["storeId"]}');"
52 | if (@config["directFn"])
53 | script += "s.proxy.directFn = #{cname}.#{@config["directFn"]};"
54 | else
55 | script += "s.proxy.setApi({create:#{cname}.#{@config["api"]["create"]},read:#{cname}.#{@config["api"]["read"]},update:#{cname}.#{@config["api"]["update"]},destroy:#{cname}.#{@config["api"]["destroy"]}});"
56 | end
57 | if auto_load
58 | script += "s.load();"
59 | end
60 | script += "});"
61 | end
62 |
63 | if @writer # <-- ugly hack because 3.0.1 can't deal with Writer as config-param
64 | json = @config.to_json
65 | json[json.length-1] = ','
66 | json += "\"writer\":new Ext.data.#{@format.capitalize}Writer(#{@writer.to_json})}"
67 | ""
68 | else
69 | ""
70 | end
71 | end
72 |
73 | private
74 |
75 | def self.get_controller(name)
76 | begin
77 | if (defined?(Rails))
78 | "#{name.to_s.camelize}Controller".constantize
79 | else
80 | Extlib::Inflection.constantize("#{Extlib::Inflection.camelize(name)}")
81 | end
82 | rescue NameError
83 | throw NameError.new("ExtJS::Store failed with an unknown controller named '#{name.to_s}'")
84 | end
85 | end
86 |
87 | def self.get_model(controller, model)
88 | unless model.class == Class
89 | begin
90 | if (defined?(Rails))
91 | model = ((model) ? model : controller.singularize).camelize.constantize
92 | else
93 | model = Extlib::Inflection.constantize(Extlib::Inflection.camelize(((model) ? model : Extlib::Inflection.singularize(controller))))
94 | end
95 | rescue NameError => e
96 | throw NameError.new("EXTJS::Store found an unknown model #{model.to_s})")
97 | end
98 | end
99 | model
100 | end
101 |
102 | def proxy
103 | proxy = {}
104 | if @proxy === 'direct'
105 | actions = ['create', 'read', 'update', 'destroy']
106 | proxy["api"] = {}
107 | @controller.direct_actions.each_index do |n|
108 | proxy["api"][actions[n]] = @controller.direct_actions[n][:name]
109 | end
110 | else
111 | if @config["api"]
112 | proxy["api"] = {}
113 | @config["api"].each {|k,v| proxy["api"][k] = "/#{@controller.controller_name}/#{v}" }
114 | else
115 | proxy["url"] = "/#{@controller.controller_name}.#{@format.to_s}"
116 | end
117 | end
118 | proxy
119 | end
120 |
121 | def reader
122 | {
123 | "successProperty" => @controller.extjs_success_property,
124 | "root" => @controller.extjs_root,
125 | "messageProperty" => @controller.extjs_message_property
126 | }.merge(@schema || @model.extjs_record(@fieldset))
127 | end
128 |
129 |
130 | end
131 | end
132 |
--------------------------------------------------------------------------------
/lib/helpers/component.rb:
--------------------------------------------------------------------------------
1 | ##
2 | # ExtJS::Helpers::Component
3 | #
4 | module ExtJS::Helpers
5 | module Component
6 | ##
7 | # add class-var @@extjs_on_ready
8 | def self.included(helper)
9 |
10 | end
11 |
12 | def extjs_component(*params)
13 | options = params.extract_options!
14 | options[:controller] = self
15 | ExtJS::Component.new(options)
16 | end
17 |
18 | ##
19 | # Adds a script or ExtJS::Component instance to on_ready queue. The queue is emptied and rendered to
20 | # via #extjs_render
21 | #
22 | def extjs_onready(*params)
23 | @onready_queue = [] if @onready_queue.nil?
24 | params.each do |cmp|
25 | @onready_queue << cmp
26 | end
27 | end
28 |
29 | ##
30 | # Empties the on_ready queue. Renders within tags
31 | #
32 | def extjs_render
33 | @onready_queue = [] if @onready_queue.nil?
34 | ""
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/helpers/store.rb:
--------------------------------------------------------------------------------
1 | module ExtJS::Helpers
2 | module Store
3 | def extjs_store(*params)
4 | ExtJS::Data::Store.new(*params)
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/lib/model/active_record.rb:
--------------------------------------------------------------------------------
1 | ##
2 | # ActiveRecord adapter to ExtJS::Model mixin.
3 | #
4 | module ExtJS
5 | module Model
6 | module ClassMethods
7 |
8 | def extjs_primary_key
9 | self.primary_key.to_sym
10 | end
11 |
12 | def extjs_column_names
13 | self.column_names.map(&:to_sym)
14 | end
15 |
16 | def extjs_columns_hash
17 | self.columns_hash.symbolize_keys
18 | end
19 |
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 | # if the column is the primary key always allow it to be blank.
27 | # Otherwise we could not create new records with ExtJS because
28 | # new records have no id and thus cannot be valid
29 | col.name == self.primary_key || col.null
30 | end
31 |
32 | ##
33 | # returns the default value
34 | # @param {ActiveRecord::ConnectionAdapters::Column}
35 | # @return {Mixed}
36 | #
37 | def extjs_default(col)
38 | col.default
39 | end
40 |
41 | ##
42 | # returns the corresponding column name of the type column for a polymorphic association
43 | # @param {String/Symbol} the id column name for this association
44 | # @return {Symbol}
45 | def extjs_polymorphic_type(id_column_name)
46 | id_column_name.to_s.gsub(/_id\Z/, '_type').to_sym
47 | end
48 |
49 | ##
50 | # determine datatype of supplied Column object
51 | # @param {ActiveRecord::ConnectionAdapters::Column}
52 | # @return {String}
53 | #
54 | def extjs_type(col)
55 | type = col.type.to_s
56 | case type
57 | when "datetime", "date", "time", "timestamp"
58 | type = "date"
59 | when "text"
60 | type = "string"
61 | when "integer"
62 | type = "int"
63 | when "decimal"
64 | type = "float"
65 | end
66 | type
67 | end
68 |
69 | ##
70 | # return a simple, normalized list of AR associations having the :name, :type and association class
71 | # @return {Array}
72 | #
73 | def extjs_associations
74 | @extjs_associations ||= self.reflections.inject({}) do |memo, (key, assn)|
75 | type = (assn.macro === :has_many || assn.macro === :has_and_belongs_to_many) ? :many : assn.macro
76 | memo[key.to_sym] = {
77 | :name => key.to_sym,
78 | :type => type,
79 | :class => assn.options[:polymorphic] ? nil : assn.class_name.constantize,
80 | :foreign_key => assn.association_foreign_key.to_sym,
81 | :is_polymorphic => !!assn.options[:polymorphic]
82 | }
83 | memo
84 | end
85 | end
86 | end
87 | end
88 | end
89 |
90 |
--------------------------------------------------------------------------------
/lib/model/base.rb:
--------------------------------------------------------------------------------
1 | module ExtJS
2 | module Model
3 |
4 | def self.included(model)
5 | model.send(:extend, ClassMethods)
6 | model.send(:include, InstanceMethods)
7 | ##
8 | # @config {String} extjs_parent_trail_template This a template used to render mapped field-names.
9 | # Default is Proc.new{ |field_name| "_#{field_name}" }
10 | # You could also use the Rails standard
11 | # Proc.new{ |field_name| "[#{field_name}]" }
12 | #
13 | model.cattr_accessor :extjs_parent_trail_template
14 | model.extjs_parent_trail_template = Proc.new{ |field_name| "_#{field_name}" } if model.extjs_parent_trail_template.nil?
15 | end
16 |
17 | ##
18 | # InstanceMethods
19 | #
20 | module InstanceMethods
21 |
22 | ##
23 | # Converts a model instance to a record compatible with ExtJS
24 | #
25 | # The first parameter should be the fieldset for which the record will be returned.
26 | # If no parameter is provided, then the default fieldset will be choosen
27 | # Alternativly the first parameter can be a Hash with a :fields member to directly specify
28 | # the fields to use for the record.
29 | #
30 | # All these are valid calls:
31 | #
32 | # user.to_record # returns record for :default fieldset
33 | # # (fieldset is autmatically defined, if not set)
34 | #
35 | # user.to_record :fieldset # returns record for :fieldset fieldset
36 | # # (fieldset is autmatically defined, if not set)
37 | #
38 | # user.to_record :fields => [:id, :password]
39 | # # returns record for the fields 'id' and 'password'
40 | #
41 | # For even more valid options for this method (which all should not be neccessary to use)
42 | # have a look at ExtJS::Model::Util.extract_fieldset_and_options
43 | def to_record(*params)
44 | fieldset, options = Util.extract_fieldset_and_options params
45 |
46 | fields = []
47 | if options[:fields].empty?
48 | fields = self.class.extjs_get_fields_for_fieldset(fieldset)
49 | else
50 | fields = self.class.process_fields(*options[:fields])
51 | end
52 |
53 | assns = self.class.extjs_associations
54 | pk = self.class.extjs_primary_key
55 |
56 | # build the initial field data-hash
57 | data = {pk => self.send(pk)}
58 |
59 | fields.each do |field|
60 | next if data.has_key? field[:name] # already processed (e.g. explicit mentioning of :id)
61 |
62 | value = nil
63 | if association_reflection = assns[field[:name]] # if field is an association
64 | association = self.send(field[:name])
65 |
66 | # skip this association if we already visited it
67 | # otherwise we could end up in a cyclic reference
68 | next if options[:visited_classes].include? association.class
69 |
70 | case association_reflection[:type]
71 | when :belongs_to, :has_one
72 | if association.respond_to? :to_record
73 | assn_fields = field[:fields]
74 | if assn_fields.nil?
75 | assn_fields = association.class.extjs_get_fields_for_fieldset(field.fetch(:fieldset, fieldset))
76 | end
77 |
78 | value = association.to_record :fields => assn_fields,
79 | :visited_classes => options[:visited_classes] + [self.class]
80 | else
81 | value = {}
82 | (field[:fields]||[]).each do |sub_field|
83 | value[sub_field[:name]] = association.send(sub_field[:name]) if association.respond_to? sub_field[:name]
84 | end
85 | end
86 | if association_reflection[:type] == :belongs_to
87 | # Append associations foreign_key to data
88 | data[association_reflection[:foreign_key]] = self.send(association_reflection[:foreign_key])
89 | if association_reflection[:is_polymorphic]
90 | foreign_type = self.class.extjs_polymorphic_type(association_reflection[:foreign_key])
91 | data[foreign_type] = self.send(foreign_type)
92 | end
93 | end
94 | when :many
95 | value = association.collect { |r| r.to_record } # use carefully, can get HUGE
96 | end
97 | else # not an association -> get the method's value
98 | value = self.send(field[:name])
99 | value = value.to_record if value.respond_to? :to_record
100 | end
101 | data[field[:name]] = value
102 | end
103 | data
104 | end
105 | end
106 |
107 | ##
108 | # ClassMethods
109 | #
110 | module ClassMethods
111 | ##
112 | # render AR columns to Ext.data.Record.create format
113 | # eg: {name:'foo', type: 'string'}
114 | #
115 | # The first parameter should be the fieldset for which the record definition will be returned.
116 | # If no parameter is provided, then the default fieldset will be choosen
117 | # Alternativly the first parameter can be a Hash with a :fields member to directly specify
118 | # the fields to use for the record config.
119 | #
120 | # All these are valid calls:
121 | #
122 | # User.extjs_record # returns record config for :default fieldset
123 | # # (fieldset is autmatically defined, if not set)
124 | #
125 | # User.extjs_record :fieldset # returns record config for :fieldset fieldset
126 | # # (fieldset is autmatically defined, if not set)
127 | #
128 | # User.extjs_record :fields => [:id, :password]
129 | # # returns record config for the fields 'id' and 'password'
130 | #
131 | # For even more valid options for this method (which all should not be neccessary to use)
132 | # have a look at ExtJS::Model::Util.extract_fieldset_and_options
133 | def extjs_record(*params)
134 | fieldset, options = Util.extract_fieldset_and_options params
135 |
136 | if options[:fields].empty?
137 | fields = self.extjs_get_fields_for_fieldset(fieldset)
138 | else
139 | fields = self.process_fields(*options[:fields])
140 | end
141 |
142 | associations = self.extjs_associations
143 | columns = self.extjs_columns_hash
144 | pk = self.extjs_primary_key
145 | rs = []
146 |
147 | fields.each do |field|
148 |
149 | field = Marshal.load(Marshal.dump(field)) # making a deep copy
150 |
151 | if col = columns[field[:name]] # <-- column on this model
152 | rs << self.extjs_field(field, col)
153 | elsif assn = associations[field[:name]]
154 | # skip this association if we already visited it
155 | # otherwise we could end up in a cyclic reference
156 | next if options[:visited_classes].include? assn[:class]
157 |
158 | assn_fields = field[:fields]
159 | if assn[:class].respond_to?(:extjs_record) # <-- exec extjs_record on assn Model.
160 | if assn_fields.nil?
161 | assn_fields = assn[:class].extjs_get_fields_for_fieldset(field.fetch(:fieldset, fieldset))
162 | end
163 |
164 | record = assn[:class].extjs_record(field.fetch(:fieldset, fieldset), { :visited_classes => options[:visited_classes] + [self], :fields => assn_fields})
165 | rs.concat(record[:fields].collect { |assn_field|
166 | self.extjs_field(assn_field, :parent_trail => field[:name], :mapping => field[:name], :allowBlank => true) # <-- allowBlank on associated data?
167 | })
168 | elsif assn_fields # <-- :parent => [:id, :name, :sub => [:id, :name]]
169 | field_collector = Proc.new do |parent_trail, mapping, assn_field|
170 | if assn_field.is_a?(Hash) && assn_field.keys.size == 1 && assn_field.keys[0].is_a?(Symbol) && assn_field.values[0].is_a?(Array)
171 | field_collector.call(parent_trail.to_s + self.extjs_parent_trail_template.call(assn_field.keys.first), "#{mapping}.#{assn_field.keys.first}", assn_field.values.first)
172 | else
173 | self.extjs_field(assn_field, :parent_trail => parent_trail, :mapping => mapping, :allowBlank => true)
174 | end
175 | end
176 | rs.concat(assn_fields.collect { |assn_field| field_collector.call(field[:name], field[:name], assn_field) })
177 | else
178 | rs << extjs_field(field)
179 | end
180 |
181 | # attach association's foreign_key if not already included.
182 | if columns.has_key?(assn[:foreign_key]) && !rs.any? { |r| r[:name] == assn[:foreign_key] }
183 | rs << extjs_field({:name => assn[:foreign_key]}, columns[assn[:foreign_key]])
184 | end
185 | # attach association's type if polymorphic association and not alredy included
186 | if assn[:is_polymorphic]
187 | foreign_type = self.extjs_polymorphic_type(assn[:foreign_key])
188 | if columns.has_key?(foreign_type) && !rs.any? { |r| r[:name] == foreign_type }
189 | rs << extjs_field({:name => foreign_type}, columns[foreign_type])
190 | end
191 | end
192 | else # property is a method?
193 | rs << extjs_field(field)
194 | end
195 | end
196 |
197 | return {
198 | :fields => rs,
199 | :idProperty => pk
200 | }
201 | end
202 |
203 | ##
204 | # meant to be used within a Model to define the extjs record fields.
205 | # eg:
206 | # class User
207 | # extjs_fieldset :grid, [:first, :last, :email => {"sortDir" => "ASC"}, :company => [:id, :name]]
208 | # end
209 | # or
210 | # class User
211 | # extjs_fieldset :last, :email => {"sortDir" => "ASC"}, :company => [:id, :name] # => implies fieldset name :default
212 | # end
213 | #
214 | def extjs_fieldset(*params)
215 | fieldset, options = Util.extract_fieldset_and_options params
216 | var_name = :"@extjs_fieldsets__#{fieldset}"
217 | self.instance_variable_set( var_name, self.process_fields(*options[:fields]) )
218 | end
219 |
220 | def extjs_get_fields_for_fieldset(fieldset)
221 | var_name = :"@extjs_fieldsets__#{fieldset}"
222 | super_value = nil
223 | unless self.instance_variable_get( var_name )
224 | if self.superclass.respond_to? :extjs_get_fields_for_fieldset
225 | super_value = self.superclass.extjs_get_fields_for_fieldset(fieldset)
226 | end
227 | self.extjs_fieldset(fieldset, self.extjs_column_names) unless super_value
228 | end
229 | super_value || self.instance_variable_get( var_name )
230 | end
231 |
232 | ##
233 | # shortcut to define the default fieldset. For backwards-compatibility.
234 | #
235 | def extjs_fields(*params)
236 | self.extjs_fieldset(:default, {
237 | :fields => params
238 | })
239 | end
240 |
241 | ##
242 | # Prepare a field configuration list into a normalized array of Hashes, {:name => "field_name"}
243 | # @param {Mixed} params
244 | # @return {Array} of Hashes
245 | #
246 | def process_fields(*params)
247 | fields = []
248 | if params.size == 1 && params.last.is_a?(Hash) # peek into argument to see if its an option hash
249 | options = params.last
250 | if options.has_key?(:additional) && options[:additional].is_a?(Array)
251 | return self.process_fields(*(self.extjs_column_names + options[:additional].map(&:to_sym)))
252 | elsif options.has_key?(:exclude) && options[:exclude].is_a?(Array)
253 | return self.process_fields(*(self.extjs_column_names - options[:exclude].map(&:to_sym)))
254 | elsif options.has_key?(:only) && options[:only].is_a?(Array)
255 | return self.process_fields(*options[:only])
256 | end
257 | end
258 |
259 | params = self.extjs_column_names if params.empty?
260 |
261 | associations = extjs_associations
262 |
263 | params.each do |f|
264 | if f.kind_of?(Hash)
265 | if f.keys.size == 1 && f.keys[0].is_a?(Symbol) && f.values[0].is_a?(Array) # {:association => [:field1, :field2]}
266 | fields << {
267 | :name => f.keys[0],
268 | :fields => process_fields(*f.values[0])
269 | }
270 | elsif f.keys.size == 1 && f.keys[0].is_a?(Symbol) && f.values[0].is_a?(Hash) # {:field => {:sortDir => 'ASC'}}
271 | fields << f.values[0].update(:name => f.keys[0])
272 | elsif f.has_key?(:name) # already a valid Hash, just copy it over
273 | fields << f
274 | else
275 | raise ArgumentError, "encountered a Hash that I don't know anything to do with `#{f.inspect}:#{f.class}`"
276 | end
277 | else # should be a String or Symbol
278 | raise ArgumentError, "encountered a fields Array that I don't understand: #{params.inspect} -- `#{f.inspect}:#{f.class}` is not a Symbol or String" unless f.is_a?(Symbol) || f.is_a?(String)
279 | fields << {:name => f.to_sym}
280 | end
281 | end
282 |
283 | fields
284 | end
285 |
286 | ##
287 | # Render a column-config object
288 | # @param {Hash/Column} field Field-configuration Hash, probably has :name already set and possibly Ext.data.Field options.
289 | # @param {ORM Column Object from AR, DM or MM}
290 | #
291 | def extjs_field(field, config=nil)
292 | if config.kind_of? Hash
293 | if config.has_key?(:mapping) && config.has_key?(:parent_trail)
294 | field.update( # <-- We use a template for rendering mapped field-names.
295 | :name => config[:parent_trail].to_s + self.extjs_parent_trail_template.call(field[:name]),
296 | :mapping => "#{config[:mapping]}.#{field[:name]}"
297 | )
298 | end
299 | field.update(config.except(:mapping, :parent_trail))
300 | elsif !config.nil? # <-- Hopfully an ORM Column object.
301 | field.update(
302 | :allowBlank => self.extjs_allow_blank(config),
303 | :type => self.extjs_type(config),
304 | :defaultValue => self.extjs_default(config)
305 | )
306 | field[:dateFormat] = "c" if field[:type] === "date" && field[:dateFormat].nil? # <-- ugly hack for date
307 | end
308 | field.update(:type => "auto") if field[:type].nil?
309 | # convert Symbol values to String values
310 | field.keys.each do |k|
311 | raise ArgumentError, "extjs_field expects a Hash as first parameter with all it's keys Symbols. Found key #{k.inspect}:#{k.class.to_s}" unless k.is_a?(Symbol)
312 | field[k] = field[k].to_s if field[k].is_a?(Symbol)
313 | end
314 | field
315 | end
316 |
317 | # ##
318 | # # Returns an array of symbolized association names that will be referenced by a call to to_record
319 | # # i.e. [:parent1, :parent2]
320 | # #
321 | # def extjs_used_associations
322 | # if @extjs_used_associations.nil?
323 | # assoc = []
324 | # self.extjs_record_fields.each do |f|
325 | # #This needs to be the first condition because the others will break if f is an Array
326 | # if extjs_associations[f[:name]]
327 | # assoc << f[:name]
328 | # end
329 | # end
330 | # @extjs_used_associations = assoc.uniq
331 | # end
332 | # @extjs_used_associations
333 | # end
334 | end
335 |
336 | module Util
337 |
338 | ##
339 | # returns the fieldset from the arguments and normalizes the options.
340 | # @return [{Symbol}, {Hash}]
341 | def self.extract_fieldset_and_options arguments
342 | orig_args = arguments
343 | fieldset = :default
344 | options = { # default options
345 | :visited_classes => [],
346 | :fields => []
347 | }
348 | if arguments.size > 2 || (arguments.size == 2 && !arguments[0].is_a?(Symbol))
349 | raise ArgumentError, "Don't know how to handle #{arguments.inspect}"
350 | elsif arguments.size == 2 && arguments[0].is_a?(Symbol)
351 | fieldset = arguments.shift
352 | if arguments[0].is_a?(Array)
353 | options.update({
354 | :fields => arguments[0]
355 | })
356 | elsif arguments[0].is_a?(Hash)
357 | options.update(arguments[0])
358 | end
359 | elsif arguments.size == 1 && (arguments[0].is_a?(Symbol) || arguments[0].is_a?(String))
360 | fieldset = arguments.shift.to_sym
361 | elsif arguments.size == 1 && arguments[0].is_a?(Hash)
362 | fieldset = arguments[0].delete(:fieldset) || :default
363 | options.update(arguments[0])
364 | end
365 | [fieldset, options]
366 | end
367 | end
368 | end
369 | end
370 |
371 |
--------------------------------------------------------------------------------
/lib/model/data_mapper.rb:
--------------------------------------------------------------------------------
1 | ##
2 | # DataMapper adapter for ExtJS::Model mixin
3 | #
4 |
5 | module ExtJS
6 | module Model
7 | module ClassMethods
8 |
9 | def extjs_primary_key
10 | self.key.first.name
11 | end
12 |
13 | def extjs_column_names
14 | self.properties.collect {|p| p.name.to_s }
15 | end
16 |
17 | def extjs_columns_hash
18 | if @extjs_columns_hash.nil?
19 | @extjs_columns_hash = {}
20 | self.properties.each do |p|
21 | @extjs_columns_hash[p.name] = p
22 | end
23 | end
24 | @extjs_columns_hash
25 | end
26 |
27 | def extjs_allow_blank(col)
28 | (col === self.key.first) ? true : col.nullable?
29 | end
30 |
31 | def extjs_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 extjs_associations
48 | if @extjs_associations.nil?
49 | @extjs_associations = {}
50 | self.relationships.keys.each do |key|
51 | assn = self.relationships[key]
52 | @extjs_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 | @extjs_associations
62 | end
63 | end
64 | end
65 | end
66 |
67 |
--------------------------------------------------------------------------------
/lib/model/mongo_mapper.rb:
--------------------------------------------------------------------------------
1 | ##
2 | # MongoMapper adapter to ExtJS::Model mixin
3 | #
4 |
5 | module ExtJS
6 | module Model
7 | ##
8 | # ClassMethods
9 | #
10 | module ClassMethods
11 |
12 | def extjs_primary_key
13 | :id
14 | end
15 |
16 | def extjs_column_names
17 | self.column_names
18 | end
19 |
20 | def extjs_columns_hash
21 | self.keys
22 | end
23 |
24 | def extjs_associations
25 | @extjs_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 extjs_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 extjs_allow_blank(col)
54 | (col.name == '_id') || (col.options[:required] != true)
55 | end
56 |
57 | def extjs_default(col)
58 | col.default_value
59 | end
60 |
61 | end
62 | end
63 | end
64 |
65 |
--------------------------------------------------------------------------------
/lib/test/macros.rb:
--------------------------------------------------------------------------------
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
--------------------------------------------------------------------------------
/rails/init.rb:
--------------------------------------------------------------------------------
1 | require 'extjs-mvc'
2 |
--------------------------------------------------------------------------------
/shoulda_macros/macros.rb:
--------------------------------------------------------------------------------
1 | class Test::Unit::TestCase
2 | ##
3 | # Asserts that the passed list of fields are specified in the extjs_fields call
4 | # in the model class.
5 | # @fields {Symbols} fields A list of fields
6 | #
7 | def self.should_have_extjs_fields *fields
8 | klass = described_type
9 | should "have the correct extjs_fields" do
10 | fields.each do |field|
11 | found_record = klass.extjs_record_fields.find do|record_field|
12 | record_field[:name] == field.to_s
13 | end
14 | assert_not_nil found_record, "extjs field #{field} isn't listed in the #{klass.name} model"
15 | end
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/test/active_record_test.rb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/extjs/mvc/b1972ca38457380e2acc1bfba113c74318881a69/test/active_record_test.rb
--------------------------------------------------------------------------------
/test/app/config/application.rb:
--------------------------------------------------------------------------------
1 | # TODO: Figure out how to iterate each ORM framework AR, DM, MM and test each.
2 | require 'active_record'
3 | require 'active_support'
4 | require 'extjs-mvc'
5 | require 'extlib/inflection'
6 |
7 | gem 'sqlite3-ruby'
8 |
9 | class Test::App
10 |
11 | attr_reader :models
12 |
13 | def initialize(orm = :active_record)
14 | @orm = orm
15 | @config = YAML::load(IO.read("#{ROOT}/config/database.yml"))
16 |
17 | # Load ORM
18 | send("boot_#{orm.to_s}")
19 |
20 | load_models
21 |
22 | require 'db/schema'
23 |
24 | end
25 |
26 | ##
27 | # Reset a model's @extjs_fieldsets
28 | #
29 | def clean_all
30 | @models.map { |klass| clean klass }
31 | end
32 |
33 |
34 | private
35 |
36 | def boot_active_record
37 | ActiveRecord::Base.establish_connection(@config['test'])
38 | end
39 |
40 | def boot_mongo_mapper
41 |
42 | end
43 |
44 | def boot_data_mapper
45 |
46 | end
47 |
48 | ##
49 | # Do a dir on /models and constantize each filename
50 | #
51 | def load_models
52 | @models = []
53 | # Load Models and Schema for corresponding orm
54 | re = /^.*\/(.*).rb$/
55 | Dir["#{ROOT}/models/#{@orm.to_s}/*"].each { |c|
56 | require c
57 | match = c.match(re)
58 | @models << Extlib::Inflection.constantize(Extlib::Inflection.camelize(match[1])) if match
59 | }
60 | end
61 |
62 | def clean klass
63 | klass.instance_variables.each do |var_name|
64 | if /\A@extjs_fieldsets__/ =~ var_name.to_s
65 | klass.instance_variable_set( var_name.to_sym, nil )
66 | end
67 | end
68 | end
69 |
70 | end
--------------------------------------------------------------------------------
/test/app/config/database.yml:
--------------------------------------------------------------------------------
1 | test:
2 | adapter: sqlite3
3 | database: ":memory:"
--------------------------------------------------------------------------------
/test/app/db/schema.rb:
--------------------------------------------------------------------------------
1 |
2 | ##
3 | # build simple database
4 | #
5 | # people
6 | #
7 | ActiveRecord::Base.connection.create_table :users, :force => true do |table|
8 | table.column :id, :serial
9 | table.column :person_id, :integer
10 | table.column :password, :string
11 | table.column :created_at, :date
12 | table.column :disabled, :boolean, :default => true
13 | end
14 | ##
15 | # people
16 | #
17 | ActiveRecord::Base.connection.create_table :people, :force => true do |table|
18 | table.column :id, :serial
19 | table.column :first, :string, :null => false
20 | table.column :last, :string, :null => false
21 | table.column :email, :string, :null => false
22 | end
23 | ##
24 | # user_groups, join table
25 | #
26 | ActiveRecord::Base.connection.create_table :user_groups, :force => true do |table|
27 | table.column :user_id, :integer
28 | table.column :group_id, :integer
29 | end
30 |
31 | ##
32 | # groups
33 | #
34 | ActiveRecord::Base.connection.create_table :groups, :force => true do |table|
35 | table.column :id, :serial
36 | table.column :title, :string
37 | end
38 |
39 | ##
40 | # locations
41 | #
42 | ActiveRecord::Base.connection.create_table :locations, :force => true do |table|
43 | table.column :id, :serial
44 | table.column :name, :string
45 | table.column :street, :string
46 | table.column :type, :string
47 | end
48 |
49 | ##
50 | # addresses
51 | #
52 | ActiveRecord::Base.connection.create_table :addresses, :force => true do |table|
53 | table.column :id, :serial
54 | table.column :addressable_type, :string
55 | table.column :addressable_id, :integer
56 | table.column :street, :string
57 | end
58 |
59 | ##
60 | # Mock a Model for testing data-types
61 | #
62 | ActiveRecord::Base.connection.create_table :data_types, :force => true do |table|
63 | table.column :id, :serial
64 | table.column :string_column, :string
65 | table.column :decimal_column, :decimal
66 | table.column :float_column, :float
67 | table.column :date_column, :date
68 | table.column :datetime_column, :datetime
69 | table.column :time_column, :time
70 | table.column :email, :string
71 | table.column :integer_column, :integer
72 | table.column :notnull_column, :string, :null => false
73 | table.column :default_column, :boolean, :default => true
74 | table.column :boolean_column, :boolean
75 | end
76 |
--------------------------------------------------------------------------------
/test/app/models/active_record/address.rb:
--------------------------------------------------------------------------------
1 | class Address < ActiveRecord::Base
2 | belongs_to :addressable, :polymorphic => true
3 | include ExtJS::Model
4 | end
--------------------------------------------------------------------------------
/test/app/models/active_record/data_type.rb:
--------------------------------------------------------------------------------
1 | class DataType < ActiveRecord::Base
2 | include ExtJS::Model
3 | end
4 |
--------------------------------------------------------------------------------
/test/app/models/active_record/group.rb:
--------------------------------------------------------------------------------
1 | class Group < ActiveRecord::Base
2 | has_many :users
3 | include ExtJS::Model
4 | end
--------------------------------------------------------------------------------
/test/app/models/active_record/house.rb:
--------------------------------------------------------------------------------
1 | require "#{File.dirname(__FILE__)}/location"
2 |
3 | class House < Location
4 | end
--------------------------------------------------------------------------------
/test/app/models/active_record/location.rb:
--------------------------------------------------------------------------------
1 | class Location < ActiveRecord::Base
2 | has_one :address, :as => :addressable
3 | include ExtJS::Model
4 | end
5 |
6 |
--------------------------------------------------------------------------------
/test/app/models/active_record/person.rb:
--------------------------------------------------------------------------------
1 | class Person < ActiveRecord::Base
2 | has_one :user
3 | include ExtJS::Model
4 | end
--------------------------------------------------------------------------------
/test/app/models/active_record/user.rb:
--------------------------------------------------------------------------------
1 | class User < ActiveRecord::Base
2 | include ExtJS::Model
3 | belongs_to :person
4 |
5 | has_and_belongs_to_many :groups, :join_table => :user_groups
6 | end
--------------------------------------------------------------------------------
/test/app/models/active_record/user_group.rb:
--------------------------------------------------------------------------------
1 | class UserGroup < ActiveRecord::Base
2 | belongs_to :user
3 | belongs_to :group
4 | end
--------------------------------------------------------------------------------
/test/component_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class ComponentTest < Test::Unit::TestCase
4 | context "An ExtJS::Component Instance" do
5 |
6 | setup do
7 | @cmp = ExtJS::Component.new("title" => "A Component", "xtype" => "panel")
8 | end
9 |
10 | should "Render" do
11 | assert @cmp.render.match(/Ext.ComponentMgr.create/)
12 | end
13 | end
14 | end
15 |
16 |
--------------------------------------------------------------------------------
/test/controller_test.rb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/extjs/mvc/b1972ca38457380e2acc1bfba113c74318881a69/test/controller_test.rb
--------------------------------------------------------------------------------
/test/data_mapper_test.rb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/extjs/mvc/b1972ca38457380e2acc1bfba113c74318881a69/test/data_mapper_test.rb
--------------------------------------------------------------------------------
/test/model_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | ##
4 | # create a couple of related instances.
5 | #
6 | p = Person.create(:first => "Chris", :last => "Scott", :email => "chris@scott.com")
7 | u = User.create(:password => "1234", :person => p)
8 |
9 | class BogusModel
10 | include ExtJS::Model
11 | def additional_attribute
12 | 'computed value'
13 | end
14 | class << self
15 | def extjs_allow_blank(col)
16 | true
17 | end
18 |
19 | def extjs_default(col)
20 | nil
21 | end
22 |
23 | def extjs_type(col)
24 | nil
25 | end
26 |
27 | def extjs_column_names
28 | [:one, :two, :three_id]
29 | end
30 |
31 | def extjs_columns_hash
32 | {
33 | :one => {},
34 | :two => {},
35 | :three_id => {}
36 | }
37 | end
38 |
39 | def extjs_polymorphic_type(id_column_name)
40 | id_column_name.to_s.gsub(/_id\Z/, '_type').to_sym
41 | end
42 |
43 | def extjs_primary_key
44 | :id
45 | end
46 |
47 | def extjs_associations
48 | {
49 | :three => {
50 | :name => :tree,
51 | :type => :belongs_to,
52 | :class => nil,
53 | :foreign_key => :three_id,
54 | :is_polymorphic => false
55 | }
56 | }
57 | end
58 | end
59 | end
60 |
61 |
62 | class BogusModelChild < BogusModel
63 | end
64 |
65 | class ModelTest < Test::Unit::TestCase
66 | context "Rendering DataReader configuration for Person and User" do
67 |
68 | setup do
69 | App.clean_all
70 | end
71 |
72 | should "Person and User should render a valid Reader config" do
73 | reader = Person.extjs_record
74 | assert reader.kind_of?(Hash) && reader.has_key?(:fields) && reader.has_key?(:idProperty)
75 | end
76 | should "Person instance should render with to_record, a Hash containing at least a primary_key" do
77 | rec = Person.first.to_record
78 | assert_kind_of(Hash, rec)
79 | assert_array_has_item(rec.keys, 'has primary key') { |i| i.to_s == Person.extjs_primary_key.to_s }
80 | end
81 | should "User should render a Reader config" do
82 | reader = User.extjs_record
83 | assert reader.kind_of?(Hash) && reader.has_key?(:fields) && reader.has_key?(:idProperty)
84 | end
85 | should "User instance should render with to_record, a Hash containing at least a primary_key" do
86 | rec = User.first.to_record
87 | assert rec.kind_of?(Hash) && rec.keys.include?(User.extjs_primary_key)
88 | end
89 | should "User instance should render to_record containing foreign_key of Person" do
90 | rec = User.first.to_record
91 | assn = User.extjs_associations[:person]
92 | assert rec.keys.include?(assn[:foreign_key])
93 | end
94 |
95 | end
96 |
97 | context "A User with HABTM relationship with Group" do
98 | setup do
99 | App.clean_all
100 | UserGroup.destroy_all
101 |
102 | @user = User.first
103 | UserGroup.create(:user => @user, :group => Group.create(:title => "Merb"))
104 | UserGroup.create(:user => @user, :group => Group.create(:title => "Rails"))
105 | end
106 |
107 | should "Render to_record should return 2 groups" do
108 | User.extjs_fields(:groups)
109 | assert @user.to_record[:groups].length == 2
110 | end
111 | end
112 |
113 | context "A User with Person relationship: User.extjs_fields(:password, :person => [:first, {:last => {'sortDir' => 'ASC'}}])" do
114 | setup do
115 | App.clean_all
116 | User.extjs_fields(:password, {:person => [:first, {:last => {:sortDir => "ASC"}}]})
117 | @fields = User.extjs_record[:fields]
118 | end
119 |
120 | should "User should render a Reader with 4 total fields" do
121 | assert @fields.count === 4
122 | end
123 | should "Reader fields should contain 'password' field" do
124 | assert_array_has_item(@fields, 'has password field') {|f| f[:name] === "password"}
125 | end
126 | should "Reader fields should contain person_id" do
127 | assns = User.extjs_associations
128 | assn = assns[:person]
129 | assert_array_has_item(@fields, 'has foreign key person_id') {|f| f[:name] === assns[:person][:foreign_key].to_s }
130 | end
131 | should "Reader fields should contain mapped field 'person.first'" do
132 | assert_array_has_item(@fields, 'has person_first') {|f| f[:name] === "person_first" and f[:mapping] === "person.first"}
133 | end
134 | should "Reader fields should contain mapped field 'person.last'" do
135 | assert_array_has_item(@fields, 'has person_last') {|f| f[:name] === "person_last" and f[:mapping] === "person.last"}
136 | end
137 | should "person.last should have additional configuration 'sortDir' => 'ASC'" do
138 | assert_array_has_item(@fields, 'has person.last with sortDir') {|f| f[:name] === "person_last" and f[:sortDir] === 'ASC' }
139 | end
140 |
141 | should "produce a valid to_record record" do
142 | person = Person.create!(:first => 'first', :last => 'last', :email => 'email')
143 | user = User.create!(:person_id => person.id, :password => 'password')
144 | record = user.to_record
145 | assert_equal(user.id, record[:id])
146 | assert_equal(person.id, record[:person_id])
147 | assert_equal('password', record[:password])
148 | assert_equal('last', record[:person][:last])
149 | assert_equal('first', record[:person][:first])
150 | end
151 | end
152 |
153 | context "User with standard Person association" do
154 | setup do
155 | App.clean_all
156 | User.extjs_fields(:id, :password, :person)
157 | end
158 | should "produce a valid store config" do
159 | fields = User.extjs_record[:fields]
160 | assert_array_has_item(fields, 'has id') {|f| f[:name] === "id" }
161 | assert_array_has_item(fields, 'has person_id') {|f| f[:name] === "person_id" }
162 | assert_array_has_item(fields, 'has password') {|f| f[:name] === "password" }
163 | assert_array_has_item(fields, 'has person_last') {|f| f[:name] === "person_last" and f[:mapping] == "person.last" }
164 | assert_array_has_item(fields, 'has person_first') {|f| f[:name] === "person_first" and f[:mapping] == "person.first" }
165 | end
166 | should "produce a valid to_record record" do
167 | person = Person.create!(:first => 'first', :last => 'last', :email => 'email')
168 | user = User.create!(:person_id => person.id, :password => 'password')
169 | record = user.to_record
170 | assert_equal(user.id, record[:id])
171 | assert_equal(person.id, record[:person_id])
172 | assert_equal('password', record[:password])
173 | assert_equal('last', record[:person][:last])
174 | assert_equal('first', record[:person][:first])
175 | end
176 | end
177 |
178 | context "Person with User association (has_one relationship)" do
179 | setup do
180 | App.clean_all
181 | User.extjs_fields(:id, :password)
182 | Person.extjs_fields(:id, :user)
183 | end
184 | should "produce a valid store config" do
185 | fields = Person.extjs_record[:fields]
186 | assert_array_has_item(fields, 'has id') {|f| f[:name] === "id" }
187 | assert_array_has_item(fields, 'has user_id') {|f| f[:name] === "user_id" and f[:mapping] == 'user.id' }
188 | assert_array_has_item(fields, 'has user_password') {|f| f[:name] === "user_password"and f[:mapping] == 'user.password' }
189 | end
190 | should "produce a valid to_record record" do
191 | person = Person.create!(:first => 'first', :last => 'last', :email => 'email')
192 | user = User.create!(:person_id => person.id, :password => 'password')
193 | record = person.reload.to_record
194 | assert_equal(person.id, record[:id])
195 | assert_equal(user.id, record[:user][:id])
196 | assert_equal('password', record[:user][:password])
197 | end
198 | end
199 |
200 | context "Person with User association (has_one/belongs_to relationship) cyclic reference" do
201 | setup do
202 | App.clean_all
203 | User.extjs_fields(:id, :person)
204 | Person.extjs_fields(:id, :user)
205 | end
206 | should "produce a valid store config for Person" do
207 | fields = Person.extjs_record[:fields]
208 | assert_array_has_item(fields, 'has id') {|f| f[:name] === "id" }
209 | assert_array_has_item(fields, 'has user_id') {|f| f[:name] === "user_id" and f[:mapping] == 'user.id' }
210 | end
211 | should "produce a valid to_record record for Person" do
212 | person = Person.create!(:first => 'first', :last => 'last', :email => 'email')
213 | user = User.create!(:person_id => person.id, :password => 'password')
214 | record = person.reload.to_record
215 | assert_equal(person.id, record[:id])
216 | assert_equal(user.id, record[:user][:id])
217 | end
218 | end
219 |
220 | context "Fields should render with correct, ExtJS-compatible data-types" do
221 | setup do
222 | App.clean_all
223 | @fields = DataType.extjs_record[:fields]
224 | end
225 |
226 | should "Understand 'string'" do
227 | assert_array_has_item(@fields, 'has string_column with string') {|f| f[:name] == 'string_column' and f[:type] == 'string'}
228 | end
229 | should "Understand 'integer' as 'int'" do
230 | assert_array_has_item(@fields, 'has integer_column with int') {|f| f[:name] == 'integer_column' and f[:type] == 'int'}
231 | end
232 | should "Understand 'float'" do
233 | assert_array_has_item(@fields, 'has float_column with float') {|f| f[:name] == 'float_column' and f[:type] == 'float'}
234 | end
235 | should "Understand 'decimal' as 'float'" do # Is this correct??
236 | assert_array_has_item(@fields, 'has decimal_column with float') {|f| f[:name] == 'decimal_column' and f[:type] == 'float'}
237 | end
238 | should "Understand 'date'" do
239 | assert_array_has_item(@fields, 'has date_column with date') {|f| f[:name] == 'date_column' and f[:type] == 'date'}
240 | end
241 | should "Understand 'datetime' as 'date'" do
242 | assert_array_has_item(@fields, 'has datetime_column with date') {|f| f[:name] == 'datetime_column' and f[:type] == 'date'}
243 | end
244 | should "Understand 'time' as 'date'" do
245 | assert_array_has_item(@fields, 'has time_column with date') {|f| f[:name] == 'time_column' and f[:type] == 'date'}
246 | end
247 | should "Understand 'boolean'" do
248 | assert_array_has_item(@fields, 'has boolean_column with boolean') {|f| f[:name] == 'boolean_column' and f[:type] == 'boolean'}
249 | end
250 | should "Understand NOT NULL" do
251 | assert_array_has_item(@fields, 'has notnull_column with allowBlank == false') {|f| f[:name] == 'notnull_column' and f[:allowBlank] === false}
252 | end
253 | should "Understand DEFAULT" do
254 | assert_array_has_item(@fields, 'has default_column with defaultValue == true') {|f| f[:name] == 'default_column' and f[:defaultValue] === true}
255 | end
256 | end
257 |
258 | context "polymorphic associations" do
259 | setup do
260 | App.clean_all
261 | end
262 |
263 | should "return nil as class for a polymorphic relation" do
264 | assert_equal(nil, Address.extjs_associations[:addressable][:class])
265 | end
266 |
267 | should "create a proper default store config" do
268 | Address.extjs_fields
269 | fields = Address.extjs_record[:fields]
270 | assert_array_has_item(fields, 'has addressable_id') {|f| f[:name] === 'addressable_id' && !f[:mapping] }
271 | assert_array_has_item(fields, 'addressable_type') {|f| f[:name] === 'addressable_type' && !f[:mapping] }
272 | end
273 |
274 | should "create the right store config when including members of the polymorphic association" do
275 | Address.extjs_fields :street, :addressable => [:name]
276 | fields = Address.extjs_record[:fields]
277 | assert_array_has_item(fields, "has addressable_name") {|f| f[:name] === 'addressable_name' && f[:mapping] === 'addressable.name'}
278 | assert_array_has_item(fields, "has addressable_id") {|f| f[:name] === 'addressable_id' && !f[:mapping] }
279 | assert_array_has_item(fields, "has addressable_type") {|f| f[:name] === 'addressable_type' && !f[:mapping] }
280 | end
281 |
282 | should "fill in the right values for to_record" do
283 | Address.extjs_fields :street, :addressable => [:name]
284 | location = Location.create!(:name => 'Home')
285 | address = location.create_address(:street => 'Main Street 1')
286 | record = address.to_record
287 | assert_equal({:name => "Home", :id => location.id}, record[:addressable])
288 | assert_equal("Location", record[:addressable_type])
289 | assert_equal(location.id, record[:addressable_id])
290 | assert_equal(address.id, record[:id])
291 | assert_equal("Main Street 1", record[:street])
292 | end
293 | end
294 |
295 | context "single table inheritance" do
296 | setup do
297 | App.clean_all
298 | end
299 |
300 | should "fieldsets should be accessible from descendants" do
301 | Location.extjs_fieldset :on_location, [:street]
302 | fields = House.extjs_record(:on_location)[:fields]
303 | assert_array_has_item(fields, 'has street') {|f| f[:name] === 'street' }
304 | assert_array_has_not_item(fields, 'has name') {|f| f[:name] === 'name' }
305 | end
306 | should "fieldsets should be overrideable from descendants" do
307 | Location.extjs_fieldset :override, [:street]
308 | House.extjs_fieldset :override, [:name]
309 | fields = House.extjs_record(:override)[:fields]
310 | assert_array_has_not_item(fields, 'has street') {|f| f[:name] === 'street' }
311 | assert_array_has_item(fields, 'has name') {|f| f[:name] === 'name' }
312 | end
313 | end
314 |
315 | context "ExtJS::Model::Util" do
316 | context "#extract_fieldset_and_options default" do
317 | setup do
318 | @fieldset, @options = ExtJS::Model::Util.extract_fieldset_and_options [:fields => [:one, :two, :three]]
319 | @fields = @options[:fields]
320 | end
321 | should "return :default when no fieldset provided" do
322 | assert_equal(:'default', @fieldset)
323 | end
324 | should "not alter the fields array" do
325 | assert_equal([:one, :two, :three], @fields)
326 | end
327 | end
328 |
329 | context "#extract_fieldset_and_options with explicit fieldset definition and array with fields" do
330 | setup do
331 | @fieldset, @options = ExtJS::Model::Util.extract_fieldset_and_options [:explicit, [:one, :two, :three]]
332 | @fields = @options[:fields]
333 | end
334 | should "return :default when no fieldset provided" do
335 | assert_equal(:'explicit', @fieldset)
336 | end
337 | should "not alter the fields array" do
338 | assert_equal([:one, :two, :three], @fields)
339 | end
340 | end
341 |
342 | context "#extract_fieldset_and_options with explicit fieldset definition and hash with fields" do
343 | setup do
344 | @fieldset, @options = ExtJS::Model::Util.extract_fieldset_and_options [:explicit, {:fields => [:one, :two, :three]}]
345 | @fields = @options[:fields]
346 | end
347 | should "return :default when no fieldset provided" do
348 | assert_equal(:'explicit', @fieldset)
349 | end
350 | should "not alter the fields array" do
351 | assert_equal([:one, :two, :three], @fields)
352 | end
353 | end
354 |
355 | context "#extract_fieldset_and_options with only a hash" do
356 | setup do
357 | @fieldset, @options = ExtJS::Model::Util.extract_fieldset_and_options [{:fieldset => :explicit, :fields => [:one, :two, :three]}]
358 | @fields = @options[:fields]
359 | end
360 | should "return :default when no fieldset provided" do
361 | assert_equal(:'explicit', @fieldset)
362 | end
363 | should "not alter the fields array" do
364 | assert_equal([:one, :two, :three], @fields)
365 | end
366 | end
367 |
368 | context "#extract_fieldset_and_options edge cases" do
369 | should "called without arguments" do
370 | @fieldset, @options = ExtJS::Model::Util.extract_fieldset_and_options []
371 | @fields = @options[:fields]
372 | assert_equal(:'default', @fieldset)
373 | assert_equal([], @fields)
374 | end
375 | should "called with only the fieldset and no field arguments" do
376 | @fieldset, @options = ExtJS::Model::Util.extract_fieldset_and_options [:explicit]
377 | @fields = @options[:fields]
378 | assert_equal(:'explicit', @fieldset)
379 | assert_equal([], @fields)
380 | end
381 | should "raise error when called with more than 2 arguments" do
382 | assert_raise(ArgumentError) { ExtJS::Model::Util.extract_fieldset_and_options [:explicit, :some, {}] }
383 | end
384 | should "raise error when called with 2 arguments and the first one is no symbol" do
385 | assert_raise(ArgumentError) { ExtJS::Model::Util.extract_fieldset_and_options [{ :fields => [] }, :explicit] }
386 | end
387 | end
388 | end
389 |
390 | context "ExtJS::Model::ClassMethods" do
391 |
392 | context "#process_fields" do
393 | should "handle a simple Array of Symbols" do
394 | @fields = BogusModel.process_fields :one, :two, :three
395 | assert_equal([{:name => :one}, {:name => :two}, {:name => :three}], @fields)
396 | end
397 | should "handle a mixed Array where the last item is a Hash" do
398 | @fields = BogusModel.process_fields :one, :two, :three => [:three_one, :three_two]
399 | assert_equal([{:name => :one}, {:name => :two}, {:name => :three, :fields => [{:name => :three_one}, {:name => :three_two}]}], @fields)
400 | end
401 | should "handle a mixed Array where a middle item is a Hash" do
402 | @fields = BogusModel.process_fields :one, {:two => [:two_one, :two_two]}, :three
403 | assert_equal([
404 | {:name => :one},
405 | {:name => :two, :fields => [{:name => :two_one}, {:name => :two_two}]},
406 | {:name => :three}], @fields)
407 | end
408 | should "handle option :only" do
409 | @fields = BogusModel.process_fields :only => [:one, :two, :three]
410 | assert_equal([{:name => :one}, {:name => :two}, {:name => :three}], @fields)
411 | end
412 | should "handle option :exclude" do
413 | @fields = BogusModel.process_fields :exclude => [:two]
414 | assert_equal([{:name => :one}, {:name => :three_id}], @fields)
415 | end
416 | should "handle option :additional" do
417 | @fields = BogusModel.process_fields :additional => [:additional_attribute]
418 | assert_equal([{:name => :one}, {:name => :two}, {:name => :three_id}, {:name => :additional_attribute}], @fields)
419 |
420 | end
421 | should "handle {:field => {:sortDir => 'ASC'}}" do
422 | @fields = BogusModel.process_fields({:field => {:sortDir => 'ASC'}})
423 | assert_equal([{:name => :field, :sortDir => 'ASC'}], @fields)
424 | end
425 | should "handle recursive definition" do
426 | @fields = BogusModel.process_fields(:one, {:three => [{:one => [:one, :two]}, {:two => {:sortDir => "ASC"}}]})
427 | assert_equal([{:name => :one}, {:name => :three, :fields => [{:name => :one, :fields => [{:name => :one}, {:name => :two}]}, {:name => :two, :sortDir => 'ASC'}]}], @fields)
428 | end
429 | should "not touch already correct fields" do
430 | @fields = BogusModel.process_fields(:one, {:name => :field,:sortDir => 'ASC'})
431 | assert_equal([{:name => :one},{:name => :field, :sortDir => 'ASC'}], @fields)
432 | end
433 | should "raise ArgumentError when pass in bogus hash" do
434 | assert_raise(ArgumentError) { @fields = BogusModel.process_fields(:one, {:nme => :field,:sortDir => 'ASC'}) }
435 | end
436 | end
437 |
438 | context "#extjs_field" do
439 | should "type gets set to 'auto' when not present" do
440 | @field = BogusModel.extjs_field({:name => :test})
441 | assert_equal('auto', @field[:type])
442 | end
443 | should "not touch type when alredy present" do
444 | @field = BogusModel.extjs_field({:name => :test, :type => 'untouched'})
445 | assert_equal('untouched', @field[:type])
446 | end
447 | should "raise exception when bogus field config passed" do
448 | assert_raise(ArgumentError) { BogusModel.extjs_field({:name => :test, "type" => 'untouched'}) }
449 | end
450 |
451 | end
452 |
453 | context "#extjs_field with ORM config" do
454 | should "set allowBlank" do
455 | BogusModel.expects(:extjs_allow_blank).returns(false)
456 | @field = BogusModel.extjs_field({:name => :test}, stub())
457 | assert_equal(false, @field[:allowBlank])
458 | end
459 | should "set type" do
460 | BogusModel.expects(:extjs_type).returns('int')
461 | @field = BogusModel.extjs_field({:name => :test}, stub())
462 | assert_equal('int', @field[:type])
463 | end
464 | should "set defaultValue" do
465 | BogusModel.expects(:extjs_default).returns(true)
466 | @field = BogusModel.extjs_field({:name => :test}, stub())
467 | assert_equal(true, @field[:defaultValue])
468 | end
469 | should "set dateFormat to c it's a date" do
470 | BogusModel.expects(:extjs_type).returns('date')
471 | @field = BogusModel.extjs_field({:name => :test}, stub())
472 | assert_equal('c', @field[:dateFormat])
473 | end
474 | should "not touch dateFormat if it's already set" do
475 | BogusModel.expects(:extjs_type).returns('date')
476 | @field = BogusModel.extjs_field({:name => :test, :dateFormat => 'not-c'}, stub())
477 | assert_equal('not-c', @field[:dateFormat])
478 | end
479 | end
480 |
481 | context "#extjs_field with Hash config" do
482 | should "set correct name and mapping" do
483 | @field = BogusModel.extjs_field({:name => :son}, {:mapping => 'grandfather.father', :parent_trail => 'grandfather_father'})
484 | assert_equal('grandfather_father_son', @field[:name])
485 | assert_equal('grandfather.father.son', @field[:mapping])
486 | end
487 | should "apply config to field" do
488 | @field = BogusModel.extjs_field({:name => :son}, {:sortDir => 'ASC'})
489 | assert_equal('ASC', @field[:sortDir])
490 | end
491 | end
492 |
493 | context "#extjs_get_fields_for_fieldset" do
494 | should "return full list of columns for fieldset that was not defined, yet" do
495 | @fields = BogusModel.extjs_get_fields_for_fieldset :not_there
496 | assert_equal(BogusModel.process_fields(*BogusModel.extjs_column_names), @fields)
497 | end
498 | should "return the right fields for a fieldset that was defined before in the same class" do
499 | BogusModel.extjs_fieldset :fieldset_was_defined, [:one]
500 | @fields = BogusModel.extjs_get_fields_for_fieldset :fieldset_was_defined
501 | assert_equal(BogusModel.process_fields(:one), @fields)
502 | end
503 | should "return the fieldset of the ancestor when it was only defined in the ancestor" do
504 | BogusModel.extjs_fieldset :fieldset_was_defined_in_ancestor, [:one]
505 | @fields = BogusModelChild.extjs_get_fields_for_fieldset :fieldset_was_defined_in_ancestor
506 | assert_equal(BogusModel.process_fields(:one), @fields)
507 | end
508 | should "return the fieldset of the child when it was defined in the child and the ancestor" do
509 | BogusModel.extjs_fieldset :fieldset_was_defined_in_both, [:one]
510 | BogusModelChild.extjs_fieldset :fieldset_was_defined_in_both, [:two]
511 | @fields = BogusModelChild.extjs_get_fields_for_fieldset :fieldset_was_defined_in_both
512 | assert_equal(BogusModel.process_fields(:two), @fields)
513 | end
514 | end
515 | end
516 |
517 | protected
518 | def assert_array_has_item array, item_description, &blk
519 | assert array.find {|i| blk.call(i) }, "The array #{array.inspect} should #{item_description} but it does not"
520 | end
521 | def assert_array_has_not_item array, item_description, &blk
522 | assert !array.find {|i| blk.call(i) }, "The array #{array.inspect} should not #{item_description} but it does"
523 | end
524 |
525 | end
526 |
527 |
--------------------------------------------------------------------------------
/test/mongo_mapper_test.rb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/extjs/mvc/b1972ca38457380e2acc1bfba113c74318881a69/test/mongo_mapper_test.rb
--------------------------------------------------------------------------------
/test/store_test.rb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/extjs/mvc/b1972ca38457380e2acc1bfba113c74318881a69/test/store_test.rb
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'test/unit'
3 | require 'shoulda'
4 | require 'mocha'
5 |
6 | begin
7 | require 'ruby-debug'
8 | rescue LoadError
9 | puts "ruby-debug not loaded"
10 | end
11 |
12 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
13 | $LOAD_PATH.unshift(File.dirname(__FILE__))
14 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'app'))
15 |
16 | ROOT = File.join(File.dirname(__FILE__), 'app')
17 | require "config/application"
18 |
19 | ##
20 | # Boot test app.
21 | # TODO, send orm as param from console
22 | # eg: >rake test data_mapper
23 | # >rake test mongo_mapper
24 | #
25 | App = Test::App.new(:active_record)
26 |
27 | #FIXTURES_DIR = File.join(File.dirname(__FILE__), "fixtures")
28 |
29 |
30 | class Test::Unit::TestCase
31 | end
32 |
33 |
--------------------------------------------------------------------------------