Module: ActiveRecord::RailsDevsForDataIntegrity
- Defined in:
- lib/rails_devs_for_data_integrity.rb
Overview
Rails Devs For Data Integrity catches unique key and foreign key violations coming from the MySQLdatabase and converts them into an error on the ActiveRecord object similar to validation errors
class User < ActiveRecord::Base
handle_unique_key_violation :user_name, :message => 'is taken"
handle_foreign_key_violation :primary_email_id, :message => 'is not available'
end
Instead of this nasty MySQL foreign key error:
ActiveRecord::StatementInvalid: Mysql::Error: Cannot add or update a child row:
a foreign key constraint fails (`zoo_development/animals`,
CONSTRAINT `fk_animal_species` FOREIGN KEY (`species_id`)
REFERENCES `species` (`id`) ON DELETE SET NULL ON UPDATE CASCADE)
>> user.errors.on(:user_name)
=> "association does not exist."
Or in the case of a unique key violation:
>> user.errors.on(:primary_email_id)
=> "is a duplicate."
Developers
-
Blythe Dunham snowgiraffe.com
Install
-
Github Project: github.com/blythedunham/rails_devs_for_data_integrity/tree/master
-
Install: script/plugin install git://github.com/blythedunham/rails_devs_for_data_integrity.git
Defined Under Namespace
Modules: ClassMethods
Class Method Summary collapse
-
.included(base) ⇒ Object
:nodoc.
Instance Method Summary collapse
- #add_errors_for_violation(violation_type, columns) ⇒ Object
-
#add_foreign_key_error(exception, foreign_key = nil) ⇒ Object
Add a foreign key error message to errors based on the exception.
-
#add_unique_key_error(exception, columns = nil) ⇒ Object
Add a duplicate error message to errors based on the exception.
-
#create_or_update_with_data_integrity_check(options = {}) ⇒ Object
do a create or update with data integrity check.
-
#custom_error_message_for_violation(violation_type, columns) ⇒ Object
Custom error messages set with handle_violation.
-
#default_error_message_for_violation(violation_type, columns) ⇒ Object
:nodoc:.
-
#error_message_for_violation(violation_type, columns) ⇒ Object
Return the error message.
-
#execute_with_data_integrity_check(record = nil, &block) ⇒ Object
Executes the block and traps data integrity violations Populates the
recorderrors objects with an appropriate message if such violation occurs. -
#foreign_key_from_error_message(exception) ⇒ Object
Return the foreign key name from the foreign key exception.
-
#handle_data_integrity_error(exception, record = nil, &block) ⇒ Object
If
exceptionis a unique key violation or a foreign key error, excute the block if it exists. -
#index_for_record_not_unique(exception) ⇒ Object
:nodoc:.
-
#save_with_data_integrity_check!(*args) ⇒ Object
save! with data integrity check RecordNotSaved will be thrown by save! before converting to the standard validation error ActiveRecord::RecordInvalid.
Class Method Details
.included(base) ⇒ Object
:nodoc
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/rails_devs_for_data_integrity.rb', line 33 def self.included(base)#:nodoc base.send :class_inheritable_hash, :unique_key_check_options base.send :class_inheritable_hash, :foreign_key_check_options base.send :class_inheritable_hash, :default_violation_messages base.send :attr_reader, :duplicate_exception base.send :attr_reader, :foreign_key_exception base. = {} base. = {} base. = { :taken => 'has already been taken', :taken_multiple => "has already been taken for {{context}}", :taken_generic => 'Duplicate field.', :foreign_key => "association does not exist." } base.extend ClassMethods end |
Instance Method Details
#add_errors_for_violation(violation_type, columns) ⇒ Object
163 164 165 166 167 168 169 170 171 172 |
# File 'lib/rails_devs_for_data_integrity.rb', line 163 def add_errors_for_violation( violation_type, columns ) columns = [columns].flatten.compact = ( violation_type, columns ) if columns.blank? self.errors.add_to_base( ) else self.errors.add( columns.first, ) end end |
#add_foreign_key_error(exception, foreign_key = nil) ⇒ Object
Add a foreign key error message to errors based on the exception
197 198 199 200 |
# File 'lib/rails_devs_for_data_integrity.rb', line 197 def add_foreign_key_error(exception, foreign_key=nil) foreign_key ||= ( exception ) add_errors_for_violation( :foreign_key, foreign_key ) end |
#add_unique_key_error(exception, columns = nil) ⇒ Object
Add a duplicate error message to errors based on the exception
188 189 190 191 192 193 194 |
# File 'lib/rails_devs_for_data_integrity.rb', line 188 def add_unique_key_error( exception, columns = nil ) columns ||= begin index = index_for_record_not_unique( exception ) index.columns if index end add_errors_for_violation( :unique_key, columns ) end |
#create_or_update_with_data_integrity_check(options = {}) ⇒ Object
do a create or update with data integrity check
260 261 262 263 264 |
# File 'lib/rails_devs_for_data_integrity.rb', line 260 def create_or_update_with_data_integrity_check(={}) execute_with_data_integrity_check(self) do return create_or_update_without_data_integrity_check end end |
#custom_error_message_for_violation(violation_type, columns) ⇒ Object
Custom error messages set with handle_violation
146 147 148 149 150 151 152 153 154 155 |
# File 'lib/rails_devs_for_data_integrity.rb', line 146 def ( violation_type, columns )#:nodoc: = send( "#{violation_type}_check_options" )[ columns.first.to_sym ] if columns.any? = case [:message] when Symbol then Il8n.translate( [:message] ) else [:message] end unless .blank? end |
#default_error_message_for_violation(violation_type, columns) ⇒ Object
:nodoc:
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/rails_devs_for_data_integrity.rb', line 128 def ( violation_type, columns )#:nodoc: = if violation_type == :foreign_key :foreign_key else case columns.length when 0 then :taken_generic when 1 then :taken else :taken_multiple end end I18n.translate( :"activerecord.errors.messages.#{message_key}", :default => [ ], :context => columns.slice(1..-1).join('/') ) end |
#error_message_for_violation(violation_type, columns) ⇒ Object
Return the error message
158 159 160 161 |
# File 'lib/rails_devs_for_data_integrity.rb', line 158 def ( violation_type, columns )#:nodoc: = ( violation_type, columns ) ||= ( violation_type, columns ) end |
#execute_with_data_integrity_check(record = nil, &block) ⇒ Object
Executes the block and traps data integrity violations Populates the record errors objects with an appropriate message if such violation occurs
Example
def save_safe
execute_with_data_integrity_check(self) { save }
end
249 250 251 252 253 254 255 256 257 |
# File 'lib/rails_devs_for_data_integrity.rb', line 249 def execute_with_data_integrity_check(record = nil, &block) @duplicate_exception = nil @foreign_key_exception = nil yield record true rescue ActiveRecord::StatementInvalid => exception handle_data_integrity_error(exception, record) return false end |
#foreign_key_from_error_message(exception) ⇒ Object
Return the foreign key name from the foreign key exception
203 204 205 206 207 |
# File 'lib/rails_devs_for_data_integrity.rb', line 203 def (exception) if (match = exception.to_s.match(/^Mysql::Error.*foreign key constraint fails.*FOREIGN KEY\s*\(`?([\w_]*)`?\)/)) return match[1].dup end end |
#handle_data_integrity_error(exception, record = nil, &block) ⇒ Object
If exception is a unique key violation or a foreign key error, excute the block if it exists. If not and a record exists, add the appropriate error messages. Reraise any exceptions that are not data integrity violation errors Sometimes better to use execute_with_data_integrity_check block
exception - Exception thrown from save (insert or update) record - The activerecord object to add errors
Example
def save_safe
record = self
save
rescue ActiveRecord::StatementInvalid => exception
handle_data_integrity_error(exception, record)
end
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
# File 'lib/rails_devs_for_data_integrity.rb', line 225 def handle_data_integrity_error(exception, record=nil, &block) @duplicate_exception = exception if exception.to_s =~ /^Mysql::Error: Duplicate entry/ @foreign_key_exception = exception if exception.to_s =~ /^Mysql::Error.*foreign key constraint fails / if @duplicate_exception || @foreign_key_exception if block yield elsif record record.add_unique_key_error(exception) if @duplicate_exception record.add_foreign_key_error(exception) if @foreign_key_exception record end else raise exception end end |
#index_for_record_not_unique(exception) ⇒ Object
:nodoc:
174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/rails_devs_for_data_integrity.rb', line 174 def index_for_record_not_unique(exception) #:nodoc: case exception. when /Duplicate entry.*for key (\d+)/ index_position = $1.to_i # minus two b/c mysql message is one-based + rails excludes primary key index from indexes list ActiveRecord::Base.connection.indexes(self.class.table_name)[index_position - 2] when /Duplicate entry.*for key '(\w+)'/ index_name = $1 ActiveRecord::Base.connection.indexes(self.class.table_name).detect { |i| i.name == index_name } end end |
#save_with_data_integrity_check!(*args) ⇒ Object
save! with data integrity check RecordNotSaved will be thrown by save! before converting to the standard validation error ActiveRecord::RecordInvalid
269 270 271 272 273 274 |
# File 'lib/rails_devs_for_data_integrity.rb', line 269 def save_with_data_integrity_check!(*args) save_without_data_integrity_check!(*args) rescue ActiveRecord::RecordNotSaved => e raise ActiveRecord::RecordInvalid.new(self) if @duplicate_exception||@foreign_key_exception raise e end |