copycopter_client 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.textile +71 -0
  3. data/Rakefile +38 -0
  4. data/features/rails.feature +267 -0
  5. data/features/step_definitions/copycopter_server_steps.rb +65 -0
  6. data/features/step_definitions/rails_steps.rb +134 -0
  7. data/features/support/env.rb +8 -0
  8. data/features/support/rails_server.rb +118 -0
  9. data/init.rb +2 -0
  10. data/lib/copycopter_client/client.rb +117 -0
  11. data/lib/copycopter_client/configuration.rb +197 -0
  12. data/lib/copycopter_client/errors.rb +13 -0
  13. data/lib/copycopter_client/helper.rb +40 -0
  14. data/lib/copycopter_client/i18n_backend.rb +100 -0
  15. data/lib/copycopter_client/prefixed_logger.rb +41 -0
  16. data/lib/copycopter_client/rails.rb +31 -0
  17. data/lib/copycopter_client/railtie.rb +13 -0
  18. data/lib/copycopter_client/sync.rb +145 -0
  19. data/lib/copycopter_client/version.rb +8 -0
  20. data/lib/copycopter_client.rb +58 -0
  21. data/lib/tasks/copycopter_client_tasks.rake +6 -0
  22. data/spec/copycopter_client/client_spec.rb +208 -0
  23. data/spec/copycopter_client/configuration_spec.rb +252 -0
  24. data/spec/copycopter_client/helper_spec.rb +86 -0
  25. data/spec/copycopter_client/i18n_backend_spec.rb +133 -0
  26. data/spec/copycopter_client/prefixed_logger_spec.rb +25 -0
  27. data/spec/copycopter_client/sync_spec.rb +295 -0
  28. data/spec/spec.opts +2 -0
  29. data/spec/spec_helper.rb +30 -0
  30. data/spec/support/client_spec_helpers.rb +9 -0
  31. data/spec/support/defines_constants.rb +38 -0
  32. data/spec/support/fake_client.rb +42 -0
  33. data/spec/support/fake_copycopter_app.rb +136 -0
  34. data/spec/support/fake_html_safe_string.rb +20 -0
  35. data/spec/support/fake_logger.rb +68 -0
  36. data/spec/support/fake_passenger.rb +27 -0
  37. data/spec/support/fake_unicorn.rb +14 -0
  38. metadata +121 -0
@@ -0,0 +1,118 @@
1
+ require 'net/http'
2
+
3
+ # Starts a Rails application server in a fork and waits for it to be responsive
4
+ class RailsServer
5
+ HOST = 'localhost'.freeze
6
+
7
+ class << self
8
+ attr_accessor :instance
9
+ end
10
+
11
+ def self.start(port = nil, debug = nil)
12
+ self.instance = new(port, debug)
13
+ self.instance.start
14
+ self.instance
15
+ end
16
+
17
+ def self.stop
18
+ self.instance.stop if instance
19
+ self.instance = nil
20
+ end
21
+
22
+ def self.get(path)
23
+ self.instance.get(path)
24
+ end
25
+
26
+ def self.post(path, data)
27
+ self.instance.post(path, data)
28
+ end
29
+
30
+ def self.run(port, silent)
31
+ require 'config/environment'
32
+ require 'thin'
33
+
34
+ if Rails::VERSION::MAJOR == 3
35
+ rails = Rails.application
36
+ else
37
+ rails = ActionController::Dispatcher.new
38
+ end
39
+ app = Identify.new(rails)
40
+
41
+ Thin::Logging.silent = silent
42
+ Rack::Handler::Thin.run(app, :Port => port, :AccessLog => [])
43
+ end
44
+
45
+ def self.app_host
46
+ self.instance.app_host
47
+ end
48
+
49
+ def initialize(port, debug)
50
+ @port = (port || 3001).to_i
51
+ @debug = debug
52
+ end
53
+
54
+ def start
55
+ @pid = fork do
56
+ command = "ruby -r#{__FILE__} -e 'RailsServer.run(#{@port}, #{(!@debug).inspect})'"
57
+ puts command if @debug
58
+ exec(command)
59
+ end
60
+ wait_until_responsive
61
+ end
62
+
63
+ def stop
64
+ if @pid
65
+ Process.kill('INT', @pid)
66
+ Process.wait(@pid)
67
+ @pid = nil
68
+ end
69
+ end
70
+
71
+ def get(path)
72
+ puts "GET #{path}" if @debug
73
+ Net::HTTP.start(HOST, @port) { |http| http.get(path) }
74
+ end
75
+
76
+ def post(path, data)
77
+ puts "POST #{path}\n#{data}" if @debug
78
+ Net::HTTP.start(HOST, @port) { |http| http.post(path, data) }
79
+ end
80
+
81
+ def wait_until_responsive
82
+ 20.times do
83
+ if responsive?
84
+ return true
85
+ else
86
+ sleep(0.5)
87
+ end
88
+ end
89
+ raise "Couldn't connect to Rails application server at #{HOST}:#{@port}"
90
+ end
91
+
92
+ def responsive?
93
+ response = Net::HTTP.start(HOST, @port) { |http| http.get('/__identify__') }
94
+ response.is_a?(Net::HTTPSuccess)
95
+ rescue Errno::ECONNREFUSED, Errno::EBADF
96
+ return false
97
+ end
98
+
99
+ def app_host
100
+ "http://#{HOST}:#{@port}"
101
+ end
102
+
103
+ # From Capybara::Server
104
+
105
+ class Identify
106
+ def initialize(app)
107
+ @app = app
108
+ end
109
+
110
+ def call(env)
111
+ if env["PATH_INFO"] == "/__identify__"
112
+ [200, {}, 'OK']
113
+ else
114
+ @app.call(env)
115
+ end
116
+ end
117
+ end
118
+ end
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'copycopter_client'
2
+
@@ -0,0 +1,117 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'copycopter_client/errors'
4
+
5
+ module CopycopterClient
6
+ # Communicates with the Copycopter server. This class is used to actually
7
+ # download and upload blurbs, as well as issuing deploys.
8
+ #
9
+ # A client is usually instantiated when {Configuration#apply} is called, and
10
+ # the application will not need to interact with it directly.
11
+ class Client
12
+ # These errors will be rescued when connecting Copycopter.
13
+ HTTP_ERRORS = [Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError,
14
+ Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError,
15
+ Net::ProtocolError]
16
+
17
+ # Usually instantiated from {Configuration#apply}. Copies options.
18
+ # @param options [Hash]
19
+ # @option options [String] :api_key API key of the project to connect to
20
+ # @option options [Fixnum] :port the port to connect to
21
+ # @option options [Boolean] :public whether to download draft or published content
22
+ # @option options [Fixnum] :http_read_timeout how long to wait before timing out when reading data from the socket
23
+ # @option options [Fixnum] :http_open_timeout how long to wait before timing out when opening the socket
24
+ # @option options [Boolean] :secure whether to use SSL
25
+ # @option options [Logger] :logger where to log transactions
26
+ def initialize(options)
27
+ [:api_key, :host, :port, :public, :http_read_timeout,
28
+ :http_open_timeout, :secure, :logger].each do |option|
29
+ instance_variable_set("@#{option}", options[option])
30
+ end
31
+ end
32
+
33
+ # Downloads all blurbs for the given api_key.
34
+ #
35
+ # If the +public+ option was set to +true+, this will use published blurbs.
36
+ # Otherwise, draft content is fetched.
37
+ #
38
+ # @return [Hash] blurbs
39
+ # @raise [ConnectionError] if the connection fails
40
+ def download
41
+ connect do |http|
42
+ response = http.get(uri(download_resource))
43
+ check(response)
44
+ log("Downloaded translations")
45
+ JSON.parse(response.body)
46
+ end
47
+ end
48
+
49
+ # Uploads the given hash of blurbs as draft content.
50
+ # @param data [Hash] the blurbs to upload
51
+ # @raise [ConnectionError] if the connection fails
52
+ def upload(data)
53
+ connect do |http|
54
+ response = http.post(uri("draft_blurbs"), data.to_json)
55
+ check(response)
56
+ log("Uploaded missing translations")
57
+ end
58
+ end
59
+
60
+ # Issues a deploy, marking all draft content as published for this project.
61
+ # @raise [ConnectionError] if the connection fails
62
+ def deploy
63
+ connect do |http|
64
+ response = http.post(uri("deploys"), "")
65
+ check(response)
66
+ log("Deployed")
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ attr_reader :host, :port, :api_key, :http_read_timeout,
73
+ :http_open_timeout, :secure, :logger
74
+
75
+ def public?
76
+ @public
77
+ end
78
+
79
+ def uri(resource)
80
+ "/api/v2/projects/#{api_key}/#{resource}"
81
+ end
82
+
83
+ def download_resource
84
+ if public?
85
+ "published_blurbs"
86
+ else
87
+ "draft_blurbs"
88
+ end
89
+ end
90
+
91
+ def connect
92
+ http = Net::HTTP.new(host, port)
93
+ http.open_timeout = http_open_timeout
94
+ http.read_timeout = http_read_timeout
95
+ http.use_ssl = secure
96
+ begin
97
+ yield(http)
98
+ rescue *HTTP_ERRORS => exception
99
+ raise ConnectionError, "#{exception.class.name}: #{exception.message}"
100
+ end
101
+ end
102
+
103
+ def check(response)
104
+ if Net::HTTPNotFound === response
105
+ raise InvalidApiKey, "Invalid API key: #{api_key}"
106
+ end
107
+
108
+ unless Net::HTTPSuccess === response
109
+ raise ConnectionError, "#{response.code}: #{response.body}"
110
+ end
111
+ end
112
+
113
+ def log(message)
114
+ logger.info(message)
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,197 @@
1
+ require 'logger'
2
+ require 'copycopter_client/i18n_backend'
3
+ require 'copycopter_client/client'
4
+ require 'copycopter_client/sync'
5
+ require 'copycopter_client/prefixed_logger'
6
+
7
+ module CopycopterClient
8
+ # Used to set up and modify settings for the client.
9
+ class Configuration
10
+
11
+ # These options will be present in the Hash returned by {#to_hash}.
12
+ OPTIONS = [:api_key, :development_environments, :environment_name, :host,
13
+ :http_open_timeout, :http_read_timeout, :client_name, :client_url,
14
+ :client_version, :port, :protocol, :proxy_host, :proxy_pass,
15
+ :proxy_port, :proxy_user, :secure, :polling_delay, :logger,
16
+ :framework, :fallback_backend].freeze
17
+
18
+ # @return [String] The API key for your project, found on the project edit form.
19
+ attr_accessor :api_key
20
+
21
+ # @return [String] The host to connect to (defaults to +copycopter.com+).
22
+ attr_accessor :host
23
+
24
+ # @return [Fixnum] The port on which your Copycopter server runs (defaults to +443+ for secure connections, +80+ for insecure connections).
25
+ attr_accessor :port
26
+
27
+ # @return [Boolean] +true+ for https connections, +false+ for http connections.
28
+ attr_accessor :secure
29
+
30
+ # @return [Fixnum] The HTTP open timeout in seconds (defaults to +2+).
31
+ attr_accessor :http_open_timeout
32
+
33
+ # @return [Fixnum] The HTTP read timeout in seconds (defaults to +5+).
34
+ attr_accessor :http_read_timeout
35
+
36
+ # @return [String, NilClass] The hostname of your proxy server (if using a proxy)
37
+ attr_accessor :proxy_host
38
+
39
+ # @return [String, Fixnum] The port of your proxy server (if using a proxy)
40
+ attr_accessor :proxy_port
41
+
42
+ # @return [String, NilClass] The username to use when logging into your proxy server (if using a proxy)
43
+ attr_accessor :proxy_user
44
+
45
+ # @return [String, NilClass] The password to use when logging into your proxy server (if using a proxy)
46
+ attr_accessor :proxy_pass
47
+
48
+ # @return [Array<String>] A list of environments in which content should be editable
49
+ attr_accessor :development_environments
50
+
51
+ # @return [Array<String>] A list of environments in which the server should not be contacted
52
+ attr_accessor :test_environments
53
+
54
+ # @return [String] The name of the environment the application is running in
55
+ attr_accessor :environment_name
56
+
57
+ # @return [String] The name of the client library being used to send notifications (defaults to +Copycopter Client+)
58
+ attr_accessor :client_name
59
+
60
+ # @return [String, NilClass] The framework notifications are being sent from, if any (such as +Rails 2.3.9+)
61
+ attr_accessor :framework
62
+
63
+ # @return [String] The version of the client library being used to send notifications (such as +1.0.2+)
64
+ attr_accessor :client_version
65
+
66
+ # @return [String] The url of the client library being used
67
+ attr_accessor :client_url
68
+
69
+ # @return [Integer] The time, in seconds, in between each sync to the server. Defaults to +300+.
70
+ attr_accessor :polling_delay
71
+
72
+ # @return [Logger] Where to log messages. Must respond to same interface as Logger.
73
+ attr_reader :logger
74
+
75
+ # @return [I18n::Backend::Base] where to look for translations missing on the Copycopter server
76
+ attr_accessor :fallback_backend
77
+
78
+ alias_method :secure?, :secure
79
+
80
+ # Instantiated from {CopycopterClient.configure}. Sets defaults.
81
+ def initialize
82
+ self.secure = false
83
+ self.host = 'copycopter.com'
84
+ self.http_open_timeout = 2
85
+ self.http_read_timeout = 5
86
+ self.development_environments = %w(development staging)
87
+ self.test_environments = %w(test cucumber)
88
+ self.client_name = 'Copycopter Client'
89
+ self.client_version = VERSION
90
+ self.client_url = 'http://copycopter.com'
91
+ self.polling_delay = 300
92
+ self.logger = Logger.new($stdout)
93
+
94
+ @applied = false
95
+ end
96
+
97
+ # Allows config options to be read like a hash
98
+ #
99
+ # @param [Symbol] option Key for a given attribute
100
+ # @return [Object] the given attribute
101
+ def [](option)
102
+ send(option)
103
+ end
104
+
105
+ # Returns a hash of all configurable options
106
+ # @return [Hash] configuration attributes
107
+ def to_hash
108
+ base_options = { :public => public? }
109
+ OPTIONS.inject(base_options) do |hash, option|
110
+ hash.merge(option.to_sym => send(option))
111
+ end
112
+ end
113
+
114
+ # Returns a hash of all configurable options merged with +hash+
115
+ #
116
+ # @param [Hash] hash A set of configuration options that will take precedence over the defaults
117
+ # @return [Hash] the merged configuration hash
118
+ def merge(hash)
119
+ to_hash.merge(hash)
120
+ end
121
+
122
+ # Determines if the content will be editable
123
+ # @return [Boolean] Returns +false+ if in a development environment, +true+ otherwise.
124
+ def public?
125
+ !(development_environments + test_environments).include?(environment_name)
126
+ end
127
+
128
+ # Determines if the content will fetched from the server
129
+ # @return [Boolean] Returns +true+ if in a test environment, +false+ otherwise.
130
+ def test?
131
+ test_environments.include?(environment_name)
132
+ end
133
+
134
+ # Determines if the configuration has been applied (internal)
135
+ # @return [Boolean] Returns +true+ if applied, +false+ otherwise.
136
+ def applied?
137
+ @applied
138
+ end
139
+
140
+ # Applies the configuration (internal).
141
+ #
142
+ # Called automatically when {CopycopterClient.configure} is called in the application.
143
+ #
144
+ # This creates the {Client}, {Sync}, and {I18nBackend} and puts them together.
145
+ #
146
+ # When {#test?} returns +false+, the sync will be started.
147
+ def apply
148
+ client = Client.new(to_hash)
149
+ sync = Sync.new(client, to_hash)
150
+ I18n.backend = I18nBackend.new(sync, to_hash)
151
+ CopycopterClient.client = client
152
+ CopycopterClient.sync = sync
153
+ @applied = true
154
+ logger.info("Client #{VERSION} ready")
155
+ logger.info("Environment Info: #{environment_info}")
156
+ sync.start unless test?
157
+ end
158
+
159
+ def port
160
+ @port || default_port
161
+ end
162
+
163
+ # The protocol that should be used when generating URLs to Copycopter.
164
+ # @return [String] +https+ if {#secure?} returns +true+, +http+ otherwise.
165
+ def protocol
166
+ if secure?
167
+ 'https'
168
+ else
169
+ 'http'
170
+ end
171
+ end
172
+
173
+ # For logging/debugging (internal).
174
+ # @return [String] a description of the environment in which this configuration was built.
175
+ def environment_info
176
+ parts = ["Ruby: #{RUBY_VERSION}", framework, "Env: #{environment_name}"]
177
+ parts.compact.map { |part| "[#{part}]" }.join(" ")
178
+ end
179
+
180
+ # Wraps the given logger in a PrefixedLogger. This way, CopycopterClient
181
+ # log messages are recognizable.
182
+ # @param original_logger [Logger] the upstream logger to use, which must respond to the standard +Logger+ severity methods.
183
+ def logger=(original_logger)
184
+ @logger = PrefixedLogger.new("** [Copycopter]", original_logger)
185
+ end
186
+
187
+ private
188
+
189
+ def default_port
190
+ if secure?
191
+ 443
192
+ else
193
+ 80
194
+ end
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,13 @@
1
+ module CopycopterClient
2
+ # Raised when an error occurs while contacting the Copycopter server. This is
3
+ # raised by {Client} and generally rescued by {Sync}. The application will
4
+ # not encounter this error. Polling will continue even if this error is raised.
5
+ class ConnectionError < StandardError
6
+ end
7
+
8
+ # Raised when the client is configured with an api key that the Copycopter
9
+ # server does not recognize. Polling is aborted when this error is raised.
10
+ class InvalidApiKey < StandardError
11
+ end
12
+ end
13
+
@@ -0,0 +1,40 @@
1
+ module CopycopterClient
2
+ # Helper methods for Copycopter
3
+ # @deprecated use +I81n#translate+ instead.
4
+ module Helper
5
+ # Returns copy for the given key in the current locale.
6
+ # @param key [String] the key you want copy for
7
+ # @param default [String, Hash] an optional default value, used if this key is missing
8
+ # @option default [String] :default the default text
9
+ def copy_for(key, default=nil)
10
+ default = if default.respond_to?(:to_hash)
11
+ default[:default]
12
+ else
13
+ default
14
+ end
15
+
16
+ key = scope_copycopter_key_by_partial(key)
17
+ warn("WARNING: #s is deprecated; use t(#{key.inspect}, :default => #{default.inspect}) instead.")
18
+ I18n.translate(key, { :default => default })
19
+ end
20
+ alias_method :s, :copy_for
21
+
22
+ private
23
+
24
+ def scope_copycopter_key_by_partial(key)
25
+ if respond_to?(:scope_key_by_partial, true)
26
+ scope_key_by_partial(key)
27
+ elsif key.to_s[0].chr == "."
28
+ if respond_to?(:template)
29
+ "#{template.path_without_format_and_extension.gsub(%r{/_?}, '.')}#{key}"
30
+ else
31
+ "#{controller_name}.#{action_name}#{key}"
32
+ end
33
+ else
34
+ key
35
+ end
36
+ end
37
+ end
38
+
39
+ extend Helper
40
+ end
@@ -0,0 +1,100 @@
1
+ require 'i18n'
2
+
3
+ module CopycopterClient
4
+ # I81n implementation designed to synchronize with Copycopter.
5
+ #
6
+ # Expects an object that acts like a Hash, responding to +[]+, +[]=+, and +keys+.
7
+ #
8
+ # This backend will be used as the default I81n backend when the client is
9
+ # configured, so you will not need to instantiate this class from the
10
+ # application. Instead, just use methods on the I81n class.
11
+ #
12
+ # If a fallback backend is provided, keys available in the fallback backend
13
+ # will be used as defaults when those keys aren't available on the Copycopter
14
+ # server.
15
+ class I18nBackend
16
+ include I18n::Backend::Base
17
+
18
+ # These keys aren't used in interpolation
19
+ RESERVED_KEYS = [:scope, :default, :separator, :resolve, :object,
20
+ :fallback, :format, :cascade, :raise, :rescue_format].freeze
21
+
22
+ # Usually instantiated when {Configuration#apply} is invoked.
23
+ # @param sync [Sync] must act like a hash, returning and accept blurbs by key.
24
+ # @param options [Hash]
25
+ # @option options [I18n::Backend::Base] :fallback_backend I18n backend where missing translations can be found
26
+ def initialize(sync, options)
27
+ @sync = sync
28
+ @base_url = URI.parse("#{options[:protocol]}://#{options[:host]}:#{options[:port]}")
29
+ @fallback = options[:fallback_backend]
30
+ end
31
+
32
+ # This is invoked by frameworks when locales should be loaded. The
33
+ # Copycopter client loads content in the background, so this method waits
34
+ # until the first download is complete.
35
+ def reload!
36
+ sync.wait_for_download
37
+ end
38
+
39
+ # Translates the given local and key. See the I81n API documentation for details.
40
+ #
41
+ # Because the Copycopter API only supports copy text and doesn't support
42
+ # nested structures or arrays, the fallback value will be returned without
43
+ # using the Copycopter API if that value doesn't respond to to_str.
44
+ #
45
+ # @return [Object] the translated key (usually a String)
46
+ def translate(locale, key, options = {})
47
+ fallback_value = fallback(locale, key, options)
48
+ return fallback_value if fallback_value && !fallback_value.respond_to?(:to_str)
49
+
50
+ default = fallback_value || options.delete(:default)
51
+ content = super(locale, key, options.update(:default => default))
52
+ if content.respond_to?(:html_safe)
53
+ content.html_safe
54
+ else
55
+ content
56
+ end
57
+ end
58
+
59
+ # Returns locales availabile for this Copycopter project.
60
+ # @return [Array<String>] available locales
61
+ def available_locales
62
+ sync.keys.map { |key| key.split('.').first }.uniq
63
+ end
64
+
65
+ private
66
+
67
+ def lookup(locale, key, scope = [], options = {})
68
+ parts = I18n.normalize_keys(locale, key, scope, options[:separator])
69
+ key = parts.join('.')
70
+ content = sync[key]
71
+ sync[key] = "" if content.nil?
72
+ content
73
+ end
74
+
75
+ attr_reader :sync
76
+
77
+ def fallback(locale, key, options)
78
+ if @fallback
79
+ fallback_options = options.dup
80
+ (fallback_options.keys - RESERVED_KEYS).each do |interpolated_key|
81
+ fallback_options[interpolated_key] = "%{#{interpolated_key}}"
82
+ end
83
+
84
+ @fallback.translate(locale, key, fallback_options)
85
+ end
86
+ rescue I18n::MissingTranslationData
87
+ nil
88
+ end
89
+
90
+ def default(locale, object, subject, options = {})
91
+ content = super(locale, object, subject, options)
92
+ if content.respond_to?(:to_str)
93
+ parts = I18n.normalize_keys(locale, object, options[:scope], options[:separator])
94
+ key = parts.join('.')
95
+ sync[key] = content.to_str
96
+ end
97
+ content
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,41 @@
1
+ module CopycopterClient
2
+ class PrefixedLogger
3
+ attr_reader :prefix, :original_logger
4
+
5
+ def initialize(prefix, logger)
6
+ @prefix = prefix
7
+ @original_logger = logger
8
+ end
9
+
10
+ def info(message = nil, &block)
11
+ log(:info, message, &block)
12
+ end
13
+
14
+ def debug(message = nil, &block)
15
+ log(:debug, message, &block)
16
+ end
17
+
18
+ def warn(message = nil, &block)
19
+ log(:warn, message, &block)
20
+ end
21
+
22
+ def error(message = nil, &block)
23
+ log(:error, message, &block)
24
+ end
25
+
26
+ def fatal(message = nil, &block)
27
+ log(:fatal, message, &block)
28
+ end
29
+
30
+ private
31
+
32
+ def log(severity, message, &block)
33
+ prefixed_message = "#{prefix} #{thread_info} #{message}"
34
+ original_logger.send(severity, prefixed_message, &block)
35
+ end
36
+
37
+ def thread_info
38
+ "[P:#{Process.pid}] [T:#{Thread.current.object_id}]"
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,31 @@
1
+ require 'copycopter_client/helper'
2
+
3
+ if defined?(ActionController::Base)
4
+ ActionController::Base.send :include, CopycopterClient::Helper
5
+ end
6
+ if defined?(ActionView::Base)
7
+ ActionView::Base.send :include, CopycopterClient::Helper
8
+ end
9
+
10
+ module CopycopterClient
11
+ # Responsible for Rails initialization
12
+ module Rails
13
+ # Sets up the logger, environment, name, project root, and framework name
14
+ # for Rails applications. Must be called after framework initialization.
15
+ def self.initialize
16
+ CopycopterClient.configure(false) do |config|
17
+ config.environment_name = ::Rails.env
18
+ config.logger = ::Rails.logger
19
+ config.framework = "Rails: #{::Rails::VERSION::STRING}"
20
+ config.fallback_backend = I18n.backend
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ if defined?(Rails::Railtie)
27
+ require 'copycopter_client/railtie'
28
+ else
29
+ CopycopterClient::Rails.initialize
30
+ end
31
+
@@ -0,0 +1,13 @@
1
+ module CopycopterClient
2
+ # Connects to integration points for Rails 3 applications
3
+ class Railtie < ::Rails::Railtie
4
+ initializer :initialize_copycopter_rails, :after => :before_initialize do
5
+ CopycopterClient::Rails.initialize
6
+ end
7
+
8
+ rake_tasks do
9
+ load "tasks/copycopter_client_tasks.rake"
10
+ end
11
+ end
12
+ end
13
+