Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

initial commit

  • Loading branch information...
commit 72ac93565fb8a05664b34bd9c501b3af2b7f924e 1 parent d28f7d0
@christocracy christocracy authored
View
113 README.rdoc
@@ -1,18 +1,103 @@
-= direct
+= rails-extjs-direct
+THIS GEM IS UNDER DEVELOPMENT AND NOT A FULL IMLPLEMENTATION OF THE Ext.Direct specification
-Description goes here.
+http://rubyforge.org/projects/rails-extjs/
+http://www.extjs.com
-== Note on Patches/Pull Requests
-
-* Fork the project.
-* Make your feature addition or bug fix.
-* Add tests for it. This is important so I don't break it in a
- future version unintentionally.
-* Commit, do not mess with rakefile, version, or history.
- (if you want to have your own version, that is fine but
- bump version in a commit by itself I can ignore when I pull)
-* Send me a pull request. Bonus points for topic branches.
+== DESCRIPTION:
-== Copyright
+A series of components for implementing the Ext.Direct API specification in Rails 2.3 and higher.
+Ext.Direct in Rails is implementede with Rack Middleware.
-Copyright (c) 2009 Chris Scott. See LICENSE for details.
+== FEATURES/PROBLEMS:
+
+== SYNOPSIS:
+
+* STEP1: Attach Rack MiddleWare to Rails config in environment.rb. The "/direct" bit is the url
+designated as Ext.Direct request.
+
+require 'rails-extjs-direct'
+Rails::Initializer.run do |config|
+ config.middleware.use Rails::ExtJS::Direct::RemotingProvider, "/direct"
+ .
+ .
+ .
+end
+
+* STEP2: Include Rails::ExtJS::Direct::Controller mixin into each controller you wish to be "Directable":
+
+class CompanyController < ApplicationController
+ include Rails::ExtJS::Direct::Controller
+
+ def destroy
+ # The Direct::Controller mixin will provide an instance variable @xrequest to each action.
+ # @xrequest is an instance of class XRequest provided by the rails-extjs-direct gem.
+ # XRequest contains accessor methods for each parameter in the Ajax request, including "tid", "type",
+ # "data", and "id"
+ #
+ # You must always render an instance of XResponse. Use the @request instance when instantiating
+ # your XResponse. This will ensure that Ext.Direct receives the correct "tid" (transaction id).
+ #
+ res = XResponse.new(@xrequest)
+ res.status = true
+ res.message = "Company#destroy"
+ res.result = {:foo => 'bar'}
+ render(:json => res)
+ end
+end
+
+* STEP 3 -- Define your Ext.Direct client-API
+
+<script>
+Ext.Direct.addProvider({
+ // Set url corresponding to: config.middleware.use Rails::ExtJS::Direct::RemotingProvider, "/direct"
+ url: '/direct',
+ type: 'remoting',
+ actions: {
+ Company : [
+ {name: 'load', len: 1},
+ {name: 'save', len: 2},
+ {name: 'create', len: 1},
+ {name: 'destroy', len: 1}
+ ]
+ }
+});
+
+// The following series of actions will generate just 1 Ajax request.
+Company.create({name: "Slate International"});
+Company.destroy(1, function(result, response) { console.info(arguments);});
+Company.destroy(2);
+Company.destroy(3);
+
+</script>
+
+== REQUIREMENTS:
+
+== INSTALL:
+
+sudo gem install rails-extjs-direct
+
+== LICENSE:
+
+(The MIT License)
+
+Copyright (c) 2009 Chris Scott
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
6 Rakefile
@@ -5,12 +5,14 @@ begin
require 'jeweler'
Jeweler::Tasks.new do |gem|
gem.name = "direct"
- gem.summary = %Q{TODO: one-line summary of your gem}
- gem.description = %Q{TODO: longer description of your gem}
+ gem.summary = %Q{Rails Ext.Direct driver}
+ gem.description = %Q{Rails Ext.Direct server-side router implementation}
gem.email = "christocracy@gmail.com"
gem.homepage = "http://github.com/extjs/direct"
gem.authors = ["Chris Scott"]
gem.add_development_dependency "thoughtbot-shoulda"
+ gem.files = FileList["[A-Z]*", "{bin,generators,lib,test}/**/*", 'lib/jeweler/templates/.gitignore']
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
end
rescue LoadError
View
57 direct.gemspec
@@ -0,0 +1,57 @@
+# Generated by jeweler
+# DO NOT EDIT THIS FILE
+# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
+# -*- encoding: utf-8 -*-
+
+Gem::Specification.new do |s|
+ s.name = %q{direct}
+ s.version = "0.0.0"
+
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
+ s.authors = ["Chris Scott"]
+ s.date = %q{2009-08-28}
+ s.description = %q{Rails Ext.Direct server-side router implementation}
+ s.email = %q{christocracy@gmail.com}
+ s.extra_rdoc_files = [
+ "LICENSE",
+ "README.rdoc"
+ ]
+ s.files = [
+ "LICENSE",
+ "README.rdoc",
+ "Rakefile",
+ "VERSION",
+ "lib/direct.rb",
+ "lib/helpers/direct_controller_helper.rb",
+ "lib/mixins/action_controller/direct_controller.rb",
+ "lib/rack/remoting_provider.rb",
+ "lib/xexception.rb",
+ "lib/xrequest.rb",
+ "lib/xresponse.rb",
+ "test/direct_test.rb",
+ "test/test_helper.rb"
+ ]
+ s.has_rdoc = true
+ s.homepage = %q{http://github.com/extjs/direct}
+ s.rdoc_options = ["--charset=UTF-8"]
+ s.require_paths = ["lib"]
+ s.rubygems_version = %q{1.3.1}
+ s.summary = %q{Rails Ext.Direct driver}
+ s.test_files = [
+ "test/test_helper.rb",
+ "test/direct_test.rb"
+ ]
+
+ if s.respond_to? :specification_version then
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
+ s.specification_version = 2
+
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
+ s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
+ else
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
+ end
+ else
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
+ end
+end
View
16 lib/direct.rb
@@ -0,0 +1,16 @@
+#$:.unshift(File.dirname(__FILE__)) unless
+# $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
+
+module Rails
+ module ExtJS
+ module Direct
+
+ end
+ end
+end
+require File.join(File.dirname(__FILE__), 'xresponse')
+require File.join(File.dirname(__FILE__), 'xrequest')
+require File.join(File.dirname(__FILE__), 'xexception')
+require File.join(File.dirname(__FILE__), 'rack', 'remoting_provider')
+require File.join(File.dirname(__FILE__), 'mixins', 'action_controller', 'direct_controller')
+require File.join(File.dirname(__FILE__), 'helpers', 'direct_controller_helper')
View
14 lib/helpers/direct_controller_helper.rb
@@ -0,0 +1,14 @@
+module Rails::ExtJS::Direct::Controller::Helper
+ def get_extjs_direct_provider(type, url=nil)
+ @providers = {} if @providers.nil?
+
+ if @providers[type].nil?
+ begin
+ @providers[type] = "Rails::ExtJS::Direct::#{type.capitalize}Provider".constantize.new(type, url)
+ rescue NameError
+ raise StandardError.new("Unknown Direct Provider '#{type}'")
+ end
+ end
+ @providers[type]
+ end
+end
View
69 lib/mixins/action_controller/direct_controller.rb
@@ -0,0 +1,69 @@
+module Rails::ExtJS::Direct::Controller
+
+ # standard ruby method called when some class does:
+ # include Merb::ExtJS::Direct::RemotingProvider
+ def self.included(base)
+ base.class_eval do
+ @@actions = []
+ before_filter :extjs_direct_prepare_request
+
+ # include the Helper @see helpers/direct_controller_helper.rb
+ helper Helper
+
+ def self.direct_actions(*actions)
+ unless actions.empty?
+ @@actions = actions.collect {|a| a.kind_of?(Hash) ? a : {:name => a, :len => 1}}
+ else
+ @@actions
+ end
+ end
+
+ # @deprecated
+ def self.get_direct_actions
+ @@actions
+ end
+ end
+ end
+
+ def extjs_direct_prepare_request
+ #TODO just populate params with the XRequest data.
+
+ @xrequest = XRequest.new(params)
+ @xresponse = XResponse.new(@xrequest)
+
+ token = params["authenticity_token"] || nil
+
+ params.each_key do |k|
+ params.delete(k)
+ end
+
+ params["authenticity_token"] = token if token
+
+ params[:id] = @xrequest.id
+
+ if @xrequest.id.kind_of?(Array)
+ params[:data] = @xrequest.params
+ elsif @xrequest.params.kind_of?(Hash)
+ params[:data] = {}
+ @xrequest.params.each do |k,v|
+ params[:data][k] = v
+ end
+ end
+ end
+
+ def respond(status, params)
+ @xresponse.status = status
+ @xresponse.message = params[:message] if params[:message]
+ @xresponse.result = params[:result] if params[:result]
+
+ render :json => @xresponse
+ end
+
+ def rescue_action(e)
+ if (e.kind_of?(XException))
+ render :json => XExceptionResponse.new(@xrequest, e)
+ else
+ render :json => XExceptionResponse.new(@xrequest, e)
+ end
+ end
+end
View
68 lib/rack/remoting_provider.rb
@@ -0,0 +1,68 @@
+begin
+ require 'json'
+rescue LoadError => load_error
+ raise "Error loading 'json' library. Please type 'sudo gem install json' and try again."
+end
+
+class Rails::ExtJS::Direct::RemotingProvider
+ def initialize(app, rpath)
+ if app.kind_of?(String)
+ @controllers = {}
+ @type = app
+ else
+ @app = app
+ end
+ @router_path = rpath
+ end
+
+ def add_controller(controller_name)
+ @controllers[controller_name.capitalize] = "#{controller_name.capitalize}Controller".constantize.get_direct_actions
+ end
+
+ def render
+ "<script>Ext.Direct.addProvider({type: '#{@type}', url: '#{@router_path}', actions: #{@controllers.to_json}});</script>"
+ end
+
+ def call(env)
+ if env["PATH_INFO"].match("^"+@router_path)
+ output = []
+ parse(env["action_controller.request.request_parameters"]).each do |req|
+ # have to dup the env for each request
+ request_env = env.dup
+
+ # pop poorly-named Ext.Direct routing-params off.
+ controller = req.delete("action").downcase
+ action = req.delete("method")
+
+ # set env URI and PATH_INFO for each request
+ request_env["PATH_INFO"] = "/#{controller}/#{action}"
+ request_env["REQUEST_URI"] = "/#{controller}/#{action}"
+
+ # set request params
+ request_env["action_controller.request.request_parameters"] = req
+
+ # call the app!
+ # TODO Implement begin/rescue to catch XExceptions
+ status, headers, response = @app.call(request_env)
+ output << response.body
+ end
+ # join all responses together
+ res = output.join(',')
+
+ # [wrap in array] if multiple responses. Each controller response will have returned json already
+ # so we don't want to re-encode.
+ res = "[" + res + "]" if output.length > 1
+
+ # return response
+ [200, {"Content-Type" => "text/html"}, res]
+ else
+ @app.call(env)
+ end
+ end
+
+ def parse(data)
+ return (data["_json"]) ? data["_json"] : [data]
+ end
+
+end
+
View
36 lib/xexception.rb
@@ -0,0 +1,36 @@
+##
+# XException
+# General exception class for all Ext application exceptions.
+# @author Chris Scott
+#
+class XException < StandardError
+end
+
+##
+# XExceptionResponse
+# A special extension of XResponse for returning responses where an Exception was raised. includes a back-trace in the response which
+# should probably be turned on with a debug switch only.
+# @author Chris Scott
+#
+class XExceptionResponse < XResponse
+ attr_accessor :where
+
+ ##
+ # initialize
+ # @param {XRequest}
+ # @param {StandardError}
+ #
+ def initialize(req, e)
+ super(req)
+ @type = 'exception'
+ @message = e.message
+ @where = e.backtrace
+ end
+
+ def to_h
+ data = super
+ data[:type] = 'exception'
+ data[:where] = @where.join("\n")
+ data
+ end
+end
View
31 lib/xrequest.rb
@@ -0,0 +1,31 @@
+###
+# XRequest
+# A standard response class suitable for Ext.Direct requests.
+# @author Chris Scott
+#
+class XRequest
+ attr_reader :id, :tid, :controller, :action, :type, :params
+
+ def initialize(params)
+ # TODO: simply setting @id, @params
+ @id = (params["id"].to_i > 0) ? params["id"].to_i : (params["data"].kind_of?(Array) && (params["data"].first.kind_of?(Integer) || params["data"].first.nil?)) ? params["data"].shift : nil
+ @tid = params["tid"]
+ @type = params["type"]
+ @params = (params["data"].kind_of?(Array) && params["data"].length == 1 && params["data"].first.kind_of?(Hash)) ? params["data"].first : params["data"] || []
+ @controller = params["xcontroller"]
+ @action = params["xaction"]
+ end
+
+ ##
+ # arg
+ # return a request argument at index. can be used to access either [] or {}
+ # @param {String/Integer} index
+ # @raises XException if params doesn't exist.
+ #
+ def arg(index)
+ if params[index].nil?
+ raise XException.new("Attempt to access unknown request argument '#{index.to_s}' on transaction #{@tid}")
+ end
+ @params[index]
+ end
+end
View
32 lib/xresponse.rb
@@ -0,0 +1,32 @@
+###
+# XResponse
+# A standard response class suitable for Ext.Direct AJAX responses.
+# @author Chris Scott
+#
+class XResponse
+ attr_accessor :type, :status, :errors, :success, :message, :result
+ attr_reader :tid
+
+ def initialize(req)
+ if req.kind_of?(XRequest)
+ @tid = req.tid
+ @type = req.type
+ else
+ req.kind_of?(Hash)
+ @tid = req["tid"]
+ @type = req["type"]
+ end
+ @status = false
+ @message = ''
+ @result = []
+ @errors = []
+ end
+
+ def to_h
+ {:tid => @tid, :status => @status, :type => @type, :message => @message, :result => @result, :errors => @errors}
+ end
+
+ def to_json
+ self.to_h.to_json
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.