rails_bridge 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/.rspec +1 -0
  2. data/Gemfile +21 -0
  3. data/Gemfile.lock +154 -0
  4. data/LICENSE +13 -0
  5. data/README.rdoc +244 -0
  6. data/Rakefile +46 -0
  7. data/VERSION +1 -0
  8. data/app/controllers/rails_bridge/layout_bridge_controller.rb +43 -0
  9. data/app/views/content.html.erb +13 -0
  10. data/app/views/rails_bridge/layout_bridge/index.html.erb +6 -0
  11. data/config/routes.rb +11 -0
  12. data/lib/generators/content_bridge/USAGE +12 -0
  13. data/lib/generators/content_bridge/content_bridge_generator.rb +9 -0
  14. data/lib/generators/content_bridge/templates/content_bridge.rb +47 -0
  15. data/lib/rails_bridge.rb +16 -0
  16. data/lib/rails_bridge/content_bridge.rb +137 -0
  17. data/lib/rails_bridge/content_request.rb +100 -0
  18. data/lib/rails_bridge/engine.rb +34 -0
  19. data/rails_bridge.gemspec +186 -0
  20. data/script/console +2 -0
  21. data/spec/dummy/.rspec +1 -0
  22. data/spec/dummy/Rakefile +7 -0
  23. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  24. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  25. data/spec/dummy/app/rails_bridge/content_bridges/twitter_content_bridge.rb +26 -0
  26. data/spec/dummy/app/rails_bridge/layout_bridge/layouts/application/content.html.erb +1 -0
  27. data/spec/dummy/app/rails_bridge/layout_bridge/views/layouts/_partial.html.erb +1 -0
  28. data/spec/dummy/app/views/layouts/_partial.html.erb +1 -0
  29. data/spec/dummy/app/views/layouts/alternative.html.erb +0 -0
  30. data/spec/dummy/app/views/layouts/application.html.erb +17 -0
  31. data/spec/dummy/autotest/discover.rb +2 -0
  32. data/spec/dummy/config.ru +4 -0
  33. data/spec/dummy/config/application.rb +44 -0
  34. data/spec/dummy/config/boot.rb +10 -0
  35. data/spec/dummy/config/database.yml +22 -0
  36. data/spec/dummy/config/environment.rb +5 -0
  37. data/spec/dummy/config/environments/development.rb +26 -0
  38. data/spec/dummy/config/environments/production.rb +49 -0
  39. data/spec/dummy/config/environments/test.rb +35 -0
  40. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  41. data/spec/dummy/config/initializers/inflections.rb +10 -0
  42. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  43. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  44. data/spec/dummy/config/initializers/session_store.rb +8 -0
  45. data/spec/dummy/config/locales/en.yml +5 -0
  46. data/spec/dummy/config/routes.rb +2 -0
  47. data/spec/dummy/db/development.sqlite3 +0 -0
  48. data/spec/dummy/db/test.sqlite3 +0 -0
  49. data/spec/dummy/public/404.html +26 -0
  50. data/spec/dummy/public/422.html +26 -0
  51. data/spec/dummy/public/500.html +26 -0
  52. data/spec/dummy/public/favicon.ico +0 -0
  53. data/spec/dummy/public/javascripts/application.js +2 -0
  54. data/spec/dummy/public/javascripts/controls.js +965 -0
  55. data/spec/dummy/public/javascripts/dragdrop.js +974 -0
  56. data/spec/dummy/public/javascripts/effects.js +1123 -0
  57. data/spec/dummy/public/javascripts/prototype.js +6001 -0
  58. data/spec/dummy/public/javascripts/rails.js +175 -0
  59. data/spec/dummy/public/stylesheets/.gitkeep +0 -0
  60. data/spec/dummy/script/rails +6 -0
  61. data/spec/dummy/spec/spec_helper.rb +33 -0
  62. data/spec/integration/content_bridge_spec.rb +82 -0
  63. data/spec/integration/engine_spec.rb +25 -0
  64. data/spec/requests/layout_bridge_controller_spec.rb +16 -0
  65. data/spec/spec_helper.rb +62 -0
  66. data/spec/support/content_bridge_helper.rb +26 -0
  67. data/spec/support/layout_bridge_helper.rb +2 -0
  68. data/spec/support/rails_bridge_helper.rb +12 -0
  69. data/spec/support/test_server_helper.rb +125 -0
  70. data/spec/unit/content_bridge_spec.rb +59 -0
  71. data/spec/unit/content_request_spec.rb +84 -0
  72. data/spec/unit/rails_bridge_spec.rb +11 -0
  73. metadata +380 -0
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.5
@@ -0,0 +1,43 @@
1
+ module RailsBridge
2
+ class LayoutBridgeController < ::ApplicationController
3
+ def index
4
+ paths = self.view_paths
5
+ @layouts = {}
6
+ paths.each do |path|
7
+ layouts_path = File.join(path, 'layouts', '*.{erb,haml}')
8
+ files = Dir.glob( File.join(path, 'layouts', '*.{erb,haml}') )
9
+ files.each do |file|
10
+ name = File.basename(file).split('.').first
11
+ next if name =~ /^_.*/ # ignore partials
12
+ @layouts[name] ||= file # only the first matching layout in the views path is accessible
13
+ end
14
+ end
15
+
16
+ end
17
+
18
+ def show
19
+ @layout_name = params[:id]
20
+ custom_content_path = File.join( Rails.root, RailsBridge::LAYOUTS_PATH, @layout_name )
21
+ bridge_views_path = File.join( Rails.root, RailsBridge::VIEWS_PATH )
22
+ self.prepend_view_path(custom_content_path)
23
+ self.prepend_view_path(bridge_views_path)
24
+ string = replace_relative_urls( render_to_string :layout=>@layout_name, :template=>'content' )
25
+ render :text=>string, :content_type=>'text/plain'
26
+ end
27
+
28
+ private
29
+
30
+ def replace_relative_urls html
31
+ substitutions = [
32
+ [/(<a.*?href\s*=\s*")\//, "$1#{site_url}/"], # replace anchor URLs
33
+ [/(<script.*?src\s*?=\s*?")\//, "$1#{site_url}/"], # replace script URLs
34
+ [/(<link.*?href\s*=\s*")\//, "$1#{site_url}/"] # replace stylesheet URLs
35
+ ]
36
+ html.mgpsub( substitutions )
37
+ end
38
+
39
+ def site_url
40
+ (defined?(RailsBridge::SITE_URL) && RailsBridge::SITE_URL) || "#{request.env['rack.url_scheme']}://#{request.host}:#{request.port}"
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,13 @@
1
+ <!--
2
+
3
+ To put your own templating content into this section, create the following view in your views path:
4
+
5
+ <default_views_path>/rails_bridge/layout_bridge/layouts/<%= @layou_name %>/content.html.erb
6
+
7
+ You may also override individual partials to be used for this templatized layout by creating them with the same relative path under
8
+
9
+ app/rails_bridge/layout_bridge/views
10
+
11
+ Any partials declared in this directory will have precedence over those in your default views path.
12
+
13
+ -->
@@ -0,0 +1,6 @@
1
+ <p>Active layouts</p>
2
+ <ul>
3
+ <% @layouts.each do |name, path| %>
4
+ <li><%= link_to(name, url_for(:action=>:show, :id=>name)) %> from <%= path %></li>
5
+ <% end -%>
6
+ </ul>
data/config/routes.rb ADDED
@@ -0,0 +1,11 @@
1
+ Rails.application.routes.draw do
2
+ namespace :rails_bridge do
3
+ resources :layouts, :controller => "layout_bridge" do
4
+ member do
5
+ get :test
6
+ end
7
+ end
8
+ root :to => 'layout_bridge#index'
9
+ end
10
+ end
11
+
@@ -0,0 +1,12 @@
1
+ Description:
2
+ Creates a RailsBridge::ContentBridge class.
3
+
4
+ Example:
5
+ rails generate content_bridge Twitter get_user_status
6
+
7
+ This will create:
8
+ app/rails_bridge/content_bridges
9
+ app/rails_bridge/content_bridges/twitter_bridge.rb
10
+
11
+ A content_request named 'get_user_status' will be defined in the
12
+ generated TwitterContentBridge class.
@@ -0,0 +1,9 @@
1
+ class ContentBridgeGenerator < Rails::Generators::NamedBase
2
+ argument :requests, :type => :array, :default => [], :banner => "request [request]..."
3
+ source_root File.expand_path('../templates', __FILE__)
4
+
5
+ def create_class_file
6
+ template 'content_bridge.rb', File.expand_path(File.join('app/rails_bridge', 'content_bridges', "#{file_name}.rb"))
7
+ end
8
+
9
+ end
@@ -0,0 +1,47 @@
1
+ class <%= class_name %> < RailsBridge::ContentBridge
2
+ # Uncomment the following options to create defaults to be used by all requests
3
+ # self.protocol = 'https' # defaults to 'http'
4
+ # self.host = 'example.com'
5
+ # self.port = 8080 # defaults to 80
6
+ # self.path = '/path/to/resource' # no default
7
+ # self.params = {:locale=>'en'} # no default
8
+ # self.default_content = "Content unavailable."
9
+ # self.on_success {|content| JSON.parse(content)}
10
+ # self.request_timeout = 1000 # miliseconds
11
+ # self.cache_timeout = 3600 # seconds
12
+
13
+ <%- unless requests.any? -%>
14
+ # # Example request declaration.
15
+ # # It would be invoked using:
16
+ # # <%= class_name %>.get_example
17
+ # content_request :<%= example %> do |request|
18
+ # # Uncomment the following options to override defaults set on the class.
19
+ # # request.protocol = 'https'
20
+ # # request.host = 'example.com'
21
+ # # request.port = 8080
22
+ # # request.path = '/path/to/resource'
23
+ # # request.params = nil
24
+ # # request.default_content = "<%= request.capitalize %> content unavailable."
25
+ # # request.on_success nil
26
+ # # request.request_timeout = 500 # miliseconds
27
+ # # request.cache_timeout = 60 # seconds
28
+ # end
29
+
30
+ <%- else -%>
31
+ <%- for request in requests -%>
32
+ content_request :<%= request %> do |request|
33
+ # Uncomment the following options to override defaults set on the class.
34
+ # request.protocol = 'https'
35
+ # request.host = 'example.com'
36
+ # request.port = 8080
37
+ # request.path = '/path/to/resource'
38
+ # request.params = nil
39
+ # request.default_content = "<%= request.capitalize %> content unavailable."
40
+ # request.on_success nil
41
+ # request.request_timeout = 500 # miliseconds
42
+ # request.cache_timeout = 60 # seconds
43
+ end
44
+
45
+ <%- end -%>
46
+ <%- end -%>
47
+ end
@@ -0,0 +1,16 @@
1
+ module RailsBridge
2
+ end
3
+
4
+ require 'active_support/core_ext/class'
5
+ require 'active_support/core_ext/hash'
6
+ require 'active_support/cache'
7
+ $libdir = File.join( File.dirname(__FILE__), 'rails_bridge' )
8
+ require File.join( $libdir, 'content_request' )
9
+ require File.join( $libdir, 'content_bridge' )
10
+
11
+ if defined? Rails
12
+ require File.join( $libdir, 'engine' )
13
+ else
14
+ RailsBridge::ContentBridge.cache = ActiveSupport::Cache.lookup_store(:memory_store)
15
+ end
16
+
@@ -0,0 +1,137 @@
1
+ require 'typhoeus'
2
+ require 'active_support/core_ext/logger'
3
+
4
+ module RailsBridge
5
+ # = ContentBridge
6
+ #
7
+ # An abstraction for embedding content from a remote application within a Rails request.
8
+ class ContentBridge
9
+
10
+ # Class Constants
11
+ DEFAULT_CONTENT = 'Remote Content Unavailable'
12
+ DEFAULT_REQUEST_TIMEOUT = 2000 # miliseconds
13
+ DEFAULT_CACHE_TIMEOUT = 0
14
+
15
+ # Class Attributes
16
+ class_inheritable_accessor :protocol, :host, :port, :path, :params, :request_timeout, :cache_timeout, :default_content
17
+ class_inheritable_accessor :cache, :logger
18
+ @@content_requests = {}
19
+ @@on_success = nil
20
+
21
+ # Initialize Default Class Attribute Values
22
+ self.request_timeout = DEFAULT_REQUEST_TIMEOUT
23
+ self.cache_timeout = DEFAULT_CACHE_TIMEOUT
24
+ self.default_content = DEFAULT_CONTENT
25
+ self.cache = nil
26
+ self.logger = Logger.new(File.open("/dev/null", 'w'))
27
+
28
+
29
+ class << self
30
+ # custom accessor methods
31
+ def content_requests; @@content_requests; end
32
+
33
+ def on_success
34
+ if block_given?
35
+ @@on_success = Proc.new
36
+ else
37
+ @@on_success
38
+ end
39
+ end
40
+
41
+ def on_success=(proc)
42
+ @@on_success = proc
43
+ end
44
+
45
+
46
+ def cache_set key, content, expires_in
47
+ logger.debug "set key: #{key}"
48
+ self.cache.write( key, content, :expires_in=>expires_in )
49
+ end
50
+
51
+ def cache_get key
52
+ logger.debug "get key: #{key}"
53
+ content = self.cache.fetch(key, :race_condition_ttl=>5.seconds)
54
+ end
55
+
56
+
57
+ def get_remote_content( remote, options={} )
58
+ hydra = Typhoeus::Hydra.hydra # the singleton Hydra
59
+ hydra.disable_memoization
60
+ if remote.is_a? Symbol
61
+ raise "Undefined content_request :#{remote}" unless remote = @@content_requests[remote]
62
+ end
63
+ if remote.is_a? Hash
64
+ remote = RailsBridge::ContentRequest.new remote
65
+ remote.content_bridge = self
66
+ end
67
+ if remote.is_a? RailsBridge::ContentRequest
68
+ content_request = remote
69
+ remote_url = content_request.url
70
+ options[:params] ||= content_request.params
71
+ options[:request_timeout] ||= content_request.request_timeout
72
+ options[:cache_timeout] ||= content_request.cache_timeout
73
+ options[:default_content] ||= content_request.default_content
74
+ on_success = content_request.on_success
75
+ else
76
+ remote_url = remote
77
+ on_sucess = nil
78
+ end
79
+ options[:request_timeout] ||= self.request_timeout
80
+ options[:cache_timeout] ||= self.cache_timeout
81
+ options[:timeout] = options.delete(:request_timeout) # Rename the request timeout param for Typhoeus
82
+ default_content = options.delete(:default_content) || self.default_content
83
+ # options[:verbose] = true # for debugging only
84
+
85
+ request = Typhoeus::Request.new(remote_url, options)
86
+ unless self.cache && request.cache_timeout && request.cache_timeout > 0 && result = cache_get( request.cache_key )
87
+ result = default_content
88
+ request.on_complete do |response|
89
+ case response.code
90
+ when 200
91
+ if on_success
92
+ result = on_success.call(response.body)
93
+ else
94
+ result = response.body
95
+ end
96
+ cache_set( request.cache_key, result, request.cache_timeout ) if self.cache && request.cache_timeout && request.cache_timeout > 0
97
+ logger.debug "ContentBridge : Request Succeeded - Content: #{result}"
98
+ when 0
99
+ logger.warn "ContentBridge : Request Timeout for #{remote_url}"
100
+ else
101
+ logger.warn "ContentBridge : Request for #{remote_url}\mRequest Failed with HTTP result code: #{response.code}\n#{response.body}"
102
+ end
103
+ end
104
+ hydra.queue request
105
+ hydra.run # this is a blocking call that returns once all requests are complete
106
+ end
107
+ result
108
+ end
109
+
110
+ def content_request name, options={}
111
+ raise "name must be a symbol" unless name.is_a? Symbol
112
+ begin
113
+ raise "WARNING: Already defined content_request '#{name}'" if @@content_requests.key?( name )
114
+ rescue
115
+ logger.warn $!.message
116
+ logger.warn $!.backtrace * "\n"
117
+ end
118
+ new_request = ContentRequest.new options
119
+ yield new_request if block_given?
120
+ @@content_requests[name] = new_request
121
+ new_request.content_bridge = self
122
+ new_request
123
+ end
124
+
125
+ def method_missing method, *args
126
+ if matches = method.to_s.match( /^get_(.*)$/ )
127
+ request_name = matches[1]
128
+ self.get_remote_content( request_name.to_sym, *args )
129
+ else
130
+ super
131
+ end
132
+ end
133
+
134
+ end
135
+
136
+ end
137
+ end
@@ -0,0 +1,100 @@
1
+ require 'uri'
2
+
3
+ module RailsBridge
4
+ class ContentRequest
5
+ attr_accessor :default_content, :request_timeout, :cache_timeout, :protocol, :host, :port, :path, :params, :content_bridge
6
+ attr_accessor :on_success
7
+ attr :url
8
+
9
+ # Options:
10
+ # * :default_content - Content to be returned in test mode or when the remote server is unavailable
11
+ # * :request_timeout - Maximum time to wait for successful response in ms
12
+ # * :cache_timeout - TTL expiry for cache in seconds - nil or 0 will prevent caching
13
+ # * :protocol - Protocol of remote server (http|https). Default is 'http'
14
+ # * :host - Host of remote server.
15
+ # * :port - Port of remote server. Default is 80.
16
+ # * :path - Path for remote request. Default is '/'
17
+ # * :params - URL query params for remote request.
18
+ # * :fragment - URL part after the '#' for remote request.
19
+ # * :url - Explicit URL for remote request.
20
+ # if the :url option is passed, the :protocol, :host, :port, :path, :params, and :fragment options are ignored
21
+ def initialize options={}
22
+ if url = options[:url]
23
+ self.url = url
24
+ else
25
+ self.protocol = options[:protocol]
26
+ self.host = options[:host]
27
+ self.port = options[:port]
28
+ self.path = options[:path]
29
+ self.params = options[:params]
30
+ end
31
+ self.default_content = options[:default_content]
32
+ self.request_timeout = options[:request_timeout]
33
+ self.cache_timeout = options[:cache_timeout]
34
+ end
35
+
36
+ def protocol; @protocol || (self.content_bridge && self.content_bridge.protocol) || 'http'; end
37
+ def host; @host || (self.content_bridge && self.content_bridge.host); end
38
+ def port; @port || (self.content_bridge && self.content_bridge.port) || 80; end
39
+ def path; @path || (self.content_bridge && self.content_bridge.path) || '/'; end
40
+ def params; @params || (self.content_bridge && self.content_bridge.params); end
41
+
42
+ def url= url
43
+ uri = URI.parse( url )
44
+ self.protocol = uri.scheme
45
+ self.host = uri.host
46
+ self.port = uri.port
47
+ self.path = uri.path
48
+ self.params = extract_query_params( uri.query )
49
+ end
50
+
51
+ # We don't include the params in the URL, because Typhoeus::Request takes them as a separate argument
52
+ def url
53
+ URI::Generic.new( self.protocol, nil, self.host, self.port, nil, self.path, nil, nil, nil, true ).to_s
54
+ end
55
+
56
+ def get_remote_content( options={} )
57
+ if self.content_bridge
58
+ self.content_bridge.get_remote_content( self, options )
59
+ else
60
+ RailsBridge::ContentBridge.get_remote_content( self, options )
61
+ end
62
+ end
63
+
64
+ def on_success
65
+ if block_given?
66
+ @on_success = Proc.new
67
+ else
68
+ @on_success || (self.content_bridge && self.content_bridge.on_success);
69
+ end
70
+ end
71
+
72
+ def on_success=(proc)
73
+ @on_success = proc
74
+ end
75
+
76
+ private
77
+ def extract_query_params query
78
+ return nil if query.nil?
79
+ query = CGI::unescape( query )
80
+ pairs = query.split("&")
81
+ params = pairs.inject({}){|hash, pair| k,v=pair.split('='); v.nil? ? hash : (hash[k.to_sym]=v;hash)}
82
+ params.keys.empty? ? nil : params
83
+ end
84
+
85
+ def encode_query_params params
86
+ params = escape_query_params( params )
87
+ params.keys.map{|k| "#{k}=#{params[k]}"}.join('&')
88
+ end
89
+
90
+ def escape_query_params params
91
+ return unless params
92
+ new_params = {}
93
+ params.each do |k,v|
94
+ new_params[k] = CGI::escape( v.to_s )
95
+ end
96
+ new_params
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,34 @@
1
+ module RailsBridge
2
+ ROOT_PATH = 'app/rails_bridge'
3
+ LAYOUTS_PATH = File.join(ROOT_PATH, 'layout_bridge', 'layouts')
4
+ VIEWS_PATH = File.join(ROOT_PATH, 'layout_bridge', 'views')
5
+
6
+ # namespace our plugin and inherit from Rails::Railtie
7
+ # to get our plugin into the initialization process
8
+ class Engine < Rails::Engine
9
+
10
+
11
+ # initialize our plugin on boot.
12
+ initializer "rails_bridge.initialize" do |app|
13
+ end
14
+
15
+ config.after_initialize do
16
+ RailsBridge::ContentBridge.logger = Rails.logger
17
+ RailsBridge::ContentBridge.cache = Rails.cache
18
+ RailsBridge::Engine.create_rails_bridge_home
19
+ end
20
+
21
+ config.autoload_paths << ROOT_PATH
22
+
23
+ def self.create_rails_bridge_home
24
+ rails_bridge_home = File.join( Rails.root, RailsBridge::ROOT_PATH )
25
+ FileUtils.mkdir_p( rails_bridge_home ) unless File.exist?( rails_bridge_home )
26
+ layout_bridge_views_path = File.join( Rails.root, RailsBridge::VIEWS_PATH )
27
+ layout_bridge_layouts_path = File.join( Rails.root, RailsBridge::LAYOUTS_PATH )
28
+ FileUtils.mkdir_p( layout_bridge_views_path ) unless File.exist?( layout_bridge_views_path )
29
+ FileUtils.mkdir_p( layout_bridge_layouts_path ) unless File.exist?( layout_bridge_layouts_path )
30
+ rails_bridge_home
31
+ end
32
+
33
+ end
34
+ end