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

RailsConf 2015 - Breaking Down the Barrier: Demystifying Contributing to Rails

RailsConf 2015 - Breaking Down the Barrier: Demystifying Contributing to Rails

http://confreaks.tv/videos/railsconf2015-breaking-down-the-barrier-demystifying-contributing-to-rails

Contributing to Rails for the first time can be terrifying. In this lab I’ll make contributing to Rails more approachable by going over the contributing guidelines and technical details you need to know. We’ll walk through traversing the source code with tools such as CTags, source_location and TracePoint. Additionally, we’ll create reproduction scripts for reporting issues and learn advanced git commands like bisect and squash. At the end of this session you’ll have the confidence to fix bugs and add features to Ruby on Rails.

Eileen M. Uchitelle

April 21, 2015
Tweet

More Decks by Eileen M. Uchitelle

Other Decks in Technology

Transcript

  1. CLONE THE REPO
    Grab a USB or clone the railsconf_scripts repo from
    https://github.com/eileencodes/railsconf_scripts.git
    $ cd railsconf_scripts
    # mac/linux users
    $ bundle install --local
    # windows users not using a linux VM
    $ git checkout windows
    $ bundle install --local

    View full-size slide

  2. BREAKING DOWN
    THE
    Demystifying Contributing
    to Ruby on Rails
    BARRIER:

    View full-size slide

  3. EILEEN M. UCHITELLE
    Programmer at Basecamp
    ! eileencodes.com
    " @eileencodes
    # @eileencodes

    View full-size slide

  4. Kingston, New York

    View full-size slide

  5. Arya
    @aryadog

    View full-size slide

  6. PREREQUISITES

    View full-size slide

  7. to Rails
    can be INTIMIDATING.
    CONTRIBUTING

    View full-size slide

  8. to Rails open source
    can be is INTIMIDATING.
    CONTRIBUTING #

    View full-size slide

  9. CLONE THE REPO
    Grab a USB or clone the railsconf_scripts repo from
    github.com/eileencodes/railsconf_scripts
    $ cd railsconf_scripts/
    # mac/linux users
    $ bundle install --local
    # windows users not using a linux VM
    $ git checkout windows
    $ bundle install --local

    View full-size slide

  10. ENVIRONMENT
    REQUIREMENTS

    View full-size slide

  11. ENVIRONMENT
    $ Ruby version manager (rbenv, rvm, chruby)
    $ Ruby 2.2.2

    View full-size slide

  12. % Ruby version manager (rbenv, rvm, chruby)
    % Ruby 2.2.2
    $ Databases for Active Record
    $ MySQL
    $ PostgreSQL
    $ SQLite3
    ENVIRONMENT

    View full-size slide

  13. % Ruby version manager (rbenv, rvm, chruby)
    % Ruby 2.2.2
    % Databases for Active Record
    % MySQL
    % PostgreSQL
    % SQLite3
    $ Git & a GitHub Account
    ENVIRONMENT

    View full-size slide

  14. RUNNING THE
    TEST SUITE

    View full-size slide

  15. RUNNING TESTS
    $ cd actionpack
    $ rake test
    Finished in 3.237796s, 861.0796 runs/
    s, 4508.3137 assertions/s.

    2788 runs, 14597 assertions, 0
    failures, 0 errors, 0 skips

    View full-size slide

  16. ACTIVE RECORD
    $ cd activerecord
    $ rake test:sqlite3
    $ rake test:mysql2
    $ rake test:mysql
    $ rake test:postgresql

    View full-size slide

  17. $ ruby -Ilib:test path/to/test_file.rb
    RUN A TEST FILE

    View full-size slide

  18. $ ruby -Ilib:test path/to/test_file.rb
    -n test_name_of_test
    RUN A SINGLE TEST

    View full-size slide

  19. RUN A SINGLE TEST
    $ ARCONN=mysql2 ruby -Ilib:test path/
    to/test_file.rb -n test_name_of_test

    View full-size slide

  20. $ bundle exec rake TEST=path/to/
    test_file.rb -n test_name
    RUN A SINGLE TEST

    View full-size slide

  21. $ cd activerecord/
    $ rake test:sqlite3
    $ ruby -Ilib:test test/cases/
    reflection_test.rb
    $ ARCONN=mysql2 ruby -Ilib:test test/
    cases/reflection_test.rb -n
    test_columns
    RUNNING TESTS

    View full-size slide

  22. GUIDELINES FOR
    OPENING AN ISSUE

    View full-size slide

  23. OPENING AN ISSUE
    $ Be clear and include the Rails version

    View full-size slide

  24. OPENING AN ISSUE
    % Be clear and include the Rails version
    $ Don’t open security issues on the issues tracker

    View full-size slide

  25. OPENING AN ISSUE
    % Be clear and include the Rails version
    % Don’t open security issues on the issues tracker
    $ Don’t open issues that aren’t a bug in Rails

    View full-size slide

  26. OPENING AN ISSUE
    % Be clear and include the Rails version
    % Don’t open security issues on the issues tracker
    % Don’t open issues that aren’t a bug in Rails
    $ Don’t open a separate issue if you have a PR

    View full-size slide

  27. OPENING AN ISSUE
    % Be clear and include the Rails version
    % Don’t open security issues on the issues tracker
    % Don’t open issues that aren’t a bug in Rails
    % Don’t open a separate issue if you have a PR
    $ Don’t open feature request tickets

    View full-size slide

  28. OPENING AN ISSUE
    % Be clear and include the Rails version
    % Don’t open security issues on the issues tracker
    % Don’t open issues that aren’t a bug in Rails
    % Don’t open a separate issue if you have a PR
    % Don’t open feature request tickets
    % Do include a test script or application

    View full-size slide

  29. Examples of executable scripts
    github.com/rails/rails/blob/master/
    guides/bug_report_templates
    TEST SCRIPTS

    View full-size slide

  30. $ cd railsconf_scripts/
    $ open ar_script_example.rb
    TEST SCRIPTS

    View full-size slide

  31. require 'active_record'
    require 'minitest/autorun'
    require 'logger'
    ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
    ActiveRecord::Base.logger = Logger.new(STDOUT)
    ActiveRecord::Schema.define do
    create_table :projects do |t|
    t.string :name
    end
    end
    class Project < ActiveRecord::Base
    validates_presence_of :name
    end
    class BugTest < Minitest::Test
    def test_assert
    project = Project.create!
    assert_not_operator project, :valid?
    end
    end

    View full-size slide

  32. require 'active_record'
    require 'minitest/autorun'
    require 'logger'
    ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
    ActiveRecord::Base.logger = Logger.new(STDOUT)
    ActiveRecord::Schema.define do
    create_table :projects do |t|
    t.string :name
    end
    end
    class Project < ActiveRecord::Base
    validates_presence_of :name
    end
    class BugTest < Minitest::Test
    def test_assert
    project = Project.create!
    assert_not_operator project, :valid?
    end
    end

    View full-size slide

  33. require 'active_record'
    require 'minitest/autorun'
    require 'logger'
    ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
    ActiveRecord::Base.logger = Logger.new(STDOUT)
    ActiveRecord::Schema.define do
    create_table :projects do |t|
    t.string :name
    end
    end
    class Project < ActiveRecord::Base
    validates_presence_of :name
    end
    class BugTest < Minitest::Test
    def test_assert
    project = Project.create!
    assert_not_operator project, :valid?
    end
    end

    View full-size slide

  34. require 'active_record'
    require 'minitest/autorun'
    require 'logger'
    ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
    ActiveRecord::Base.logger = Logger.new(STDOUT)
    ActiveRecord::Schema.define do
    create_table :projects do |t|
    t.string :name
    end
    end
    class Project < ActiveRecord::Base
    validates_presence_of :name
    end
    class BugTest < Minitest::Test
    def test_assert
    project = Project.create!
    assert_not_operator project, :valid?
    end
    end

    View full-size slide

  35. require 'active_record'
    require 'minitest/autorun'
    require 'logger'
    ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
    ActiveRecord::Base.logger = Logger.new(STDOUT)
    ActiveRecord::Schema.define do
    create_table :projects do |t|
    t.string :name
    end
    end
    class Project < ActiveRecord::Base
    validates_presence_of :name
    end
    class BugTest < Minitest::Test
    def test_assert
    project = Project.create!
    assert_not_operator project, :valid?
    end
    end

    View full-size slide

  36. require 'active_record'
    require 'minitest/autorun'
    require 'logger'
    ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
    ActiveRecord::Base.logger = Logger.new(STDOUT)
    ActiveRecord::Schema.define do
    create_table :projects do |t|
    t.string :name
    end
    end
    class Project < ActiveRecord::Base
    validates_presence_of :name
    end
    class BugTest < Minitest::Test
    def test_assert
    project = Project.create!
    assert_not_operator project, :valid?
    end
    end

    View full-size slide

  37. require 'active_record'
    require 'minitest/autorun'
    require 'logger'
    ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
    ActiveRecord::Base.logger = Logger.new(STDOUT)
    ActiveRecord::Schema.define do
    create_table :projects do |t|
    t.string :name
    end
    end
    class Project < ActiveRecord::Base
    validates_presence_of :name
    end
    class BugTest < Minitest::Test
    def test_assert
    project = Project.create!
    assert_not_operator project, :valid?
    end
    end

    View full-size slide

  38. TEST SCRIPTS
    $ cd railsconf_scripts/
    $ bundle exec ruby ar_script_example.rb

    View full-size slide

  39. GUIDELINES FOR
    OPENING A
    PULL REQUEST

    View full-size slide

  40. OPENING A PR
    $ Open PR’s against master

    View full-size slide

  41. OPENING A PR
    % Open PR’s against master
    $ We don’t accept “cosmetic changes”

    View full-size slide

  42. OPENING A PR
    % Open PR’s against master
    % We don’t accept “cosmetic changes”
    $ Write clear and descriptive commit messages

    View full-size slide

  43. Short (50 chars or less) summary of changes
    More detailed explanatory text, if necessary. Wrap it to
    about 72 characters or so. In some contexts, the first
    line is treated as the subject of an email and the rest
    of the text as the body. The blank line separating the
    summary from the body is critical (unless you omit
    the body entirely); tools like rebase can get confused
    if you run the two together.
    Further paragraphs come after blank lines.
    - Bullet points are okay, too
    - Typically a hyphen or asterisk is used for the
    bullet, preceded by a single space, with blank lines in
    between, but conventions vary here

    View full-size slide

  44. OPENING A PR
    % Open PR’s against master
    % We don’t accept “cosmetic changes”
    % Write clear and descriptive commit messages
    $ Be prepared to squash your commits

    View full-size slide

  45. OPENING A PR
    % Open PR’s against master
    % We don’t accept “cosmetic changes”
    % Write clear and descriptive commit messages
    % Be prepared to squash your commits
    $ Don’t ping constantly or litter PR’s with +1’s

    View full-size slide

  46. OPENING A PR
    % Open PR’s against master
    % We don’t accept “cosmetic changes”
    % Write clear and descriptive commit messages
    % Be prepared to squash your commits
    % Don’t ping constantly or litter PR’s with +1’s
    $ We <3 documentation PR’s

    View full-size slide

  47. commit 02d3a253610eaf9c80587913b366e3fa0f56b71f
    Author: eileencodes
    Date: Sat Oct 4 11:19:37 2014 -0400
    [ci skip] Clarify deletion strategies for collection
    proxies
    For detailed testing of behavior see:
    https://gist.github.com/eileencodes/
    5b0a2fe011dcff6203fe
    This shows destroy_all always destroys records and
    fires callbacks.
    It will never use nullify or delete_all
    delete_all's behavior varies greatly based on `hm`
    vs `hm:t` and deletion
    strategy.

    View full-size slide

  48. OPENING A PR
    % Open PR’s against master
    % We don’t accept “cosmetic changes”
    % Write clear and descriptive commit messages
    % Be prepared to squash your commits
    % Don’t ping constantly or litter PR’s with +1’s
    % We <3 documentation PR’s
    $ Keep it simple

    View full-size slide

  49. OPENING A PR
    % Open PR’s against master
    % We don’t accept “cosmetic changes”
    % Write clear and descriptive commit messages
    % Be prepared to squash your commits
    % Don’t ping constantly or litter PR’s with +1’s
    % We <3 documentation PR’s
    % Keep it simple
    $ Use Benchmark/ips

    View full-size slide

  50. $ cd railsconf_scripts/
    $ open benchmark_ips_example.rb
    BENCHMARK/IPS

    View full-size slide

  51. # railsconf_scripts/benchmark_ips_example.rb
    require 'benchmark/ips'
    ARRAY = (1..100).to_a
    def slow
    ARRAY.shuffle.first
    end
    def fast
    ARRAY.sample
    end
    Benchmark.ips do |x|
    x.report('slow') { slow }
    x.report('fast') { fast }
    x.compare!
    end

    View full-size slide

  52. # railsconf_scripts/benchmark_ips_example.rb
    require 'benchmark/ips'
    ARRAY = (1..100).to_a
    def slow
    ARRAY.shuffle.first
    end
    def fast
    ARRAY.sample
    end
    Benchmark.ips do |x|
    x.report('slow') { slow }
    x.report('fast') { fast }
    x.compare!
    end

    View full-size slide

  53. # railsconf_scripts/benchmark_ips_example.rb
    require 'benchmark/ips'
    ARRAY = (1..100).to_a
    def slow
    ARRAY.shuffle.first
    end
    def fast
    ARRAY.sample
    end
    Benchmark.ips do |x|
    x.report('slow') { slow }
    x.report('fast') { fast }
    x.compare!
    end

    View full-size slide

  54. # railsconf_scripts/benchmark_ips_example.rb
    require 'benchmark/ips'
    ARRAY = (1..100).to_a
    def slow
    ARRAY.shuffle.first
    end
    def fast
    ARRAY.sample
    end
    Benchmark.ips do |x|
    x.report('slow') { slow }
    x.report('fast') { fast }
    x.compare!
    end

    View full-size slide

  55. $ bundle exec ruby benchmark_ips_example.rb
    Calculating -------------------------------------
    slow 26.801k i/100ms
    fast 131.410k i/100ms
    -------------------------------------------------
    slow 343.594k (± 5.4%) i/s -
    1.715M
    fast 6.348M (±11.8%) i/s -
    31.276M
    Comparison:
    fast: 6347552.5 i/s
    slow: 343594.2 i/s - 18.47x slower

    View full-size slide

  56. $ cd railsconf_scripts/
    $ bundle exec ruby
    BENCHMARK/IPS
    benchmark_ips_example.rb

    View full-size slide

  57. OPENING A PR
    % Open PR’s against master
    % We don’t accept “cosmetic changes”
    % Write clear and descriptive commit messages
    % Be prepared to squash your commits
    % Don’t ping constantly or litter PR’s with +1’s
    % We <3 documentation PR’s
    % Keep it simple
    % Use Benchmark/ips
    $ Write tests

    View full-size slide

  58. WORKING WITH
    DEPRECATIONS

    View full-size slide

  59. # nodoc example from activerecord/lib/active_record/
    reflection.rb:588
    class HasManyReflection < AssociationReflection # :nodoc:
    def initialize(name, scope, options, active_record)
    super(name, scope, options, active_record)
    end
    def macro; :has_many; end
    def collection?; true; end
    end

    View full-size slide

  60. # Deprecation from activerecord/lib/active_record/tasks/
    # database_tasks.rb:207
    def load_schema_for(*args)
    ActiveSupport::Deprecation.warn(<<-MSG.squish)
    This method was renamed to `#load_schema` and will
    be removed in the future.
    Use `#load_schema` instead.
    MSG
    load_schema(*args)
    end

    View full-size slide

  61. TRAVERSING
    UNFAMILIAR CODE

    View full-size slide

  62. # actionview/lib/action_view/template/error.rb:118
    def source_location
    if line_number
    "on line ##{line_number} of "
    else
    'in '
    end + file_name
    end

    View full-size slide

  63. SOURCE LOCATION
    $ cd railsconf_scripts/
    $ open source_location.rb

    View full-size slide

  64. class Project < ActiveRecord::Base
    has_many :projects
    end
    class Comment < ActiveRecord::Base
    belongs_to :project
    end
    class BugTest < Minitest::Test
    def test_delete_all
    post = Post.create!(title: "Post title", content: "Lots of content")
    comment = Comment.create!(content: "I am a comment", post_id: post.id)
    assert 1, post.comments.count
    post.comments.delete_all
    assert 0, post.comments.count
    end
    end

    View full-size slide

  65. class Project < ActiveRecord::Base
    has_many :projects
    end
    class Comment < ActiveRecord::Base
    belongs_to :project
    end
    class BugTest < Minitest::Test
    def test_delete_all
    post = Post.create!(title: "Post title", content: "Lots of content")
    comment = Comment.create!(content: "I am a comment", post_id: post.id)
    assert 1, post.comments.count
    puts post.comments.method(:delete_all).source_location
    assert 0, post.comments.count
    end
    end

    View full-size slide

  66. SOURCE LOCATION
    $ cd railsconf_scripts/
    $ bundle exec ruby source_location.rb

    View full-size slide

  67. (0.0ms) begin transaction
    SQL (0.1ms) INSERT INTO "comments" ("content",
    "post_id") VALUES (?, ?) [["content", "I am a
    comment"], ["post_id", 1]]
    (0.0ms) commit transaction
    (0.1ms) SELECT COUNT(*) FROM "comments" WHERE
    "comments"."post_id" = ? [["post_id", 1]]
    .../activerecord/lib/active_record/associations/
    collection_proxy.rb
    442
    (0.0ms) SELECT COUNT(*) FROM "comments" WHERE
    "comments"."post_id" = ? [["post_id", 1]]

    View full-size slide

  68. # activerecord/lib/active_record/associations/
    collection_proxy.rb:442
    def delete_all(dependent = nil)
    @association.delete_all(dependent)
    end

    View full-size slide

  69. CTags
    # OS X
    $ brew install ctags
    # Debian-based linux
    $ sudo apt-get install exuberant-ctags
    # Red Hat-based linux
    $ sudo yum install ctags

    View full-size slide

  70. CTags
    $ cd path/to/rails/
    $ ctags -R .

    View full-size slide

  71. CTags
    $ cd path/to/rails/
    $ ctags -R -f .git/tags .

    View full-size slide

  72. CTags
    Sublime: github.com/SublimeText/CTags
    TextMate: github.com/textmate/
    ctags.tmbundle

    View full-size slide

  73. # .vimrc
    map rt :!ctags --tag-relative --extra=+f -
    Rf.git/tags --exclude=.git,pkg —languages=-
    javascript,sql
    set tags+=.git/tags

    View full-size slide

  74. CTags
    $ CTRL + ]

    View full-size slide

  75. CTags
    $ :ts
    $ N + ENTER

    View full-size slide

  76. # activerecord/lib/active_record/reflection.rb:314
    def association_primary_key(klass = nil)
    options[:primary_key] ||
    primary_key(klass || self.klass)
    end

    View full-size slide

  77. # activerecord/lib/active_record/reflection.rb:314
    def association_primary_key(klass = nil)
    puts caller
    options[:primary_key] ||
    primary_key(klass || self.klass)
    end

    View full-size slide

  78. .../through_association.rb:48:in `construct_join_attributes'
    .../has_many_through_association.rb:169:in `through_records_for'
    .../has_many_through_association.rb:180:in `block in delete_through_records'
    .../has_many_through_association.rb:179:in `each'
    .../has_many_through_association.rb:179:in `delete_through_records'
    .../has_many_through_association.rb:154:in `delete_records'
    .../has_many_through_association.rb:124:in `delete_or_nullify_all_records'
    .../collection_association.rb:214:in `delete_all'
    .../collection_proxy.rb:443:in `delete_all’
    ar_test_scripts/ar_rails_test.rb:85:in `'

    View full-size slide

  79. .../activerecord/lib/active_record/associations/
    through_association.rb:48:in `block in
    construct_join_attributes’

    View full-size slide

  80. def construct_join_attributes(*records)
    ensure_mutable
    if source_reflection.association_primary_key(reflection.klass) ==
    reflection.klass.primary_key
    join_attributes = { source_reflection.name => records }
    else
    join_attributes = {
    source_reflection.foreign_key =>
    records.map { |record|
    record.send(source_reflection.association_primary_key(
    reflection.klass))
    }
    }
    end
    if options[:source_type]
    join_attributes[source_reflection.foreign_type] =
    records.map { |record| record.class.base_class.name }
    [...]

    View full-size slide

  81. CALLER
    $ cd railsconf_scripts/
    $ open puts_caller.rb

    View full-size slide

  82. ActiveRecord::Schema.define do
    create_table :projects do |t|
    t.string :name
    end
    end
    class Project < ActiveRecord::Base
    after_create :call_me
    def call_me
    puts "======== i am a callback ========"
    end
    end
    class BugTest < Minitest::Test
    def test_create
    project = Project.create!(name: "whatever")
    end
    end

    View full-size slide

  83. create_table :projects do |t|
    t.string :name
    end
    end
    class Project < ActiveRecord::Base
    after_create :call_me
    def call_me
    puts caller
    puts "======== i am a callback ========"
    end
    end
    class BugTest < Minitest::Test
    def test_create
    project = Project.create!(name: "whatever")
    end
    end

    View full-size slide

  84. CALLER
    $ bundle exec ruby puts_caller.rb

    View full-size slide

  85. .../activesupport/lib/active_support/callbacks.rb:428:in `block in
    make_lambda'
    .../activesupport/lib/active_support/callbacks.rb:229:in `call'
    .../activesupport/lib/active_support/callbacks.rb:229:in `block in
    halting_and_conditional'
    .../activesupport/lib/active_support/callbacks.rb:502:in `call'
    .../activesupport/lib/active_support/callbacks.rb:502:in `block in call'
    .../activesupport/lib/active_support/callbacks.rb:502:in `each'
    .../activesupport/lib/active_support/callbacks.rb:502:in `call'
    .../activesupport/lib/active_support/callbacks.rb:90:in `run_callbacks'
    .../activerecord/lib/active_record/callbacks.rb:305:in `_create_record'
    .../activerecord/lib/active_record/timestamp.rb:57:in `_create_record'
    .../activerecord/lib/active_record/persistence.rb:506:in `create_or_update'
    .../activerecord/lib/active_record/callbacks.rb:301:in `block in
    create_or_update'
    .../activesupport/lib/active_support/callbacks.rb:86:in `run_callbacks'
    .../activerecord/lib/active_record/callbacks.rb:301:in `create_or_update'
    .../activerecord/lib/active_record/persistence.rb:151:in `save!'
    [...]

    View full-size slide

  86. .../activesupport/lib/active_support/callbacks.rb:
    428:in `block in make_lambda'

    View full-size slide

  87. def make_lambda(filter)
    case filter
    when Symbol
    lambda { |target, _, &blk| target.send filter, &blk }
    when String
    l = eval "lambda { |value| #{filter} }"
    lambda { |target, value| target.instance_exec(value, &l) }
    when Conditionals::Value then filter
    when ::Proc
    if filter.arity > 1
    return lambda { |target, _, &blk|
    raise ArgumentError unless block
    target.instance_exec(target, block, &filter)
    }
    end
    if filter.arity <= 0
    lambda { |target, _| target.instance_exec(&filter) }
    else
    lambda { |target, _| target.instance_exec(target, &filter) }
    [...]

    View full-size slide

  88. def test_reject_if_method_without_arguments
    Pirate.accepts_nested_attributes_for :ship,
    reject_if: :new_record?
    pirate = Pirate.new(catchphrase: "Stop wastin' me time")
    pirate.ship_attributes = { name: 'Black Pearl' }
    assert_no_difference('Ship.count') { pirate.save! }
    end

    View full-size slide

  89. def test_reject_if_method_without_arguments
    Pirate.accepts_nested_attributes_for :ship,
    reject_if: :new_record?
    pirate = Pirate.new(catchphrase: "Stop wastin' me time")
    tp = TracePoint.new(:call) do |*args|
    p args
    end
    tp.enable
    pirate.ship_attributes = { name: 'Black Pearl' }
    tp.disable
    assert_no_difference('Ship.count') { pirate.save! }
    end

    View full-size slide

  90. def test_reject_if_method_without_arguments
    Pirate.accepts_nested_attributes_for :ship,
    reject_if: :new_record?
    pirate = Pirate.new(catchphrase: "Stop wastin' me time")
    tp = TracePoint.new(:call) do |*args|
    p args
    end
    tp.enable
    pirate.ship_attributes = { name: 'Black Pearl' }
    tp.disable
    assert_no_difference('Ship.count') { pirate.save! }
    end

    View full-size slide

  91. def test_reject_if_method_without_arguments
    Pirate.accepts_nested_attributes_for :ship,
    reject_if: :new_record?
    pirate = Pirate.new(catchphrase: "Stop wastin' me time")
    tp = TracePoint.new(:call) do |*args|
    p args
    end
    tp.enable
    pirate.ship_attributes = { name: 'Black Pearl' }
    tp.disable
    assert_no_difference('Ship.count') { pirate.save! }
    end

    View full-size slide

  92. def test_reject_if_method_without_arguments
    Pirate.accepts_nested_attributes_for :ship,
    reject_if: :new_record?
    pirate = Pirate.new(catchphrase: "Stop wastin' me time")
    tp = TracePoint.new(:call) do |*args|
    p args
    end
    tp.enable
    pirate.ship_attributes = { name: 'Black Pearl' }
    tp.disable
    assert_no_difference('Ship.count') { pirate.save! }
    end

    View full-size slide

  93. def test_reject_if_method_without_arguments
    Pirate.accepts_nested_attributes_for :ship,
    reject_if: :new_record?
    pirate = Pirate.new(catchphrase: "Stop wastin' me time")
    tp = TracePoint.new(:call) do |*args|
    p args
    end
    tp.enable
    pirate.ship_attributes = { name: 'Black Pearl' }
    tp.disable
    assert_no_difference('Ship.count') { pirate.save! }
    end

    View full-size slide

  94. `ship_attributes='@.../activerecord/lib/active_record/nested_attributes.rb:347
    `assign_nested_attributes_for_one_to_one_association'@.../activerecord/lib/active_record/
    nested_attributes.rb:382
    `nested_attributes_options'@.../activesupport/lib/active_support/core_ext/class/attribute.rb:106
    `nested_attributes_options'@.../activesupport/lib/active_support/core_ext/class/attribute.rb:86
    `with_indifferent_access'@.../activesupport/lib/active_support/core_ext/hash/indifferent_access.rb:8
    `new_from_hash_copying_default'@.../activesupport/lib/active_support/hash_with_indifferent_access.rb:75
    `initialize'@.../activesupport/lib/active_support/hash_with_indifferent_access.rb:58
    `update'@.../activesupport/lib/active_support/hash_with_indifferent_access.rb:127
    `convert_key'@.../activesupport/lib/active_support/hash_with_indifferent_access.rb:258
    `convert_value'@.../activesupport/lib/active_support/hash_with_indifferent_access.rb:262
    `ship'@.../activerecord/lib/active_record/associations/builder/association.rb:110
    `association'@.../activerecord/lib/active_record/associations.rb:149
    `association_instance_get’@.../activerecord/lib/active_record/associations.rb:189
    `_reflect_on_association'@.../activerecord/lib/active_record/reflection.rb:109
    `_reflections'@.../activesupport/lib/active_support/core_ext/class/attribute.rb:86
    `association_class'@.../activerecord/lib/active_record/reflection.rb:435
    `macro'@.../activerecord/lib/active_record/reflection.rb:588
    `require'@.../activesupport/lib/active_support/dependencies.rb:272
    `load_dependency'@.../activesupport/lib/active_support/dependencies.rb:236
    `load?'@.../activesupport/lib/active_support/dependencies.rb:311
    `mechanism'@.../activesupport/lib/active_support/core_ext/module/attribute_accessors.rb:60
    `constant_watch_stack'@.../activesupport/lib/active_support/core_ext/module/attribute_accessors.rb:60
    `watching?'@.../activesupport/lib/active_support/dependencies.rb:99
    `delegate'@.../activesupport/lib/active_support/core_ext/module/delegation.rb:151
    `include?'@/Users/eileen/.rbenv/versions/2.2.0/lib/ruby/2.2.0/set.rb:211
    `initialize'@.../activerecord/lib/active_record/associations/association.rb:24
    `check_validity!'@.../activerecord/lib/active_record/reflection.rb:331
    `check_validity_of_inverse!’@.../activerecord/lib/active_record/reflection.rb:335
    ...

    View full-size slide

  95. `ship_attributes='@.../activerecord/lib/active_record/
    nested_attributes.rb:347

    View full-size slide

  96. def generate_association_writer(association_name, type)
    generated_association_methods.module_eval <<-eoruby,
    __FILE__, __LINE__ + 1
    if method_defined?(:#{association_name}_attributes=)
    remove_method(:#{association_name}_attributes=)
    end
    def #{association_name}_attributes=(attributes)
    assign_nested_attributes_for_#{type}
    _association(:#{association_name}, attributes)
    end
    eoruby
    end

    View full-size slide

  97. TRACE POINT
    $ cd railsconf_scripts/
    $ open trace_point.rb

    View full-size slide

  98. class User < ActiveRecord::Base
    has_one :avatar
    accepts_nested_attributes_for :avatar
    end
    class Avatar < ActiveRecord::Base
    belongs_to :user
    end
    class BugTest < Minitest::Test
    def test_trace_point
    user = User.new(name: "My Name")
    user.avatar_attributes = { name: "I am a file name" }
    user.save!
    end
    end

    View full-size slide

  99. end
    class Avatar < ActiveRecord::Base
    belongs_to :user
    end
    class BugTest < Minitest::Test
    def test_trace_point
    user = User.new(name: "My Name")
    tp = TracePoint.new(:call) do |*args|
    p args
    end
    user.avatar_attributes = { name: "I am a file name" }
    user.save!
    end
    end

    View full-size slide

  100. class Avatar < ActiveRecord::Base
    belongs_to :user
    end
    class BugTest < Minitest::Test
    def test_trace_point
    user = User.new(name: "My Name")
    tp = TracePoint.new(:call) do |*args|
    p args
    end
    tp.enable
    user.avatar_attributes = { name: "I am a file name" }
    tp.disable
    user.save!
    end
    end

    View full-size slide

  101. TRACE POINT
    $ bundle exec ruby trace_point.rb

    View full-size slide

  102. `avatar_attributes='@.../activerecord/lib/active_record/
    nested_attributes.rb:347

    View full-size slide

  103. ADVANCED GIT
    COMMANDS

    View full-size slide

  104. GIT
    $ git add .
    $ git commit -m‘Commit message’
    $ git checkout practicing-git

    View full-size slide

  105. GIT
    $ git add upstream
    $ git bisect
    $ git reset --[soft|hard]
    $ git rebase -i
    $ git reflog

    View full-size slide

  106. GIT REMOTES
    $ cd path/to/rails/
    $ git remote add upstream
    https://github.com/rails/rails.git

    View full-size slide

  107. GIT REMOTES
    $ cd path/to/rails/
    $ git remote remove upstream
    $ git remote add upstream
    https://github.com/rails/rails.git

    View full-size slide

  108. GIT REMOTES
    $ git pull —-rebase upstream master

    View full-size slide

  109. GIT REMOTES
    $ git pull —-rebase upstream master
    $ git push origin master

    View full-size slide

  110. GIT BISECT
    $ git bisect

    View full-size slide

  111. GIT BISECT
    $ git bisect start

    View full-size slide

  112. GIT BISECT
    $ git bisect bad
    $ git bisect good v4.2.rc3

    View full-size slide

  113. Practice
    Git Bisect
    C1 C2 C3 C4 C5 C6 M3 M4
    M0

    View full-size slide

  114. AMENDING COMMITS
    $ git commit --amend

    View full-size slide

  115. GIT RESET
    $ git reset --soft HEAD@{1}

    View full-size slide

  116. GIT RESET
    $ git reset --soft HEAD~N

    View full-size slide

  117. GIT RESET
    $ git reset HEAD

    View full-size slide

  118. Git Reset
    M1 M2 M3 M4
    M0
    master
    HEAD
    Practice

    View full-size slide

  119. GIT REBASE -I
    $ git rebase -i master

    View full-size slide

  120. 1 pick e92e34f Refactor existing code
    2 pick 80601e8 Oops add in missing code
    3 pick 8d91f76 Remove extra piece I left behind
    4 pick 933bd58 Fix bug reported in #9895
    5 pick 99302ef Clean up changes
    6 pick 5cc3c5a Fix spelling mistake in documentation
    7
    8 # Rebase 0131d99..5cc3c5a onto 0131d99
    9 #
    10 # Commands:
    11 # p, pick = use commit
    12 # r, reword = use commit, but edit the commit message
    13 # e, edit = use commit, but stop for amending
    14 # s, squash = use commit, but meld into previous commit
    15 # f, fixup = like "squash", but discard this commit's log
    16 # x, exec = run command (the rest of the line) using shell

    View full-size slide

  121. 1 pick e92e34f Refactor existing code
    2 pick 80601e8 Oops add in missing code
    3 pick 8d91f76 Remove extra piece I left behind
    4 pick 933bd58 Fix bug reported in #9895
    5 pick 99302ef Clean up changes
    6 pick 5cc3c5a Fix spelling mistake in documentation
    7
    8 # Rebase 0131d99..5cc3c5a onto 0131d99
    9 #
    10 # Commands:
    11 # p, pick = use commit
    12 # r, reword = use commit, but edit the commit message
    13 # e, edit = use commit, but stop for amending
    14 # s, squash = use commit, but meld into previous commit
    15 # f, fixup = like "squash", but discard this commit's log
    16 # x, exec = run command (the rest of the line) using shell

    View full-size slide

  122. 1 reword e92e34f Refactor existing code
    2 pick 80601e8 Oops add in missing code
    3 pick 8d91f76 Remove extra piece I left behind
    4 pick 933bd58 Fix bug reported in #9895
    5 pick 99302ef Clean up changes
    6 pick 5cc3c5a Fix spelling mistake in documentation
    7
    8 # Rebase 0131d99..5cc3c5a onto 0131d99
    9 #
    10 # Commands:
    11 # p, pick = use commit
    12 # r, reword = use commit, but edit the commit message
    13 # e, edit = use commit, but stop for amending
    14 # s, squash = use commit, but meld into previous commit
    15 # f, fixup = like "squash", but discard this commit's log
    16 # x, exec = run command (the rest of the line) using shell

    View full-size slide

  123. 1 reword e92e34f Refactor existing code
    2 pick 80601e8 Oops add in missing code
    3 edit 8d91f76 Remove extra piece I left behind
    4 pick 933bd58 Fix bug reported in #9895
    5 pick 99302ef Clean up changes
    6 pick 5cc3c5a Fix spelling mistake in documentation
    7
    8 # Rebase 0131d99..5cc3c5a onto 0131d99
    9 #
    10 # Commands:
    11 # p, pick = use commit
    12 # r, reword = use commit, but edit the commit message
    13 # e, edit = use commit, but stop for amending
    14 # s, squash = use commit, but meld into previous commit
    15 # f, fixup = like "squash", but discard this commit's log
    16 # x, exec = run command (the rest of the line) using shell

    View full-size slide

  124. 1 reword e92e34f Refactor existing code
    2 pick 80601e8 Oops add in missing code
    3 edit 8d91f76 Remove extra piece I left behind
    4 pick 933bd58 Fix bug reported in #9895
    5 squash 99302ef Clean up changes
    6 pick 5cc3c5a Fix spelling mistake in documentation
    7
    8 # Rebase 0131d99..5cc3c5a onto 0131d99
    9 #
    10 # Commands:
    11 # p, pick = use commit
    12 # r, reword = use commit, but edit the commit message
    13 # e, edit = use commit, but stop for amending
    14 # s, squash = use commit, but meld into previous commit
    15 # f, fixup = like "squash", but discard this commit's log
    16 # x, exec = run command (the rest of the line) using shell

    View full-size slide

  125. 1 reword e92e34f Refactor existing code
    2 fixup 80601e8 Oops add in missing code
    3 edit 8d91f76 Remove extra piece I left behind
    4 pick 933bd58 Fix bug reported in #9895
    5 squash 99302ef Clean up changes
    6 pick 5cc3c5a Fix spelling mistake in documentation
    7
    8 # Rebase 0131d99..5cc3c5a onto 0131d99
    9 #
    10 # Commands:
    11 # p, pick = use commit
    12 # r, reword = use commit, but edit the commit message
    13 # e, edit = use commit, but stop for amending
    14 # s, squash = use commit, but meld into previous commit
    15 # f, fixup = like "squash", but discard this commit's log
    16 # x, exec = run command (the rest of the line) using shell

    View full-size slide

  126. 1 reword e92e34f Refactor existing code
    2 fixup 80601e8 Oops add in missing code
    3 edit 8d91f76 Remove extra piece I left behind
    4 pick 933bd58 Fix bug reported in #9895
    5 squash 99302ef Clean up changes
    6 pick 5cc3c5a Fix spelling mistake in documentation
    7
    8 # Rebase 0131d99..5cc3c5a onto 0131d99
    9 #
    10 # Commands:
    11 # p, pick = use commit
    12 # r, reword = use commit, but edit the commit message
    13 # e, edit = use commit, but stop for amending
    14 # s, squash = use commit, but meld into previous commit
    15 # f, fixup = like "squash", but discard this commit's log
    16 # x, exec = run command (the rest of the line) using shell

    View full-size slide

  127. Practice
    M1 M2 M3 M4
    F1 F2 F1 F2
    M0
    Interactive Rebase

    View full-size slide

  128. FORCE PUSH
    $ git push -f origin your-branch

    View full-size slide

  129. GIT REFLOG
    $ git reflog

    View full-size slide

  130. GIT REFLOG
    $ git reflog

    View full-size slide

  131. 628c168 HEAD@{0}: rebase -i (finish): returning to refs/heads/your-branch
    628c168 HEAD@{1}: rebase -i (start): checkout master
    628c168 HEAD@{2}: reset: moving to 628c168
    bc503a1 HEAD@{3}: rebase -i (finish): returning to refs/heads/your-branch
    bc503a1 HEAD@{4}: rebase -i (pick): Add Person model and migration
    1ad2ff2 HEAD@{5}: rebase -i (pick): Improve README section on git reflog
    026457d HEAD@{6}: rebase -i (start): checkout master
    633f620 HEAD@{7}: rebase -i (finish): returning to refs/heads/your-branch
    633f620 HEAD@{8}: rebase -i (pick): Improve README section on git reflog
    19f41a7 HEAD@{9}: commit (amend): Add Person model and migration
    50718b1 HEAD@{10}: rebase -i (edit): Add Person model and migration
    026457d HEAD@{11}: rebase -i (reword): Update README with sections on rebase and
    c8c6000 HEAD@{12}: rebase -i (reword): Update README with sections on rebase and
    628fbd2 HEAD@{13}: rebase -i (squash): Update README description section
    455dab0 HEAD@{14}: rebase -i (fixup): # This is a combination of 2 commits.
    4392779 HEAD@{15}: rebase -i (start): checkout master
    628c168 HEAD@{16}: rebase -i (finish): returning to refs/heads/your-branch
    628c168 HEAD@{17}: rebase -i (start): checkout master
    628c168 HEAD@{18}: rebase -i (finish): returning to refs/heads/your-branch
    628c168 HEAD@{19}: rebase -i (start): checkout master
    628c168 HEAD@{20}: rebase: aborting
    1f935f7 HEAD@{21}: rebase -i (edit): Add Person model and migration

    View full-size slide

  132. GIT REFLOG
    $ git reset -—hard 628c168

    View full-size slide

  133. 628168
    628168
    50718b
    bc503a1
    C1 C2 C3 C4
    C0
    Git Reflog
    Practice

    View full-size slide

  134. FINDING ISSUES
    TO WORK ON

    View full-size slide

  135. WHAT TO WORK ON
    $ Test release candidates and that master branch

    View full-size slide

  136. WHAT TO WORK ON
    % Test release candidates and that master branch
    $ Fix documentation & work on WIP Guides

    View full-size slide

  137. WHAT TO WORK ON
    % Test release candidates and that master branch
    % Fix documentation & work on WIP Guides
    $ Focus on your strengths

    View full-size slide

  138. WHAT TO WORK ON
    % Test release candidates and that master branch
    % Fix documentation & work on WIP Guides
    % Focus on your strengths
    $ Work on ActiveJob or WebConsole

    View full-size slide

  139. WHAT TO WORK ON
    % Test release candidates and that master branch
    % Fix documentation & work on WIP Guides
    % Focus on your strengths
    % Work on ActiveJob or WebConsole
    $ Review open pull requests

    View full-size slide

  140. WHAT TO WORK ON
    % Test release candidates and that master branch
    % Fix documentation & work on WIP Guides
    % Focus on your strengths
    % Work on ActiveJob or WebConsole
    % Review open pull requests
    $ Refactoring methods and tests

    View full-size slide

  141. WHAT TO WORK ON
    % Test release candidates and that master branch
    % Fix documentation & work on WIP Guides
    % Focus on your strengths
    % Work on ActiveJob or WebConsole
    % Review open pull requests
    % Refactoring methods and tests
    $ Watch the issues tracker

    View full-size slide

  142. WHAT WE COVERED
    % Getting set up
    % Guidelines for opening issues
    % Writing test scripts
    % Guidelines for opening pull requests
    % Git
    % Bisect, Interactive Rebase, Reflog
    % Traversing unfamiliar code
    % source_location, CTags, puts caller, TracePoint
    % Finding issues to work on

    View full-size slide

  143. EILEEN M. UCHITELLE
    Programmer at Basecamp
    ! eileencodes.com
    " @eileencodes
    # @eileencodes
    Slides:
    speakerdeck.com/eileencodes

    View full-size slide