ActiveRecord, Dirty Attributes and Michael Jackson

Brendan McIlhenny
6 min readNov 20, 2017

--

As a visual learner, I am always looking for better ways to digest what it is I am learning. For some inexplainable reason, graphics, charts and pictures representing abstract things just kind of click for me. So I found myself poking around Flatiron School’s Learn.co youtube page where I happened upon an insanely awesome plugin for helping Developers visualize the complex database schemas they are constructing. Enter Rails ERD. Rails ERD, (ERD standing for entity relationship diagrams) uses Active Record's built-in listeners to display how your models are associated.

The instructions for installing Rails ERD are simple:

  • Install Graphviz 2.22+ with Homebrew run brew install graphviz in the console
  • Add gem 'rails-erd', require: false, group: :development to your application's Gemfile
  • Run bundle exec erd in the console

Once installed, simply run rake erd in your console and a pdf will be automatically generated showing you your entire database and its corresponding relationships in graphic form.

Looking to learn more? Check out the Rails ERD GitHub read me here.

Because we are learning complex concepts at such a rapid pace, it is almost expected that we students take some of the underlying tools we are using for granted in order to remain on schedule. Unfortunately for me, I find it super difficult to let these intellectual leaps of faith go. In order for me to feel confident in my programming abilities I try to force myself to leave no stone unturned. ActiveRecord’s library of built-in Ruby functions are no exception. At the beginning of the week I was taking the belongs_to and the has_many tags in your models for granted. I knew these phrases were doing something for me, but I did not have enough time to play around with the console to see if I could gain access to these methods. I know the documentation tells me all of them, but seeing them in the console seemed to be the only way to convince myself they are in fact there.

Luckily Rails has a living breathing sandbox aka the Rails console, which provides us the perfect environment to see for yourself what is being added when you provide our models with these ActiveRecord association labels. Let’s get into how we can actually see what methods are being built and provided for us when we use these different ActiveRecord associations.

Now an object in general in Ruby has 94 (give or take)methods on it.

How can I prove this?

If you enter into the Rails console you can actually generate a new object class and call the method .methods on it like so:

2.3.1 :001 > class MichaelJackson2.3.1 :002?>   end=> nil2.3.1 :003 > michaeljackson = MichaelJackson.new=> #<MichaelJackson:0x007fda3986f6d0>2.3.1 :004 > michaelJackson.methods=> [:to_json, :`, :to_yaml, :to_yaml_properties, :presence, :blank?, :present?, :psych_to_yaml, :as_json, :acts_like?, :to_param, :to_query, :deep_dup, :duplicable?, :in?, :presence_in, :instance_values, :instance_variable_names, :with_options, :html_safe?, :lm, :lim, :pretty_print, :pretty_print_cycle, :pretty_print_instance_variables, :pretty_print_inspect, :require_dependency, :unloadable, :require_or_load, :load_dependency, :try, :try!, :instance_of?, :public_send, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :private_methods, :kind_of?, :instance_variables, :tap, :define_singleton_method, :is_a?, :public_method, :extend, :class_eval, :singleton_method, :to_enum, :enum_for, :byebug, :debugger, :pretty_inspect, :<=>, :===, :=~, :!~, :eql?, :respond_to?, :freeze, :inspect, :display, :object_id, :send, :suppress_warnings, :gem, :to_s, :method, :nil?, :hash, :class, :singleton_class, :clone, :dup, :itself, :taint, :tainted?, :untaint, :untrust, :trust, :untrusted?, :methods, :protected_methods, :frozen?, :public_methods, :singleton_methods, :!, :==, :!=, :__send__, :equal?, :instance_eval, :instance_exec, :__id__]2.3.1 :005 > michaelJackson.methods.size=> 94

So, this means an object in Ruby has 94 methods on it. What happens when we create a class that inherits from the ActiveRecord::Base library?

2.3.1 :001 > michaeljackson = MichaelJackson.new=> #<MichaelJackson id: nil, name: nil, created_at: nil, updated_at: nil>2.3.1 :002 > michaeljackson.methods=> [:created_at_previously_changed?, :created_at_was, :restore_created_at!, :created_at_previous_change, :saved_change_to_created_at, :saved_change_to_created_at?, :will_save_change_to_created_at?, :created_at_before_last_save, :created_at_in_database, :created_at_change_to_be_saved, :updated_at=, :name, :updated_at_came_from_user?, :updated_at_before_type_cast, :updated_at_changed?, :updated_at?, :updated_at_will_change!, :updated_at_change, :updated_at_previously_changed?, :updated_at_was, :restore_updated_at!, :updated_at_previous_change, :saved_change_to_updated_at, :saved_change_to_updated_at?, :will_save_change_to_updated_at?, :updated_at_before_last_save, :updated_at_in_database, :updated_at_change_to_be_saved, :id_came_from_user?, :id_changed?, :id_change, :id_will_change!, :id_previously_changed?, :id_previous_change, :restore_id!, :saved_change_to_id?, :saved_change_to_id, :id_before_last_save, :will_save_change_to_id?, :id_change_to_be_saved, :name_before_type_cast, :name_came_from_user?, :name?, :name_changed?, :name_change, :name_will_change!, :name_was, :created_at, :name_previously_changed?, :name_previous_change, :restore_name!, :saved_change_to_name?, :saved_change_to_name, :name_before_last_save, :will_save_change_to_name?, :name_change_to_be_saved, :name=, :updated_at, :name_in_database, :created_at_before_type_cast, :created_at=, :created_at?, :created_at_came_from_user?, :created_at_will_change!, :created_at_changed?, :created_at_change, :__callbacks?, :record_timestamps?, :_run_validation_callbacks, :column_for_attribute, :type_for_attribute, :table_name_prefix, :timestamped_migrations, :logger, :_validate_callbacks, :_validators?, :attribute_aliases, :attribute_method_matchers, :index_nested_attribute_errors, :_reflections, :primary_key_prefix_type, :_reflections?, :aggregate_reflections?, :include_root_in_json, :include_root_in_json?, :pluralize_table_names?, :lock_optimistically, :defined_enums, :time_zone_aware_attributes, :default_timezone, :lock_optimistically?, :_validation_callbacks, :_initialize_callbacks, :warn_on_records_fetched_greater_than, :defined_enums?, :_find_callbacks, :_touch_callbacks, :_run_touch_callbacks, :_run_save_callbacks, :_save_callbacks, :cache_timestamp_format, :table_name_prefix?, :schema_format, :table_name_suffix?, :error_on_ignored_order, :_run_create_callbacks, :error_on_ignored_order_or_limit, :store_full_sti_class, :_create_callbacks, :dump_schema_after_migration, :_run_update_callbacks, :dump_schemas, :_update_callbacks, :nested_attributes_options, :default_connection_handler, :_run_destroy_callbacks, :_destroy_callbacks, :attribute_aliases?, :attribute_method_matchers?, :store_full_sti_class?, :default_scopes, :default_scope_override, :__callbacks, :_run_initialize_callbacks, :_run_find_callbacks, :nested_attributes_options?, :skip_time_zone_conversion_for_attributes, :time_zone_aware_types, :skip_time_zone_conversion_for_attributes?, :time_zone_aware_types?, :partial_writes, :pluralize_table_names, :_run_before_commit_without_transaction_enrollment_callbacks, :_run_before_commit_callbacks, :_run_commit_without_transaction_enrollment_callbacks, :default_connection_handler?, :cache_timestamp_format?, :table_name_suffix, :_run_commit_callbacks, :_run_rollback_callbacks, :_run_rollback_without_transaction_enrollment_callbacks, :validation_context, :_validators, :_rollback_callbacks, :_commit_callbacks, :_before_commit_callbacks, :_run_validate_callbacks, :partial_writes?, :_before_commit_without_transaction_enrollment_callbacks, :_commit_without_transaction_enrollment_callbacks, :model_name, :_rollback_without_transaction_enrollment_callbacks, :record_timestamps, :record_timestamps=, :aggregate_reflections, :to_gid_param, :to_global_id, :to_gid, :to_signed_global_id, :to_sgid, :to_sgid_param, :save, :save!, :serializable_hash, :as_json, :from_json, :read_attribute_for_serialization, :touch, :touch_later, :no_touching?, :transaction, :destroy, :with_transaction_returning_status, :rollback_active_record_state!, :before_committed!, :committed!, :rolledback!, :add_to_transaction, :reload, :_destroy, :marked_for_destruction?, :destroyed_by_association, :mark_for_destruction, :destroyed_by_association=, :changed_for_autosave?, :association, :association_cached?, :changed, :changes, :changed?, :has_changes_to_save?, :raw_write_attribute, :attribute_in_database, :clear_attribute_changes, :attribute_changed_in_place?, :attribute_was, :changes_internally_applied, :changes_applied, :clear_changes_information, :changed_attributes, :previous_changes, :saved_change_to_attribute?, :saved_change_to_attribute, :attribute_before_last_save, :saved_changes?, :saved_changes, :will_save_change_to_attribute?, :attribute_change_to_be_saved, :changes_to_save, :changed_attribute_names_to_save, :attributes_in_database, :attribute_change, :attribute_changed?, :attributes_changed_by_setter, :attribute_previously_changed?, :restore_attributes, :id, :id=, :to_key, :id_in_database, :id?, :id_before_type_cast, :id_was, :query_attribute, :read_attribute_before_type_cast, :attributes_before_type_cast, :write_attribute, :read_attribute, :_read_attribute, :[], :[]=, :respond_to?, :attribute_names, :attributes, :has_attribute?, :attribute_for_inspect, :attribute_present?, :accessed_fields, :method_missing, :respond_to_without_attributes?, :attribute_missing, :lock!, :with_lock, :locking_enabled?, :valid?, :validate, :validates_length_of, :validates_confirmation_of, :validates_inclusion_of, :validates_absence_of, :validates_numericality_of, :validates_exclusion_of, :validates_format_of, :validates_presence_of, :validates_size_of, :validates_acceptance_of, :run_callbacks, :errors, :validate!, :read_attribute_for_validation, :invalid?, :validates_with, :to_param, :cache_key, :to_partial_path, :to_model, :attributes=, :assign_attributes, :quoted_id, :populate_with_current_scope_attributes, :initialize_internals_callback, :delete, :update, :increment, :decrement, :persisted?, :update!, :new_record?, :destroyed?, :destroy!, :becomes, :becomes!, :update_attribute, :update_attributes, :update_attributes!, :update_column, :update_columns, :increment!, :decrement!, :toggle, :toggle!, :<=>, :==, :eql?, :freeze, :inspect, :hash, :frozen?, :slice, :encode_with, :init_with, :pretty_print, :readonly?, :connection_handler, :readonly!, :to_json, :`, :to_yaml, :to_yaml_properties, :presence, :blank?, :present?, :psych_to_yaml, :acts_like?, :to_query, :deep_dup, :duplicable?, :in?, :presence_in, :instance_values, :instance_variable_names, :with_options, :html_safe?, :lm, :lim, :pretty_print_cycle, :pretty_print_instance_variables, :pretty_print_inspect, :require_dependency, :unloadable, :require_or_load, :load_dependency, :try, :try!, :instance_of?, :public_send, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :private_methods, :kind_of?, :instance_variables, :tap, :define_singleton_method, :is_a?, :public_method, :extend, :class_eval, :singleton_method, :to_enum, :enum_for, :byebug, :debugger, :pretty_inspect, :===, :=~, :!~, :display, :object_id, :send, :suppress_warnings, :gem, :to_s, :method, :nil?, :class, :singleton_class, :clone, :dup, :itself, :taint, :tainted?, :untaint, :untrust, :trust, :untrusted?, :methods, :protected_methods, :public_methods, :singleton_methods, :!, :!=, :__send__, :equal?, :instance_eval, :instance_exec, :__id__]2.3.1 :003 > michaeljackson.methods.size=> 388

Wow. 388–94 =ActiveRecord added 294 new methods to it.

Let’s try one out:

michaeljackson.attribute_names=> [“id”, “name”, “created_at”, “updated_at”]

What if I told you that has_many is just a label for giving your MichaelJackson class access to 18 more methods? Let’s test it out. First, I’ll add a has_many to the MichaelJackson model.

class MichaelJackson < ActiveRecord::Base
has_many :shinypants
end

Then I will call .methods.size on a new instance of MichaelJackson.

2.3.1 :002 > michaeljackson = MichaelJackson.new=> #<MichaelJackson id: nil, name: nil, created_at: nil, updated_at: nil>2.3.1 :003 > michaeljackson.methods.size=> 406

Magic!

If we really want to go down the rabbit whole there is a whole new concept introduced by ActiveRecord known as Dirty Attributes.

Let’s say we’re back in the console and we instantiate a new instance of MichaelJackson.

mj2 = MichaelJackson.new("Michael Jackson")
mj2.name = 'Tito Jackson'
mj2.changed? # => true
mj2.name_changed? # => true
mj2.name_was # => 'Michael Jackson'
mj2.name_change # => ['Michael Jackson', 'Tito Jackson'
mj2.name = 'Jermaine Jackson'
mj2.name_change # => ['Tito Jackson', 'Jermaine Jackson']

However, once you have saved these changes to your database these objects are no longer considered dirty.

mj2.save
mj2.changed? # => false
mj2.name_changed? # => false

Now, what are the rest of these ActiveRecord methods? I have no clue, and I expect we will be touching on a few of these methods this week. That being said, being able to see a physical list of new methods being added to class MichaelJackson makes me feel a hell of a lot better. I can now look at that Man In The Mirror and tell him that he’s the kinda guy who understands ActiveRecord a lil bit more than the rest.

--

--

Brendan McIlhenny

Beijing儿, 冲浪er, Full Stack Developer, I’d rather be in New Orleans-er