Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Don't.

 Don't.

We all know that intelligent species learn from mistakes, right?

There’s just one problem: Life is too short to make every possible mistake! Also, some mistakes are more costly than others. What’s the ambitious learner to do? I’m glad you asked! I’m an expert at making mistakes, and I’m happy to share as many of them as I can possibly fit into a 45-minute time slot with you, my dear conference attendee!

We’ll discuss a variety of exciting mistakes, ranging from the misapplication of metaprogramming techniques to letting emotions become barriers to new technologies to why it’s a horrible idea to stretch a gel keyboard wrist rest across a room, even if it seems like an awesome plan at the time.

Ernie Miller

March 20, 2014
Tweet

More Decks by Ernie Miller

Other Decks in Programming

Transcript

  1. Rules for Class Macros What, not how! Idempotent! Order independent!

    Straightforward! Free of branching 1 2 3 4 5
  2. def self.included(base) base.extend ClassMethods ! base.class_eval do class_attribute :_metasearch_include_attributes, :_metasearch_exclude_attributes

    class_attribute :_metasearch_include_associations, :_metasearch_exclude_associations class_attribute :_metasearch_methods self._metasearch_include_attributes = self._metasearch_exclude_attributes = self._metasearch_exclude_associations = self._metasearch_include_associations = {} self._metasearch_methods = {} end end
  3. def attr_unsearchable(*args) if table_exists? opts = args.extract_options! args.flatten.each do |attr|

    attr = attr.to_s unless self.columns_hash.has_key?(attr) raise(ArgumentError, "No persisted attribute (column) named #{attr} in #{self}”) end self._metasearch_exclude_attributes = self._metasearch_exclude_attributes.merge( attr => { :if => opts[:if] } ) end end end
  4. def attr_searchable(*args) if table_exists? opts = args.extract_options! args.flatten.each do |attr|

    attr = attr.to_s unless self.columns_hash.has_key?(attr) raise(ArgumentError, "No persisted attribute (column) named #{attr} in #{self}”) end self._metasearch_include_attributes = self._metasearch_include_attributes.merge( attr => { :if => opts[:if] } ) end end end
  5. def assoc_unsearchable(*args) opts = args.extract_options! args.flatten.each do |assoc| assoc =

    assoc.to_s unless self.reflect_on_all_associations.map {|a| a.name.to_s}.include?(assoc) raise(ArgumentError, "No such association #{assoc} in #{self}”) end self._metasearch_exclude_associations = self._metasearch_exclude_associations.merge( assoc => { :if => opts[:if] } ) end end
  6. def assoc_searchable(*args) opts = args.extract_options! args.flatten.each do |assoc| assoc =

    assoc.to_s unless self.reflect_on_all_associations.map {|a| a.name.to_s}.include?(assoc) raise(ArgumentError, "No such association #{assoc} in #{self}”) end self._metasearch_include_associations = self._metasearch_include_associations.merge( assoc => { :if => opts[:if] } ) end end
  7. def ransackable_attributes(auth_object = nil) column_names + _ransackers.keys end ! def

    ransackable_associations(auth_object = nil) reflect_on_all_associations.map { |a| a.name.to_s } end
  8. YOU CAN NOT HAS BUCKET OF METHODS if Class <

    Module puts 'That goes for you too, Class.' end
  9. module MetaSearch module Utility #:nodoc: ! TRUE_VALUES = [true, 1,

    '1', 't', 'T', 'true', 'TRUE'].to_set FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE'].to_set ! private ! def preferred_method_name(method_id) method_name = method_id.to_s where = Where.new(method_name) rescue nil return nil unless where where.aliases.each do |a| break if method_name.sub!(/#{a}(=?)$/, "#{where.name}\\1") end method_name end ! def array_of_strings?(o) o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)} end ! def array_of_arrays?(vals) vals.is_a?(Array) && vals.first.is_a?(Array) end ! def array_of_dates?(vals) vals.is_a?(Array) && vals.first.respond_to?(:to_time) end ! def cast_attributes(type, vals) if array_of_arrays?(vals) vals.map! {|v| cast_attributes(type, v)} # Need to make sure not to kill multiparam dates/times elsif vals.is_a?(Array) && (array_of_dates?(vals) || !(DATES+TIMES).include?(type)) vals.map! {|v| cast_attribute(type, v)} else cast_attribute(type, vals)
  10. when :integer val.blank? ? nil : val.to_i when :float val.blank?

    ? nil : val.to_f when :decimal if val.blank? nil elsif val.class == BigDecimal val elsif val.respond_to?(:to_d) val.to_d else val.to_s.to_d end else raise TypeCastError, "Unable to cast columns of type #{type}" end end ! def collapse_multiparameter_options(opts) opts.keys.each do |k| if k.include?("(") real_attribute, position = k.split(/\(|\)/) cast = %w(a s i).include?(position.last) ? position.last : nil position = position.to_i - 1 value = opts.delete(k) opts[real_attribute] ||= [] opts[real_attribute][position] = if cast (value.blank? && cast == 'i') ? nil : value.send("to_#{cast}") else value end end end opts end end end
  11. require 'delegate' ! class Auditor < SimpleDelegator attr_reader :user, :record

    ! def initialize(user, record) @user, @record = user, record super(record) end ! def class @record.class end ! def update_attributes(attributes, options = {}) with_transaction_returning_status do assign_attributes(attributes, options) save end end ! def save(*) action = @record.persisted? ? 'update' : 'create' super.tap { |success| success && log_action(action) } end ! def destroy super.tap { |record| record && log_action('destroy') } end ! # ... ! end
  12. require 'delegate' ! class Auditor < SimpleDelegator attr_reader :user, :record

    ! def initialize(user, record) @user, @record = user, record super(record) end ! def class @record.class end ! def update_attributes(attributes, options = {}) with_transaction_returning_status do assign_attributes(attributes, options) save end end ! def save(*) action = @record.persisted? ? 'update' : 'create' super.tap { |success| success && log_action(action) } end ! def destroy super.tap { |record| record && log_action('destroy') } end ! # ... ! end
  13. def render(&block) options = @options.stringify_keys tag_value = options.delete("value") name_and_id =

    options.dup ! if name_and_id["for"] name_and_id["id"] = name_and_id["for"] else name_and_id.delete("id") end ! add_default_name_and_id_for_value(tag_value, name_and_id) options.delete("index") options.delete("namespace") options["for"] = name_and_id["id"] unless options.key?("for") ! if block_given? content = @template_object.capture(&block) else content = if @content.blank? @object_name.gsub!(/\[(.*)_attributes\]\[\d+\]/, '.\1') method_and_value = tag_value.present? ? "#{@method_name}.#{tag_value}" : @method_name ! if object.respond_to?(:to_model) key = object.class.model_name.i18n_key i18n_default = ["#{key}.#{method_and_value}".to_sym, ""] end ! i18n_default ||= "" I18n.t("#{@object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence else @content.to_s end ! content ||= if object && object.class.respond_to?(:human_attribute_name) object.class.human_attribute_name(@method_name) end ! content ||= @method_name.humanize end ! label_tag(name_and_id["id"], content, options) end action_view/helpers/tags/label.rb
  14. def render(&block) options = @options.stringify_keys tag_value = options.delete("value") name_and_id =

    options.dup ! if name_and_id["for"] name_and_id["id"] = name_and_id["for"] else name_and_id.delete("id") end ! add_default_name_and_id_for_value(tag_value, name_and_id) options.delete("index") options.delete("namespace") options["for"] = name_and_id["id"] unless options.key?("for") ! if block_given? content = @template_object.capture(&block) else content = if @content.blank? @object_name.gsub!(/\[(.*)_attributes\]\[\d+\]/, '.\1') method_and_value = tag_value.present? ? "#{@method_name}.#{tag_value}" : @method_name ! if object.respond_to?(:to_model) key = object.class.model_name.i18n_key i18n_default = ["#{key}.#{method_and_value}".to_sym, ""] end ! i18n_default ||= "" I18n.t("#{@object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence else @content.to_s end ! content ||= if object && object.class.respond_to?(:human_attribute_name) object.class.human_attribute_name(@method_name) end ! content ||= @method_name.humanize end ! label_tag(name_and_id["id"], content, options) end action_view/helpers/tags/label.rb
  15. class PostsController < ApplicationController ! # ... ! def edit

    @post = Auditor.new(current_user, Post.find(params[:id])) end ! # ... ! end <%= link_to 'Comments', post_comments_path(@post) %>
  16. class PostsController < ApplicationController ! # ... ! def edit

    @post = Auditor.new(current_user, Post.find(params[:id])) end ! # ... ! end <%= link_to 'Comments', post_comments_path(@post) %> /posts/1/comments/?post_id=1
  17. def generate key, name, options, recall = {}, parameterize =

    nil constraints = recall.merge options ! match_route(name, constraints) do |route| data = constraints.dup ! keys_to_keep = route.parts.reverse.drop_while { |part| !options.key?(part) || (options[part] || recall[part]).nil? } | route.required_parts ! (data.keys - keys_to_keep).each do |bad_key| data.delete bad_key end ! parameterized_parts = data.dup ! if parameterize parameterized_parts.each do |k,v| parameterized_parts[k] = parameterize.call(k, v) end end ! parameterized_parts.keep_if { |_,v| v } ! next if !name && route.requirements.empty? && route.parts.empty? ! next unless verify_required_parts!(route, parameterized_parts) ! z = Hash[options.to_a - data.to_a - route.defaults.to_a] ! return [route.format(parameterized_parts), z] end ! raise Router::RoutingError end
  18. def generate key, name, options, recall = {}, parameterize =

    nil constraints = recall.merge options ! match_route(name, constraints) do |route| data = constraints.dup ! keys_to_keep = route.parts.reverse.drop_while { |part| !options.key?(part) || (options[part] || recall[part]).nil? } | route.required_parts ! (data.keys - keys_to_keep).each do |bad_key| data.delete bad_key end ! parameterized_parts = data.dup ! if parameterize parameterized_parts.each do |k,v| parameterized_parts[k] = parameterize.call(k, v) end end ! parameterized_parts.keep_if { |_,v| v } ! next if !name && route.requirements.empty? && route.parts.empty? ! next unless verify_required_parts!(route, parameterized_parts) ! z = Hash[options.to_a - data.to_a - route.defaults.to_a] ! return [route.format(parameterized_parts), z] end ! raise Router::RoutingError end
  19. def eql?(other) self.class == other.class && self.user == other.user &&

    self.record == other.record end alias :== :eql?
  20. before_save!!! class User < ActiveRecord::Base def create_or_update say_my_name && super

    end ! def say_my_name puts "My name is #{name}"; true end end
  21. before_save!!! class User < ActiveRecord::Base def create_or_update say_my_name && super

    end ! def say_my_name puts "My name is #{name}"; true end end
  22. after_save!!! class User < ActiveRecord::Base def create_or_update super && final_stuff

    or raise ActiveRecord::Rollback end ! def final_stuff puts "IMPORTANT FINAL STUFF" end end
  23. after_save!!! class User < ActiveRecord::Base def create_or_update super && final_stuff

    or raise ActiveRecord::Rollback end ! def final_stuff puts "IMPORTANT FINAL STUFF" end end
  24. around_save!!! class User < ActiveRecord::Base def create_or_update puts "GET READY"

    unless sneak_attack? super puts "WASN'T THAT EXCITING?" if awesome? end end
  25. around_save!!! class User < ActiveRecord::Base def create_or_update puts "GET READY"

    unless sneak_attack? super puts "WASN'T THAT EXCITING?" if awesome? end end
  26. Employee.where(:slacker => true).values_of :first_name, :last_name, :hired_at # => [["Ernie", "Miller",

    2009-09-21 08:00:00 -0400], ...] ! class Animal < ActiveRecord::Base serialize :extra_info end ! Animal.where(:genus => 'felis').values_of :species, :extra_info # => [["catus", {:domestic => true}], ["lolcatus", {:can_has_cheezburger => true}], ...] Valium https://github.com/ernie/valium
  27. “I've had times where I would just fork off a

    child process, do all the memory-intensive stuff inside the child process (1000+ AR objects would be instantiated) and then kill off the child when it was finished to remove potential memory leaks.” — A commenter