copycopter_client 1.0.0.beta1

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 (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
+