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 Slide

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

    View Slide

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

    View Slide

  4. View Slide

  5. Kingston, New York

    View Slide

  6. Arya
    @aryadog

    View Slide

  7. PREREQUISITES

    View Slide

  8. GOALS

    View Slide

  9. to Rails
    can be INTIMIDATING.
    CONTRIBUTING

    View Slide

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

    View Slide

  11. FORMAT

    View Slide

  12. 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 Slide

  13. ENVIRONMENT
    REQUIREMENTS

    View Slide

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

    View Slide

  15. View Slide

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

    View Slide

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

    View Slide

  18. RUNNING THE
    TEST SUITE

    View Slide

  19. View Slide

  20. 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  26. $ 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 Slide

  27. GUIDELINES FOR
    OPENING AN ISSUE

    View Slide

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

    View Slide

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

    View Slide

  30. View Slide

  31. 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 Slide

  32. 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 Slide

  33. 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 Slide

  34. 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 Slide

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

    View Slide

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

    View 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 Slide

  38. 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 Slide

  39. 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 Slide

  40. 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 Slide

  41. 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 Slide

  42. 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 Slide

  43. 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 Slide

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

    View Slide

  45. GUIDELINES FOR
    OPENING A
    PULL REQUEST

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  49. 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 Slide

  50. 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 Slide

  51. 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 Slide

  52. View Slide

  53. 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 Slide

  54. 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 Slide

  55. 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 Slide

  56. 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 Slide

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

    View Slide

  58. # 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 Slide

  59. # 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 Slide

  60. # 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 Slide

  61. # 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 Slide

  62. $ 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 Slide

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

    View Slide

  64. 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 Slide

  65. WORKING WITH
    DEPRECATIONS

    View Slide

  66. # 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 Slide

  67. # 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 Slide

  68. TRAVERSING
    UNFAMILIAR CODE

    View Slide

  69. View Slide

  70. # 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 Slide

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

    View Slide

  72. 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 Slide

  73. 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 Slide

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

    View Slide

  75. (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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  82. CTags
    $ CTRL + ]

    View Slide

  83. CTags
    $ :ts

    View Slide

  84. CTags
    $ :ts
    $ N + ENTER

    View Slide

  85. View Slide

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

    View Slide

  87. # 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 Slide

  88. .../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 Slide

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

    View Slide

  90. 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 Slide

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

    View Slide

  92. 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 Slide

  93. 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 Slide

  94. CALLER
    $ bundle exec ruby puts_caller.rb

    View Slide

  95. .../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 Slide

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

    View Slide

  97. 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 Slide

  98. 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 Slide

  99. View Slide

  100. 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 Slide

  101. 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 Slide

  102. 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 Slide

  103. 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 Slide

  104. 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 Slide

  105. `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 Slide

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

    View Slide

  107. 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 Slide

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

    View Slide

  109. 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 Slide

  110. 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 Slide

  111. 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 Slide

  112. TRACE POINT
    $ bundle exec ruby trace_point.rb

    View Slide

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

    View Slide

  114. ADVANCED GIT
    COMMANDS

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  121. GIT BISECT
    $ git bisect

    View Slide

  122. GIT BISECT
    $ git bisect start

    View Slide

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

    View Slide

  124. View Slide

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

    View Slide

  126. AMENDING COMMITS
    $ git commit --amend

    View Slide

  127. GIT RESET
    $ git reset --soft [email protected]{1}

    View Slide

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

    View Slide

  129. GIT RESET
    $ git reset HEAD

    View Slide

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

    View Slide

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

    View Slide

  132. GIT

    View Slide

  133. 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 Slide

  134. 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 Slide

  135. 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 Slide

  136. 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 Slide

  137. 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 Slide

  138. 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 Slide

  139. 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 Slide

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

    View Slide

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

    View Slide

  142. GIT REFLOG
    $ git reflog

    View Slide

  143. View Slide

  144. GIT REFLOG
    $ git reflog

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  148. FINDING ISSUES
    TO WORK ON

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  152. 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 Slide

  153. 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 Slide

  154. 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 Slide

  155. 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 Slide

  156. 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 Slide

  157. Thank You!

    View Slide

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

    View Slide