├── db └── .gitkeep ├── .rspec ├── .rvmrc ├── .document ├── lib ├── safe_attributes │ ├── version.rb │ ├── railtie.rb │ └── base.rb └── safe_attributes.rb ├── Appraisals ├── .gitignore ├── Gemfile ├── gemfiles ├── rails_3_0.gemfile ├── rails_3_1.gemfile ├── rails_3_2.gemfile ├── rails_3_0.gemfile.lock ├── rails_3_2.gemfile.lock └── rails_3_1.gemfile.lock ├── Rakefile ├── spec ├── spec_helper.rb └── safe_attributes │ └── safe_attributes_spec.rb ├── NEWS.rdoc ├── LICENSE ├── safe_attributes.gemspec └── README.rdoc /db/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /.rvmrc: -------------------------------------------------------------------------------- 1 | rvm --create use ruby-1.9.3@safe_attributes 2 | -------------------------------------------------------------------------------- /.document: -------------------------------------------------------------------------------- 1 | lib/**/*.rb 2 | bin/* 3 | - 4 | features/**/*.feature 5 | LICENSE.txt 6 | -------------------------------------------------------------------------------- /lib/safe_attributes/version.rb: -------------------------------------------------------------------------------- 1 | module SafeAttributes 2 | module VERSION 3 | MAJOR = 1 4 | MINOR = 0 5 | TINY = 10 6 | 7 | STRING = [MAJOR, MINOR, TINY].compact.join('.') 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /Appraisals: -------------------------------------------------------------------------------- 1 | appraise "rails-3-0" do 2 | gem "activerecord", "~> 3.0.0" 3 | end 4 | 5 | appraise "rails-3-1" do 6 | gem "activerecord", "~> 3.1.0" 7 | end 8 | 9 | appraise "rails-3-2" do 10 | gem "activerecord", "~> 3.2.0" 11 | end 12 | -------------------------------------------------------------------------------- /lib/safe_attributes.rb: -------------------------------------------------------------------------------- 1 | require 'active_support' 2 | require 'safe_attributes/base' 3 | 4 | module SafeAttributes 5 | extend ActiveSupport::Concern 6 | include SafeAttributes::Base 7 | end 8 | 9 | require 'safe_attributes/railtie.rb' if defined?(Rails) 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## MAC OS 2 | .DS_Store 3 | 4 | ## TEXTMATE 5 | *.tmproj 6 | tmtags 7 | 8 | ## EMACS 9 | *~ 10 | \#* 11 | .\#* 12 | 13 | ## VIM 14 | *.swp 15 | 16 | ## PROJECT::GENERAL 17 | coverage 18 | rdoc 19 | pkg 20 | .bundle 21 | Gemfile.lock 22 | 23 | ## PROJECT::SPECIFIC 24 | db/*.db 25 | -------------------------------------------------------------------------------- /lib/safe_attributes/railtie.rb: -------------------------------------------------------------------------------- 1 | require 'safe_attributes' 2 | require 'rails' 3 | 4 | module SafeAttributes 5 | class Railtie < ::Rails::Railtie 6 | initializer "safeattributes.active_record" do |app| 7 | ActiveSupport.on_load :active_record do 8 | ActiveRecord::Base.send :include, SafeAttributes 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | gem 'appraisal' 6 | 7 | group :test do 8 | gem 'simplecov', :require => false, :platform => :mri 9 | end 10 | 11 | gem 'debugger', :platform => :mri_19 12 | gem 'sqlite3', :platform => :ruby 13 | gem 'jruby-openssl', :platform => :jruby 14 | gem 'activerecord-jdbcsqlite3-adapter', :platform => :jruby 15 | -------------------------------------------------------------------------------- /gemfiles/rails_3_0.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "appraisal" 6 | gem "debugger", :platform=>:mri_19 7 | gem "sqlite3", :platform=>:ruby 8 | gem "jruby-openssl", :platform=>:jruby 9 | gem "activerecord-jdbcsqlite3-adapter", :platform=>:jruby 10 | gem "activerecord", "~> 3.0.0" 11 | 12 | gemspec :path=>"../" -------------------------------------------------------------------------------- /gemfiles/rails_3_1.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "appraisal" 6 | gem "debugger", :platform=>:mri_19 7 | gem "sqlite3", :platform=>:ruby 8 | gem "jruby-openssl", :platform=>:jruby 9 | gem "activerecord-jdbcsqlite3-adapter", :platform=>:jruby 10 | gem "activerecord", "~> 3.1.0" 11 | 12 | gemspec :path=>"../" -------------------------------------------------------------------------------- /gemfiles/rails_3_2.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "appraisal" 6 | gem "debugger", :platform=>:mri_19 7 | gem "sqlite3", :platform=>:ruby 8 | gem "jruby-openssl", :platform=>:jruby 9 | gem "activerecord-jdbcsqlite3-adapter", :platform=>:jruby 10 | gem "activerecord", "~> 3.2.0" 11 | 12 | gemspec :path=>"../" -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'appraisal' 3 | require 'safe_attributes/version' 4 | require 'rspec/core' 5 | require 'rspec/core/rake_task' 6 | 7 | RSpec::Core::RakeTask.new(:spec) do |spec| 8 | end 9 | 10 | task :default => :spec 11 | 12 | require 'rdoc/task' 13 | Rake::RDocTask.new do |rdoc| 14 | rdoc.rdoc_dir = 'rdoc' 15 | rdoc.title = "SafeAttributes #{SafeAttributes::VERSION::STRING}" 16 | rdoc.rdoc_files.include('README*') 17 | rdoc.rdoc_files.include('NEWS*') 18 | rdoc.rdoc_files.include('lib/**/*.rb') 19 | end 20 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 2 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 3 | 4 | require 'rubygems' 5 | require 'bundler' 6 | Bundler.setup(:default, :development) 7 | 8 | require 'active_record' 9 | require 'active_support' 10 | require 'safe_attributes' 11 | require 'rspec' 12 | require 'rspec/autorun' 13 | 14 | root = File.expand_path(File.join(File.dirname(__FILE__), '..')) 15 | ActiveRecord::Base.establish_connection( 16 | :adapter => (RUBY_PLATFORM == 'java') ? "jdbcsqlite3" : "sqlite3", 17 | :database => "#{root}/db/safeattributes.db" 18 | ) 19 | 20 | RSpec.configure do |config| 21 | end 22 | 23 | -------------------------------------------------------------------------------- /NEWS.rdoc: -------------------------------------------------------------------------------- 1 | = Version 1.0.10 2 | 3 | * The attribute 'association' should work better now 4 | 5 | = Version 1.0.9 6 | 7 | * Can avoid the railtie that automatically includes SafeAttributes, see README 8 | * Using appraisal to test multiple versions of activerecord 9 | 10 | = Version 1.0.8 11 | 12 | * Tested against Rails 3.2 13 | * No longer uses InstanceMethods, avoids deprecation warning under Rails 3.2 14 | * Updated README noting column named 'attribute' will never work 15 | 16 | = Version 1.0.7 17 | 18 | * Resolved validation issue when using Devise 19 | 20 | = Version 1.0.6 21 | 22 | * Updated gemspec after verifying gem works with ActiveRecord 3.1. 23 | 24 | = Version 1.0.5 25 | 26 | * Resolved issues with columns named 'changed' and 'errors' 27 | 28 | = Version 1.0.4 29 | 30 | * Tested against Ruby 1.9.2, found and fixed an issue with default 31 | bad attribute name list 32 | * Tested against JRuby 1.5.6, found no issues 33 | 34 | = Version 1.0.3 35 | 36 | * Added validations support that should work with ActiveModel validators 37 | by default 38 | 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010,2011,2012,2013 C. Brian Jones 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /safe_attributes.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "safe_attributes/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "safe_attributes" 7 | s.version = SafeAttributes::VERSION::STRING 8 | 9 | s.required_rubygems_version = '>= 1.8.10' 10 | s.authors = ["Brian Jones"] 11 | s.date = Time.now.utc.strftime("%Y-%m-%d") 12 | s.summary = "Useful for legacy database support, adds support for reserved word column names with ActiveRecord" 13 | s.description = "Better support for legacy database schemas for ActiveRecord, such as columns named class, or any other name that conflicts with an instance method of ActiveRecord." 14 | s.email = "cbjones1@gmail.com" 15 | s.homepage = "http://github.com/bjones/safe_attributes" 16 | s.files = Dir.glob("{lib,spec}/**/*") + %w(README.rdoc NEWS.rdoc LICENSE Rakefile Gemfile Appraisals safe_attributes.gemspec) 17 | s.test_files = Dir.glob("{spec}/**/*") 18 | s.require_paths = ["lib"] 19 | s.licenses = ["MIT"] 20 | 21 | s.add_runtime_dependency(%q, [">= 3.0.0"]) 22 | 23 | s.add_development_dependency(%q, [">= 0.8.7"]) 24 | s.add_development_dependency(%q, [">= 0"]) 25 | s.add_development_dependency(%q, [">= 2.3.0"]) 26 | end 27 | 28 | -------------------------------------------------------------------------------- /gemfiles/rails_3_0.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: /Users/ge.unx.sas.com/vol/vol420/u42/cbjone/codebase/safe_attributes 3 | specs: 4 | safe_attributes (1.0.10) 5 | activerecord (>= 3.0.0) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | activemodel (3.0.17) 11 | activesupport (= 3.0.17) 12 | builder (~> 2.1.2) 13 | i18n (~> 0.5.0) 14 | activerecord (3.0.17) 15 | activemodel (= 3.0.17) 16 | activesupport (= 3.0.17) 17 | arel (~> 2.0.10) 18 | tzinfo (~> 0.3.23) 19 | activerecord-jdbc-adapter (1.2.7) 20 | activerecord-jdbcsqlite3-adapter (1.2.7) 21 | activerecord-jdbc-adapter (~> 1.2.7) 22 | jdbc-sqlite3 (~> 3.7.2) 23 | activesupport (3.0.17) 24 | appraisal (0.5.1) 25 | bundler 26 | rake 27 | arel (2.0.10) 28 | bouncy-castle-java (1.5.0146.1) 29 | builder (2.1.2) 30 | columnize (0.3.6) 31 | debugger (1.4.0) 32 | columnize (>= 0.3.1) 33 | debugger-linecache (~> 1.1.1) 34 | debugger-ruby_core_source (~> 1.2.0) 35 | debugger-linecache (1.1.2) 36 | debugger-ruby_core_source (>= 1.1.1) 37 | debugger-ruby_core_source (1.2.0) 38 | diff-lcs (1.1.3) 39 | i18n (0.5.0) 40 | jdbc-sqlite3 (3.7.2.1) 41 | jruby-openssl (0.8.2) 42 | bouncy-castle-java (>= 1.5.0146.1) 43 | json (1.7.5) 44 | json (1.7.5-java) 45 | rake (10.0.2) 46 | rdoc (3.12) 47 | json (~> 1.4) 48 | rspec (2.11.0) 49 | rspec-core (~> 2.11.0) 50 | rspec-expectations (~> 2.11.0) 51 | rspec-mocks (~> 2.11.0) 52 | rspec-core (2.11.1) 53 | rspec-expectations (2.11.3) 54 | diff-lcs (~> 1.1.3) 55 | rspec-mocks (2.11.3) 56 | sqlite3 (1.3.6) 57 | tzinfo (0.3.33) 58 | 59 | PLATFORMS 60 | java 61 | ruby 62 | 63 | DEPENDENCIES 64 | activerecord (~> 3.0.0) 65 | activerecord-jdbcsqlite3-adapter 66 | appraisal 67 | debugger 68 | jruby-openssl 69 | rake (>= 0.8.7) 70 | rdoc 71 | rspec (>= 2.3.0) 72 | safe_attributes! 73 | sqlite3 74 | -------------------------------------------------------------------------------- /gemfiles/rails_3_2.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: /Users/ge.unx.sas.com/vol/vol420/u42/cbjone/codebase/safe_attributes 3 | specs: 4 | safe_attributes (1.0.10) 5 | activerecord (>= 3.0.0) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | activemodel (3.2.8) 11 | activesupport (= 3.2.8) 12 | builder (~> 3.0.0) 13 | activerecord (3.2.8) 14 | activemodel (= 3.2.8) 15 | activesupport (= 3.2.8) 16 | arel (~> 3.0.2) 17 | tzinfo (~> 0.3.29) 18 | activerecord-jdbc-adapter (1.2.7) 19 | activerecord-jdbcsqlite3-adapter (1.2.7) 20 | activerecord-jdbc-adapter (~> 1.2.7) 21 | jdbc-sqlite3 (~> 3.7.2) 22 | activesupport (3.2.8) 23 | i18n (~> 0.6) 24 | multi_json (~> 1.0) 25 | appraisal (0.5.1) 26 | bundler 27 | rake 28 | arel (3.0.2) 29 | bouncy-castle-java (1.5.0146.1) 30 | builder (3.0.3) 31 | columnize (0.3.6) 32 | debugger (1.4.0) 33 | columnize (>= 0.3.1) 34 | debugger-linecache (~> 1.1.1) 35 | debugger-ruby_core_source (~> 1.2.0) 36 | debugger-linecache (1.1.2) 37 | debugger-ruby_core_source (>= 1.1.1) 38 | debugger-ruby_core_source (1.2.0) 39 | diff-lcs (1.1.3) 40 | i18n (0.6.1) 41 | jdbc-sqlite3 (3.7.2.1) 42 | jruby-openssl (0.8.2) 43 | bouncy-castle-java (>= 1.5.0146.1) 44 | json (1.7.5) 45 | json (1.7.5-java) 46 | multi_json (1.3.6) 47 | rake (10.0.2) 48 | rdoc (3.12) 49 | json (~> 1.4) 50 | rspec (2.11.0) 51 | rspec-core (~> 2.11.0) 52 | rspec-expectations (~> 2.11.0) 53 | rspec-mocks (~> 2.11.0) 54 | rspec-core (2.11.1) 55 | rspec-expectations (2.11.3) 56 | diff-lcs (~> 1.1.3) 57 | rspec-mocks (2.11.3) 58 | sqlite3 (1.3.6) 59 | tzinfo (0.3.33) 60 | 61 | PLATFORMS 62 | java 63 | ruby 64 | 65 | DEPENDENCIES 66 | activerecord (~> 3.2.0) 67 | activerecord-jdbcsqlite3-adapter 68 | appraisal 69 | debugger 70 | jruby-openssl 71 | rake (>= 0.8.7) 72 | rdoc 73 | rspec (>= 2.3.0) 74 | safe_attributes! 75 | sqlite3 76 | -------------------------------------------------------------------------------- /gemfiles/rails_3_1.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: /Users/ge.unx.sas.com/vol/vol420/u42/cbjone/codebase/safe_attributes 3 | specs: 4 | safe_attributes (1.0.10) 5 | activerecord (>= 3.0.0) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | activemodel (3.1.8) 11 | activesupport (= 3.1.8) 12 | builder (~> 3.0.0) 13 | i18n (~> 0.6) 14 | activerecord (3.1.8) 15 | activemodel (= 3.1.8) 16 | activesupport (= 3.1.8) 17 | arel (~> 2.2.3) 18 | tzinfo (~> 0.3.29) 19 | activerecord-jdbc-adapter (1.2.7) 20 | activerecord-jdbcsqlite3-adapter (1.2.7) 21 | activerecord-jdbc-adapter (~> 1.2.7) 22 | jdbc-sqlite3 (~> 3.7.2) 23 | activesupport (3.1.8) 24 | multi_json (>= 1.0, < 1.3) 25 | appraisal (0.5.1) 26 | bundler 27 | rake 28 | arel (2.2.3) 29 | bouncy-castle-java (1.5.0146.1) 30 | builder (3.0.3) 31 | columnize (0.3.6) 32 | debugger (1.2.2) 33 | columnize (>= 0.3.1) 34 | debugger-linecache (~> 1.1.1) 35 | debugger-ruby_core_source (~> 1.1.5) 36 | debugger-linecache (1.1.2) 37 | debugger-ruby_core_source (>= 1.1.1) 38 | debugger-ruby_core_source (1.1.5) 39 | diff-lcs (1.1.3) 40 | i18n (0.6.1) 41 | jdbc-sqlite3 (3.7.2.1) 42 | jruby-openssl (0.8.2) 43 | bouncy-castle-java (>= 1.5.0146.1) 44 | json (1.7.5) 45 | json (1.7.5-java) 46 | multi_json (1.2.0) 47 | rake (10.0.2) 48 | rdoc (3.12) 49 | json (~> 1.4) 50 | rspec (2.11.0) 51 | rspec-core (~> 2.11.0) 52 | rspec-expectations (~> 2.11.0) 53 | rspec-mocks (~> 2.11.0) 54 | rspec-core (2.11.1) 55 | rspec-expectations (2.11.3) 56 | diff-lcs (~> 1.1.3) 57 | rspec-mocks (2.11.3) 58 | sqlite3 (1.3.6) 59 | tzinfo (0.3.33) 60 | 61 | PLATFORMS 62 | java 63 | ruby 64 | 65 | DEPENDENCIES 66 | activerecord (~> 3.1.0) 67 | activerecord-jdbcsqlite3-adapter 68 | appraisal 69 | debugger 70 | jruby-openssl 71 | rake (>= 0.8.7) 72 | rdoc 73 | rspec (>= 2.3.0) 74 | safe_attributes! 75 | sqlite3 76 | -------------------------------------------------------------------------------- /lib/safe_attributes/base.rb: -------------------------------------------------------------------------------- 1 | require 'active_support' 2 | 3 | module SafeAttributes 4 | module Base 5 | extend ActiveSupport::Concern 6 | 7 | module ClassMethods 8 | # 9 | # Within your model call this method once with a list of 10 | # methods matching either attribute() or attribute=() for 11 | # attributes (column names) you do not want to create the 12 | # the normal method for. You should not need to do this 13 | # but the option is there in case the default list is 14 | # inadequate. 15 | # 16 | def bad_attribute_names(*attrs) 17 | @bad_attribute_names ||= lambda { 18 | methods = Array.new(attrs.collect { |x| x.to_sym }) 19 | methods += ActiveRecord::Base.public_instance_methods.collect { |x| x.to_sym } 20 | methods += ActiveRecord::Base.protected_instance_methods.collect { |x| x.to_sym } 21 | methods += ActiveRecord::Base.private_instance_methods.collect { |x| x.to_sym } 22 | methods -= [:id] 23 | return methods 24 | }.call 25 | end 26 | 27 | # 28 | # Override the default implementation to not create the 29 | # attribute() method if that method name is in the list of 30 | # bad names 31 | # 32 | def define_method_attribute(attr_name) 33 | return if (bad_attribute_names.include?(attr_name.to_sym)) 34 | super(attr_name) 35 | end 36 | 37 | # 38 | # Override the default implementation to not create the 39 | # attribute= method if that method name is in the list of 40 | # bad names 41 | # 42 | def define_method_attribute=(attr_name) 43 | method = attr_name + '=' 44 | return if (bad_attribute_names.include?(method.to_sym)) 45 | super(attr_name) 46 | end 47 | 48 | def instance_method_already_implemented?(method_name) 49 | begin 50 | return super(method_name) 51 | rescue ActiveRecord::DangerousAttributeError 52 | return true 53 | end 54 | return false 55 | end 56 | end 57 | 58 | def read_attribute_for_validation(attr) 59 | if (self.attributes.include?(attr.to_s)) 60 | self[attr.to_sym] 61 | else 62 | self.send(attr.to_s) if (self.respond_to?(attr.to_sym)) 63 | end 64 | end 65 | 66 | def attribute_change(attr) 67 | [changed_attributes[attr], read_attribute_for_validation(attr)] if attribute_changed?(attr) 68 | end 69 | 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = SafeAttributes 2 | 3 | By default Rails/ActiveRecord 3 creates attribute accessors for all 4 | database table columns in each model. Columns with specific names 5 | cause errors because they result in ActiveRecord redefining a key 6 | method within either Ruby or ActiveRecord in an incompatible way. A 7 | classic example is any table with a column named 'class', though there 8 | are other possible examples. Put simply, this gem makes it easier 9 | to support a legacy database schema with ActiveRecord. 10 | 11 | Using this gem enhances ActiveRecord to change the default behavior for 12 | the creation of attribute accessors. Instance methods in 13 | ActiveRecord::Base, except for 'id', are combined into a list. This list 14 | is checked before the creation of two types of attribute accessors: 15 | attribute() and attribute=(). 16 | 17 | You can add to this list by calling bad_attribute_names with a list of 18 | method names you do not want generated. Rails generates additional methods 19 | that this module does not prevent the creation of: 20 | 21 | * attribute_before_type_cast 22 | * attribute_changed? 23 | * attribute_was 24 | * attribute_will_change! 25 | * attribute? 26 | 27 | These largely should not run afoul of Ruby or ActiveRecord in most cases. 28 | 29 | == Accessing Attributes 30 | 31 | To access an attribute in ActiveRecord without its normal getter or setter 32 | you can use a couple of different approaches. 33 | 34 | * \model_instance[:attribute] # works as both getter and setter like a hash 35 | * model_instance.read_attribute('attribute') 36 | * model_instance.write_attribute('attribute', value) 37 | 38 | You can read more about reserved words[http://oldwiki.rubyonrails.org/rails/pages/ReservedWords] and magic field names[http://oldwiki.rubyonrails.org/rails/pages/MagicFieldNames] on Rails' wiki pages. 39 | 40 | == Validations 41 | 42 | By including safe_attributes, an instance method read_attribute_for_validation 43 | is defined in a way that will work for all attributes instead of the 44 | default implementation that relies upon the default accessors. In other 45 | words, `validates_presence_of :class' will work as of version 1.0.3. 46 | 47 | == Installing 48 | 49 | gem install safe_attributes 50 | 51 | If you do not have the newest version of ActiveRecord, rubygems will 52 | attempt to install it for you. This can result in an error like below. 53 | 54 | ERROR: Error installing safe_attributes: 55 | activemodel requires activesupport (= 3.0.3, runtime) 56 | 57 | You can use this gem with activerecord and activesupport >= 3.0. 58 | If you already have an appropriate version of activerecord and 59 | activesupport installed then use --ignore-dependencies to avoid this 60 | error. If you have the latest version already then the gem should install 61 | without issue using the recommended approach above. 62 | 63 | == Using 64 | 65 | === Ruby 66 | 67 | This gem has been tested with: 68 | 69 | * Ruby Enterprise Edition 1.8.7 2012-02-08 70 | * Ruby 1.9.3p374 71 | * JRuby 1.7.2 72 | 73 | === Rails 74 | Add safe_attributes to your Gemfile. 75 | 76 | gem 'safe_attributes' 77 | 78 | SafeAttributes is included into ActiveRecord::Base automatically. While 79 | nothing else should be necessary, you can still add to the list of bad 80 | attributes if you find it necessary. This list is a list of method names 81 | not to generate. 82 | 83 | class MyModel < ActiveRecord::Base 84 | bad_attribute_names :my_attr 85 | validates_presence_of :my_attr 86 | end 87 | 88 | If you do not want SafeAttributes to be automatically included into 89 | ActiveRecord::Base, then instead add this to your Gemfile. 90 | 91 | gem 'safe_attributes', :require => 'safe_attributes/base' 92 | 93 | You will then need to include the SafeAttributes::Base module into any 94 | model you wish to have this functionality. 95 | 96 | === Outside of Rails 97 | require 'safe_attributes/base' 98 | class MyModel < ActiveRecord::Base 99 | include SafeAttributes::Base 100 | end 101 | 102 | == Caveats 103 | 104 | It is virtually impossible to have a column named 'attribute' in your 105 | schema when using ActiveRecord. After spending some time trying to 106 | make it work I've come to the conclusion the only way to support this 107 | will be to change the tactic I'm using entirely, and likely to 108 | get the developers of ActiveRecord to agree to a patch. 109 | 110 | == Note on Patches/Pull Requests 111 | 112 | * Fork the project. 113 | * Make your feature addition or bug fix. 114 | * Add tests for it. This is important so I don't break it in a 115 | future version unintentionally. 116 | * Commit, do not mess with rakefile, version, or history. 117 | (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull) 118 | * Send me a pull request. Bonus points for topic branches. 119 | 120 | == Copyright 121 | 122 | Copyright (c) 2010,2011,2012,2013 C. Brian Jones. See LICENSE for details. 123 | 124 | == Thanks 125 | 126 | * Jaime Bellmyer - http://kconrails.com 127 | * James Brennan 128 | * Billy Watson 129 | * Jean Boussier 130 | 131 | -------------------------------------------------------------------------------- /spec/safe_attributes/safe_attributes_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 2 | 3 | # create the table for the test in the database 4 | ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS 'my_models'") 5 | ActiveRecord::Base.connection.create_table(:my_models) do |t| 6 | t.string :class 7 | t.string :bad_attribute 8 | t.string :good_attribute 9 | # bad because of changed and changed? in ActiveModel 10 | t.string :changed 11 | # bad because normally it generates DangerousAttributeError 12 | t.string :errors 13 | # support hyphenated column names 14 | t.string :"comment-frequency" 15 | # Issue #8 16 | t.string :association 17 | end 18 | 19 | class MyModel < ActiveRecord::Base 20 | include SafeAttributes 21 | bad_attribute_names :bad_attribute, :bad_attribute= 22 | validates_presence_of :class 23 | end 24 | 25 | ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS 'my_users'") 26 | ActiveRecord::Base.connection.create_table(:my_users) do |t| 27 | t.string :encrypted_password 28 | end 29 | 30 | class MyUser < ActiveRecord::Base 31 | include SafeAttributes::Base 32 | attr_reader :password 33 | def password=(p) 34 | @password = p 35 | self.encrypted_password = p 36 | end 37 | 38 | validates_presence_of :password 39 | end 40 | 41 | describe "models" do 42 | 43 | before(:each) do 44 | ActiveRecord::Base.connection.increment_open_transactions 45 | ActiveRecord::Base.connection.begin_db_transaction 46 | @model = MyModel.new 47 | end 48 | 49 | after(:each) do 50 | ActiveRecord::Base.connection.rollback_db_transaction 51 | ActiveRecord::Base.connection.decrement_open_transactions 52 | end 53 | 54 | it "inspecting class returns expected attribute names" do 55 | MyModel.inspect.should match 'class: string' 56 | end 57 | 58 | it "inspecting instance returns expected attribute names" do 59 | @model.inspect.should match 'class: nil' 60 | end 61 | 62 | it "does not redefine class()" do 63 | @model.class.name.should == 'MyModel' 64 | end 65 | 66 | it "defines class=()" do 67 | @model.respond_to?(:class=) # to force method generation 68 | (@model.methods.include?('class=') || @model.methods.include?(:class=)).should be_true 69 | end 70 | 71 | it "does not define bad_attribute()" do 72 | @model.respond_to?(:bad_attribute) # to force method generation 73 | (@model.methods.include?('bad_attribute') || @model.methods.include?(:bad_attribute)).should be_false 74 | end 75 | 76 | it "does not define bad_attribute=()" do 77 | @model.respond_to?(:bad_attribute=) # to force method generation 78 | (@model.methods.include?('bad_attribute=') || @model.methods.include?(:bad_attribute=)).should be_false 79 | end 80 | 81 | it "does define good_attribute()" do 82 | @model.respond_to?(:good_attribute) # to force method generation 83 | (@model.methods.include?('good_attribute') || @model.methods.include?(:good_attribute)).should be_true 84 | end 85 | 86 | it "does define good_attribute=()" do 87 | @model.respond_to?(:good_attribute=) # to force method generation 88 | (@model.methods.include?('good_attribute=') || @model.methods.include?(:good_attribute=)).should be_true 89 | end 90 | 91 | it "does define id()" do 92 | @model.respond_to?(:id) # to force method generation 93 | (@model.methods.include?('id') || @model.methods.include?(:id)).should be_true 94 | end 95 | 96 | it "can create instance in database with special attribute name" do 97 | m = MyModel.create!(:class => 'Foo') 98 | m = MyModel.find(m.id) 99 | m[:class].should == 'Foo' 100 | end 101 | 102 | it "can create instance in database with attribute 'changed'" do 103 | m = MyModel.create!(:class => 'Foo', :changed => 'true change') 104 | m = MyModel.find(m.id) 105 | m[:changed].should == 'true change' 106 | end 107 | 108 | it "can create instance in database with attribute 'errors'" do 109 | m = MyModel.create!(:class => 'Foo', :errors => 'my errors') 110 | m = MyModel.find(m.id) 111 | m[:errors].should == 'my errors' 112 | end 113 | 114 | it "can create instance in database with attribute 'comment-frequency'" do 115 | m = MyModel.create!(:class => 'Foo', :"comment-frequency" => 'often') 116 | m = MyModel.find(m.id) 117 | m[:"comment-frequency"].should == 'often' 118 | end 119 | 120 | it "can create instance in database with attribute 'association'" do 121 | m = MyModel.new(:class => 'Foo') 122 | m[:association] = 'worker' 123 | m.save! 124 | m = MyModel.find(m.id) 125 | m[:association].should == 'worker' 126 | end 127 | 128 | it "has class attribute" do 129 | MyModel.new().has_attribute?('class').should be_true 130 | end 131 | 132 | it "can call class= without error" do 133 | m = MyModel.new() 134 | m.class = 'Foo' 135 | m[:class].should == 'Foo' 136 | end 137 | 138 | it "can use finders with attribute" do 139 | m = MyModel.find_all_by_class('Foo') 140 | m.size.should == 0 141 | end 142 | 143 | it "validates presence of bad attribute name" do 144 | @model.valid?.should be_false 145 | Array(@model.errors[:class]).include?("can't be blank").should be_true 146 | 147 | m = MyModel.new(:class => 'Foo') 148 | m.valid?.should be_true 149 | end 150 | 151 | it "changed lists attributes with unsaved changes" do 152 | m = MyModel.new(:class => 'Foo') 153 | m.changed.size == 1 154 | end 155 | 156 | it "changed? returns true if any attribute has unsaved changes" do 157 | m = MyModel.new(:class => 'Foo') 158 | m.changed?.should be_true 159 | end 160 | 161 | it "validates presence of non-attribute" do 162 | m = MyUser.new() 163 | m.valid?.should be_false 164 | m = MyUser.new({:password => 'foobar'}) 165 | m.valid?.should be_true 166 | end 167 | 168 | describe "dirty" do 169 | it "new record - no changes" do 170 | @model.class_changed?.should be_false 171 | @model.class_change.should be_nil 172 | end 173 | 174 | it "changed record - has changes" do 175 | @model[:class] = 'arrr' 176 | @model.class_changed?.should be_true 177 | @model.class_was.should be_nil 178 | @model.class_change.should == [nil, 'arrr'] 179 | end 180 | 181 | it "saved record - no changes" do 182 | @model[:class] = 'arrr' 183 | @model.save! 184 | @model.class_changed?.should be_false 185 | @model.class_change.should be_nil 186 | end 187 | end 188 | 189 | end 190 | 191 | --------------------------------------------------------------------------------