symmetric-encryption 3.2 → 3.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: acc34e0886fa7dcf660847dd29c02a34b102a815
4
- data.tar.gz: 2e74557115bc5fc5da4f899271f756f34a6af987
3
+ metadata.gz: 37b9132a3f23db50841774bccd1d0ea48db0a325
4
+ data.tar.gz: 66808f69ba790855acac29dcd2ca59b59ec5a611
5
5
  SHA512:
6
- metadata.gz: 9760706ef46ddd952e26b10fbea5007b51e8bfc5fedc85b7827fa4a3119850c089d1865d86b7ced4d8b5037117aa23b06eeb0c7f0841a56240823a28f09ac4c0
7
- data.tar.gz: e2b5991b164831c50519c5dd0879f1f5d720909bbcb57ff6a4537219ba58268cfa3425bcc0b10515a0f23a1783c6ba67a201ae3a18c7002de125e1d5ab173705
6
+ metadata.gz: 33ba910830b6f113ccefe74d029e2ec5d68f3ea15234bae0234c7de335d9d9e29f13edbc248398f6d766e02277ee22535f8d274cae10b48bcc9b8f388de81af5
7
+ data.tar.gz: c0dbfec5a29239451d18cd4ec8eef68f891100267a1b670e4b9224861b3cf9321ae7f525fc08ca7927aca260c72ec6696da40cca57af243d343bf288b28c40be
data/LICENSE.txt CHANGED
@@ -186,7 +186,7 @@
186
186
  same "printed page" as the copyright notice for easier
187
187
  identification within third-party archives.
188
188
 
189
- Copyright 2012 Clarity Services, Inc.
189
+ Copyright 2012, 2013, 2014 Reid Morrison
190
190
 
191
191
  Licensed under the Apache License, Version 2.0 (the "License");
192
192
  you may not use this file except in compliance with the License.
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  symmetric-encryption
2
2
  ====================
3
3
 
4
- * http://github.com/ClarityServices/symmetric-encryption
4
+ * http://github.com/reidmorrison/symmetric-encryption
5
5
 
6
6
  ## Introduction
7
7
 
@@ -163,18 +163,40 @@ class User < ActiveRecord::Base
163
163
  # Requires table users to have a column called encrypted_bank_account_number
164
164
  attr_encrypted :bank_account_number
165
165
 
166
- # Requires table users to have a column called encrypted_social_security_number
166
+ # Requires users table to have a column called encrypted_social_security_number
167
+ #
168
+ # Note: Encrypting the same value twice will result in the _same_ encrypted value
169
+ # when :random_iv => false, or is not specified
167
170
  attr_encrypted :social_security_number
168
171
 
172
+ # By specifying the type as :integer the value will be returned as an integer and
173
+ # can be set as an integer, even though it is stored in the database as an
174
+ # encrypted string
175
+ #
176
+ # Requires users table to have a column called encrypted_age of type string
177
+ attr_encrypted :age, :type => :integer
178
+
169
179
  # Since string and long_string are not used in the where clause of any SQL
170
180
  # queries it is better to ensure that the encrypted value is always different
171
181
  # by encrypting every value with a random Initialization Vector.
182
+ #
183
+ # Note: Encrypting the same value twice will result in different encrypted
184
+ # values when :random_iv => true
172
185
  attr_encrypted :string, :random_iv => true
173
186
 
174
187
  # Long encrypted strings can also be compressed prior to encryption to save
175
188
  # disk space
176
189
  attr_encrypted :long_string, :random_iv => true, :compress => true
177
190
 
191
+ # By specifying the type as :json the value will be serialized to JSON
192
+ # before encryption and deserialized from JSON after decryption.
193
+ #
194
+ # It is sometimes useful to use compression on large fields, so we can enable
195
+ # compression before the string is encrypted
196
+ #
197
+ # Requires users table to have a column called encrypted_values of type string
198
+ attr_encrypted :values, :type => :json, :compress => true
199
+
178
200
  validates :encrypted_bank_account_number, :symmetric_encryption => true
179
201
  validates :encrypted_social_security_number, :symmetric_encryption => true
180
202
  end
@@ -191,6 +213,19 @@ user.save!
191
213
  User.create(:bank_account_number => '12345')
192
214
  ```
193
215
 
216
+ Several types are supported for ActiveRecord models when encrypting or decrypting data.
217
+ Each type maps to the built-in Ruby types as follows:
218
+
219
+ - :string => String
220
+ - :integer => Integer
221
+ - :float => Float
222
+ - :decimal => BigDecimal
223
+ - :datetime => DateTime
224
+ - :time => Time
225
+ - :date => Date
226
+ - :json => Uses JSON serialization, useful for hashes and arrays
227
+ - :yaml => Uses YAML serialization, useful for hashes and arrays
228
+
194
229
  ### Mongoid Example
195
230
 
196
231
  To encrypt a field in a Mongoid document, just add ":encrypted => true" at the end
@@ -205,6 +240,13 @@ class User
205
240
  field :encrypted_bank_account_number, :type => String, :encrypted => true
206
241
  field :encrypted_social_security_number, :type => String, :encrypted => true
207
242
  field :encrypted_life_history, :type => String, :encrypted => {:compress => true, :random_iv => true}
243
+
244
+ # Encrypted fields are _always_ stored in Mongo as a String
245
+ # To get the result back as an Integer, Symmetric Encryption can do the
246
+ # necessary conversions by specifying the internal type as an option
247
+ # to :encrypted
248
+ # #see SymmetricEncryption::COERCION_TYPES for full list of types
249
+ field :encrypted_age, :type => String, :encrypted => {:type => :integer}
208
250
  end
209
251
 
210
252
  # Create a new user document
@@ -217,6 +259,8 @@ user = User.where(:encrypted_bank_account_number => SymmetricEncryption.encrypt(
217
259
  puts user.bank_account_number
218
260
  ```
219
261
 
262
+ Note: At this time Symmetric Encryption only supports Mongoid fields with a type of String
263
+
220
264
  ### Validation Example
221
265
 
222
266
  ```ruby
@@ -327,6 +371,13 @@ Encrypt a known value, such as a password:
327
371
  Note: Passwords must be encrypted in the environment in which they will be used.
328
372
  Since each environment should have its own symmetric encryption keys
329
373
 
374
+ Note: To use the rake task 'symmetric_encryption:encrypt' the gem 'highline'
375
+ must first be installed by adding to bundler or installing directly:
376
+
377
+ ```ruby
378
+ gem install 'highline'
379
+ ```
380
+
330
381
  Encrypt a file
331
382
 
332
383
  INFILE="Gemfile.lock" OUTFILE="Gemfile.lock.encrypted" rake symmetric_encryption:encrypt_file
@@ -614,9 +665,9 @@ production:
614
665
  Meta
615
666
  ----
616
667
 
617
- * Code: `git clone git://github.com/ClarityServices/symmetric-encryption.git`
618
- * Home: <https://github.com/ClarityServices/symmetric-encryption>
619
- * Issues: <http://github.com/ClarityServices/symmetric-encryption/issues>
668
+ * Code: `git clone git://github.com/reidmorrison/symmetric-encryption.git`
669
+ * Home: <https://github.com/reidmorrison/symmetric-encryption>
670
+ * Issues: <http://github.com/reidmorrison/symmetric-encryption/issues>
620
671
  * Gems: <http://rubygems.org/gems/symmetric-encryption>
621
672
 
622
673
  This project uses [Semantic Versioning](http://semver.org/).
@@ -624,12 +675,17 @@ This project uses [Semantic Versioning](http://semver.org/).
624
675
  Authors
625
676
  -------
626
677
 
627
- Reid Morrison :: reidmo@gmail.com :: @reidmorrison
678
+ [Reid Morrison](https://github.com/reidmorrison)
679
+
680
+ Contributors
681
+ ------------
682
+
683
+ [M. Scott Ford](https://github.com/mscottford)
628
684
 
629
685
  License
630
686
  -------
631
687
 
632
- Copyright 2012 Clarity Services, Inc.
688
+ Copyright 2012, 2013, 2014 Reid Morrison
633
689
 
634
690
  Licensed under the Apache License, Version 2.0 (the "License");
635
691
  you may not use this file except in compliance with the License.
@@ -643,9 +699,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
643
699
  See the License for the specific language governing permissions and
644
700
  limitations under the License.
645
701
 
646
- Compliance
702
+ Disclaimer
647
703
  ----------
648
704
 
649
- Although this library has assisted Clarity in meeting PCI Compliance it in no
650
- way guarantees that PCI Compliance will be met by anyone using this library
651
- for encryption purposes.
705
+ Although this library has assisted in meeting PCI Compliance and has passed
706
+ previous PCI audits, it in no way guarantees that PCI Compliance will be
707
+ achieved by anyone using this library.
@@ -1,4 +1,8 @@
1
+ # Used for compression
1
2
  require 'zlib'
3
+ # Used to coerce data types between string and their actual types
4
+ require 'coercible'
5
+
2
6
  require 'symmetric_encryption/version'
3
7
  require 'symmetric_encryption/cipher'
4
8
  require 'symmetric_encryption/symmetric_encryption'
@@ -10,13 +14,7 @@ end
10
14
  if defined?(Rails)
11
15
  require 'symmetric_encryption/railtie'
12
16
  end
13
- # attr_encrypted and Encrypted validator
14
- if defined?(ActiveRecord::Base)
15
- require 'symmetric_encryption/extensions/active_record/base'
16
- require 'symmetric_encryption/railties/symmetric_encryption_validator'
17
- end
18
17
 
19
- # field encryption for Mongoid
20
- if defined?(Mongoid)
21
- require 'symmetric_encryption/mongoid'
22
- end
18
+ require 'symmetric_encryption/extensions/active_record/base' if defined?(ActiveRecord::Base)
19
+ require 'symmetric_encryption/railties/symmetric_encryption_validator' if defined?(ActiveModel)
20
+ require 'symmetric_encryption/mongoid' if defined?(Mongoid)
@@ -9,10 +9,6 @@ module ActiveRecord #:nodoc:
9
9
  # * Symbolic names of each method to create which has a corresponding
10
10
  # method already defined in rails starting with: encrypted_
11
11
  # * Followed by an optional hash:
12
- # :marshal [true|false]
13
- # Whether this element should be converted to YAML before encryption
14
- # Default: false
15
- #
16
12
  # :random_iv [true|false]
17
13
  # Whether the encrypted value should use a random IV every time the
18
14
  # field is encrypted.
@@ -29,6 +25,10 @@ module ActiveRecord #:nodoc:
29
25
  # Default: false
30
26
  # Highly Recommended where feasible: true
31
27
  #
28
+ # :type [Symbol]
29
+ # The type for this field, #see SymmetricEncryption::COERCION_TYPES
30
+ # Default: :string
31
+ #
32
32
  # :compress [true|false]
33
33
  # Whether to compress str before encryption
34
34
  # Should only be used for large strings since compression overhead and
@@ -41,20 +41,38 @@ module ActiveRecord #:nodoc:
41
41
  # Ignore failures since the table may not yet actually exist
42
42
  define_attribute_methods rescue nil
43
43
 
44
- options = params.last.is_a?(Hash) ? params.pop : {}
45
- random_iv = options.fetch(:random_iv, false)
46
- compress = options.fetch(:compress, false)
47
- marshal = options.fetch(:marshal, false)
44
+ options = params.last.is_a?(Hash) ? params.pop.dup : {}
45
+ random_iv = options.delete(:random_iv) || false
46
+ compress = options.delete(:compress) || false
47
+ type = options.delete(:type) || :string
48
+
49
+ raise "Invalid type: #{type.inspect}. Valid types: #{SymmetricEncryption::COERCION_TYPES.inspect}" unless SymmetricEncryption::COERCION_TYPES.include?(type)
50
+
51
+ # For backward compatibility
52
+ if options.delete(:marshal) == true
53
+ warn("The :marshal option has been deprecated in favor of :type. For example: attr_encrypted name, :type => :yaml")
54
+ raise "Marshal is depreacted and cannot be used in conjunction with :type, just use :type. For #{params.inspect}" if type != :string
55
+ type = :yaml
56
+ end
57
+
58
+ options.each {|option| warn "Ignoring unknown option #{option.inspect} supplied to attr_encrypted with #{params.inspect}"}
59
+
60
+ if const_defined?(:EncryptedAttributes, _search_ancestors = false)
61
+ mod = const_get(:EncryptedAttributes)
62
+ else
63
+ mod = const_set(:EncryptedAttributes, Module.new)
64
+ include mod
65
+ end
48
66
 
49
67
  params.each do |attribute|
50
68
  # Generate unencrypted attribute with getter and setter
51
- class_eval(<<-UNENCRYPTED, __FILE__, __LINE__ + 1)
69
+ mod.module_eval(<<-UNENCRYPTED, __FILE__, __LINE__ + 1)
52
70
  # Returns the decrypted value for the encrypted attribute
53
71
  # The decrypted value is cached and is only decrypted if the encrypted value has changed
54
72
  # If this method is not called, then the encrypted value is never decrypted
55
73
  def #{attribute}
56
74
  if @stored_encrypted_#{attribute} != self.encrypted_#{attribute}
57
- @#{attribute} = ::SymmetricEncryption.decrypt(self.encrypted_#{attribute}).freeze
75
+ @#{attribute} = ::SymmetricEncryption.decrypt(self.encrypted_#{attribute},version=nil,:#{type}).freeze
58
76
  @stored_encrypted_#{attribute} = self.encrypted_#{attribute}
59
77
  end
60
78
  @#{attribute}
@@ -63,7 +81,7 @@ module ActiveRecord #:nodoc:
63
81
  # Set the un-encrypted attribute
64
82
  # Also updates the encrypted field with the encrypted value
65
83
  def #{attribute}=(value)
66
- self.encrypted_#{attribute} = @stored_encrypted_#{attribute} = ::SymmetricEncryption.encrypt(value#{".to_yaml" if marshal},#{random_iv},#{compress})
84
+ self.encrypted_#{attribute} = @stored_encrypted_#{attribute} = ::SymmetricEncryption.encrypt(value,#{random_iv},#{compress},:#{type})
67
85
  @#{attribute} = value.freeze
68
86
  end
69
87
  UNENCRYPTED
@@ -17,8 +17,15 @@
17
17
  #
18
18
  # field :name, :type => String
19
19
  # field :encrypted_social_security_number, :type => String, :encrypted => true
20
- # field :age, :type => Integer
20
+ # field :date_of_birth, :type => Date
21
21
  # field :encrypted_life_history, :type => String, :encrypted => {:compress => true, :random_iv => true}
22
+ #
23
+ # # Encrypted fields are _always_ stored in Mongo as a String
24
+ # # To get the result back as an Integer, Symmetric Encryption can do the
25
+ # # necessary conversions by specifying the internal type as an option
26
+ # # to :encrypted
27
+ # # #see SymmetricEncryption::COERCION_TYPES for full list of types
28
+ # field :encrypted_age, :type => String, :encrypted => {:type => :integer, :random_iv => true}
22
29
  # end
23
30
  #
24
31
  # The above document results in the following document in the Mongo collection 'persons':
@@ -67,9 +74,13 @@
67
74
  # @param [ Hash ] options The options to pass to the field.
68
75
  #
69
76
  # @option options [ Boolean | Hash ] :encrypted If the field contains encrypted data.
70
- # @option options [ Symbol ] :decrypt_as Name of the getters and setters to generate to access the decrypted value of this field.
71
- # @option options [ Boolean ] :compress Whether to compress this encrypted field
72
- # @option options [ Boolean ] :random_iv Whether the encrypted value should use a random IV every time the field is encrypted.
77
+ # When :encrypted is a Hash it consists of:
78
+ # @option options [ Symbol ] :type The type for this field, #see SymmetricEncryption::COERCION_TYPES
79
+ # @option options [ Boolean ] :random_iv Whether the encrypted value should use a random IV every time the field is encrypted.
80
+ # @option options [ Boolean ] :compress Whether to compress this encrypted field
81
+ # @option options [ Symbol ] :decrypt_as Name of the getters and setters to generate to access the decrypted value of this field.
82
+ #
83
+ # Some of the other regular Mongoid options:
73
84
  #
74
85
  # @option options [ Class ] :type The type of the field.
75
86
  # @option options [ String ] :label The label for the field.
@@ -90,14 +101,25 @@ Mongoid::Fields.option :encrypted do |model, field, options|
90
101
 
91
102
  random_iv = options.delete(:random_iv) || false
92
103
  compress = options.delete(:compress) || false
104
+ type = options.delete(:type) || :string
105
+ raise "Invalid type: #{type.inspect}. Valid types: #{SymmetricEncryption::COERCION_TYPES.inspect}" unless SymmetricEncryption::COERCION_TYPES.include?(type)
106
+
107
+ options.each {|option| warn "Ignoring unknown option #{option.inspect} supplied to Mongoid :encrypted for #{model}##{field}"}
108
+
109
+ if model.const_defined?(:EncryptedAttributes, _search_ancestors = false)
110
+ mod = model.const_get(:EncryptedAttributes)
111
+ else
112
+ mod = model.const_set(:EncryptedAttributes, Module.new)
113
+ model.send(:include, mod)
114
+ end
93
115
 
94
116
  # Generate getter and setter methods
95
- model.class_eval(<<-EOS, __FILE__, __LINE__ + 1)
117
+ mod.module_eval(<<-EOS, __FILE__, __LINE__ + 1)
96
118
  # Set the un-encrypted field
97
119
  # Also updates the encrypted field with the encrypted value
98
120
  # Freeze the decrypted field value so that it is not modified directly
99
121
  def #{decrypted_field_name}=(value)
100
- self.#{encrypted_field_name} = @stored_#{encrypted_field_name} = ::SymmetricEncryption.encrypt(value,#{random_iv},#{compress})
122
+ self.#{encrypted_field_name} = @stored_#{encrypted_field_name} = ::SymmetricEncryption.encrypt(value,#{random_iv},#{compress},:#{type})
101
123
  @#{decrypted_field_name} = value.freeze
102
124
  end
103
125
 
@@ -106,7 +128,7 @@ Mongoid::Fields.option :encrypted do |model, field, options|
106
128
  # If this method is not called, then the encrypted value is never decrypted
107
129
  def #{decrypted_field_name}
108
130
  if @stored_#{encrypted_field_name} != self.#{encrypted_field_name}
109
- @#{decrypted_field_name} = ::SymmetricEncryption.decrypt(self.#{encrypted_field_name}).freeze
131
+ @#{decrypted_field_name} = ::SymmetricEncryption.decrypt(self.#{encrypted_field_name},version=nil,:#{type}).freeze
110
132
  @stored_#{encrypted_field_name} = self.#{encrypted_field_name}
111
133
  end
112
134
  @#{decrypted_field_name}
@@ -8,7 +8,11 @@ namespace :symmetric_encryption do
8
8
 
9
9
  desc 'Encrypt a value, such as a password. Example: rake symmetric_encryption:encrypt'
10
10
  task :encrypt => :environment do
11
- require 'highline'
11
+ begin
12
+ require 'highline'
13
+ rescue LoadError
14
+ raise "Please install gem highline before using the command line task to encrypt an entered string.\n gem install \"highline\""
15
+ end
12
16
  password1 = nil
13
17
  password2 = 0
14
18
 
@@ -14,6 +14,20 @@ module SymmetricEncryption
14
14
  @@secondary_ciphers = []
15
15
  @@select_cipher = nil
16
16
 
17
+ # List of types supported when encrypting or decrypting data
18
+ #
19
+ # Each type maps to the built-in Ruby types as follows:
20
+ # :string => String
21
+ # :integer => Integer
22
+ # :float => Float
23
+ # :decimal => BigDecimal
24
+ # :datetime => DateTime
25
+ # :time => Time
26
+ # :date => Date
27
+ # :json => Uses JSON serialization, useful for hashes and arrays
28
+ # :yaml => Uses YAML serialization, useful for hashes and arrays
29
+ COERCION_TYPES = [:string, :integer, :float, :decimal, :datetime, :time, :date, :boolean, :json, :yaml]
30
+
17
31
  # Set the Primary Symmetric Cipher to be used
18
32
  #
19
33
  # Example: For testing purposes the following test cipher can be used:
@@ -58,8 +72,8 @@ module SymmetricEncryption
58
72
  end
59
73
 
60
74
  # AES Symmetric Decryption of supplied string
61
- # Returns decrypted string
62
- # Returns nil if the supplied str is nil
75
+ # Returns decrypted value
76
+ # Returns nil if the supplied value is nil
63
77
  # Returns "" if it is a string and it is empty
64
78
  #
65
79
  # Parameters
@@ -68,6 +82,13 @@ module SymmetricEncryption
68
82
  # version
69
83
  # Specify which cipher version to use if no header is present on the
70
84
  # encrypted string
85
+ # type [:string|:integer|:float|:decimal|:datetime|:time|:date|:boolean]
86
+ # If value is set to something other than :string, then the coercible gem
87
+ # will be use to coerce the unencrypted string value into the specified
88
+ # type. This assumes that the value was stored using the same type.
89
+ # Note: If type is set to something other than :string, it's expected
90
+ # that the coercible gem is available in the path.
91
+ # Default: :string
71
92
  #
72
93
  # If the supplied string has an encryption header then the cipher matching
73
94
  # the version number in the header will be used to decrypt the string
@@ -84,7 +105,7 @@ module SymmetricEncryption
84
105
  # yet significant number of cases it is possible to decrypt data using
85
106
  # the incorrect key. Clearly the data returned is garbage, but it still
86
107
  # successfully returns a string of data
87
- def self.decrypt(encrypted_and_encoded_string, version=nil)
108
+ def self.decrypt(encrypted_and_encoded_string, version=nil, type=:string)
88
109
  raise "Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data" unless @@cipher
89
110
  return encrypted_and_encoded_string if encrypted_and_encoded_string.nil? || (encrypted_and_encoded_string == '')
90
111
 
@@ -109,7 +130,7 @@ module SymmetricEncryption
109
130
  decrypted.force_encoding(SymmetricEncryption::BINARY_ENCODING)
110
131
  end
111
132
  end
112
- decrypted
133
+ coerce_from_string(decrypted, type)
113
134
  end
114
135
 
115
136
  # AES Symmetric Encryption of supplied string
@@ -118,7 +139,7 @@ module SymmetricEncryption
118
139
  # Returns "" if it is a string and it is empty
119
140
  #
120
141
  # Parameters
121
- # str [String]
142
+ # value [Object]
122
143
  # String to be encrypted. If str is not a string, #to_s will be called on it
123
144
  # to convert it to a string
124
145
  #
@@ -145,11 +166,20 @@ module SymmetricEncryption
145
166
  # compression
146
167
  # Note: Adds a 6 byte header prior to encoding, only if :random_iv is false
147
168
  # Default: false
148
- def self.encrypt(str, random_iv=false, compress=false)
169
+ #
170
+ # type [:string|:integer|:float|:decimal|:datetime|:time|:date|:boolean]
171
+ # Expected data type of the value to encrypt
172
+ # Uses the coercible gem to coerce non-string values into string values.
173
+ # When type is set to :string (the default), uses #to_s to convert
174
+ # non-string values to string values.
175
+ # Note: If type is set to something other than :string, it's expected that
176
+ # the coercible gem is available in the path.
177
+ # Default: :string
178
+ def self.encrypt(str, random_iv=false, compress=false, type=:string)
149
179
  raise "Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data" unless @@cipher
150
180
 
151
181
  # Encrypt and then encode the supplied string
152
- @@cipher.encrypt(str, random_iv, compress)
182
+ @@cipher.encrypt(coerce_to_string(str, type), random_iv, compress)
153
183
  end
154
184
 
155
185
  # Invokes decrypt
@@ -425,6 +455,63 @@ module SymmetricEncryption
425
455
  Cipher.new(config)
426
456
  end
427
457
 
458
+ # Uses coercible gem to coerce values from strings into the target type
459
+ # Note: if the type is :string, then the value is returned as is, and the
460
+ # coercible gem is not used at all.
461
+ def self.coerce_from_string(value, type)
462
+ return if value.nil?
463
+ case type
464
+ when :string
465
+ value
466
+ when :json
467
+ JSON.load(value)
468
+ when :yaml
469
+ YAML.load(value)
470
+ else
471
+ coercer = Coercible::Coercer.new
472
+ coercion_method = "to_#{type}".to_sym
473
+ coercer[String].send(coercion_method, value)
474
+ end
475
+ end
476
+
477
+ # Uses coercible gem to coerce values to strings from the specified type
478
+ # Note: if the type is :string, and value is not nil, then #to_s is called
479
+ # on the value and the coercible gem is not used at all.
480
+ def self.coerce_to_string(value, type)
481
+ return if value.nil?
482
+
483
+ case type
484
+ when :string
485
+ value.to_s
486
+ when :json
487
+ value.to_json
488
+ when :yaml
489
+ value.to_yaml
490
+ else
491
+ coercer = Coercible::Coercer.new
492
+ coercer[coercion_type(type, value)].to_string(value)
493
+ end
494
+ end
495
+
496
+ # Returns the correct coercion type to use for the specified symbol and value
497
+ def self.coercion_type(symbol, value)
498
+ if symbol == :boolean
499
+ value.class
500
+ else
501
+ COERCION_TYPE_MAP[symbol]
502
+ end
503
+ end
504
+
505
+ COERCION_TYPE_MAP = {
506
+ :string => String,
507
+ :integer => Integer,
508
+ :float => Float,
509
+ :decimal => BigDecimal,
510
+ :datetime => DateTime,
511
+ :time => Time,
512
+ :date => Date
513
+ }
514
+
428
515
  # With Ruby 1.9 strings have encodings
429
516
  if defined?(Encoding)
430
517
  BINARY_ENCODING = Encoding.find("binary")