rails_bridge 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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