Slide 1

Slide 1 text

Welcome To Rail Sconf! @tenderlove

Slide 2

Slide 2 text

"My mom has the Ferrari of sewing machines" — @heddle317

Slide 3

Slide 3 text

"Dad Gummit" — Bill Dance

Slide 4

Slide 4 text

Aaron Patterson

Slide 5

Slide 5 text

@tenderlove

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

"Tenderlove’s Seal of Approval" — Nathan Long @sleeplessgeek

Slide 9

Slide 9 text

Ruby Core

Slide 10

Slide 10 text

Rails Core

Slide 11

Slide 11 text

AT&T, AT&T logo and all AT&T related marks are trademarks of AT&T Intellectual Property and/or AT&T affiliated companies.

Slide 12

Slide 12 text

Job Titles • 2011: Corey Haines • 2012: Senior Facebook Integration Engineer Elect • 2013: Senior Software Architect

Slide 13

Slide 13 text

This Year: Thought Leader, In Training

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

Action Shots

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

Two Cats

Slide 19

Slide 19 text

SeaTac! YouTube! FaceBook! Instagram

Slide 20

Slide 20 text

Gorbachev! Puff Puff! Thunderhorse! The Third @gorbypuff

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

@tenderlove

Slide 23

Slide 23 text

My Tweets 3% 5% 2% 90% Puns Hugs Cat Photos Tech Stuff

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

Chicago Stuff

Slide 26

Slide 26 text

Da Bears

Slide 27

Slide 27 text

Da Bulls

Slide 28

Slide 28 text

Mike Ditka

Slide 29

Slide 29 text

Last Year…

Slide 30

Slide 30 text

☑Adequate Everything. Adequately.

Slide 31

Slide 31 text

F- F- F- F- F- F- F- F- F- F- F-

Slide 32

Slide 32 text

Awkward Situations

Slide 33

Slide 33 text

This story is rated PG-13 for adult situations

Slide 34

Slide 34 text

Rails is 10 years old!!

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

☑ Naming

Slide 38

Slide 38 text

☑ Caching

Slide 39

Slide 39 text

☑ Dependencies

Slide 40

Slide 40 text

"The genius behind … npm is that it solves dependencies, a longtime problem in computer science." — Npm, Inc. COO Rod Boothby http://venturebeat.com/2014/02/11/former-node-leader-takes-big-money-launches-node-startup/

Slide 41

Slide 41 text

Get your act together, RubyGems.

Slide 42

Slide 42 text

What’s left?

Slide 43

Slide 43 text

P = NP

Slide 44

Slide 44 text

|N| = 1

Slide 45

Slide 45 text

I think we’re done here.

Slide 46

Slide 46 text

Anti-Keynote

Slide 47

Slide 47 text

Coverage

Slide 48

Slide 48 text

Speed

Slide 49

Slide 49 text

Metrics

Slide 50

Slide 50 text

Hard Science fing ^

Slide 51

Slide 51 text

Things I’ve Learned At RailsConf

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

Listen To Me. ! Science is good. TDD is Great.

Slide 54

Slide 54 text

No content

Slide 55

Slide 55 text

No content

Slide 56

Slide 56 text

Troll Fast Troll Furious

Slide 57

Slide 57 text

No content

Slide 58

Slide 58 text

Great Workout For Your Eye Muscles.

Slide 59

Slide 59 text

Eyeroll Muscles

Slide 60

Slide 60 text

OMG HOW TO TEST? class Person < ActiveRecord::Base def birthyear? Date.today.year == birthyear end end

Slide 61

Slide 61 text

Pass in today class Person < ActiveRecord::Base def birthyear?(today = Date.today) today.year == birthyear end end ! assert person.birthyear?(Date.today) STAHP

Slide 62

Slide 62 text

omg so wow! class Person < ActiveRecord::Base def birthyear? Date.today.year == birthyear end end ! person = Person.new birthyear: Date.today.year assert person.birthyear? ! person = Person.new birthyear: 1980 assert_not person.birthyear?

Slide 63

Slide 63 text

No New API Necessary!

Slide 64

Slide 64 text

Constructors are pretty cool!

Slide 65

Slide 65 text

Make Construction Easy

Slide 66

Slide 66 text

Controversial Opinions

Slide 67

Slide 67 text

I am a Software Engineer

Slide 68

Slide 68 text

I am not a "Software Writer"

Slide 69

Slide 69 text

[Code] beauty is in the eye of the beholder

Slide 70

Slide 70 text

Science is important. I can’t believe I actually had to say this.

Slide 71

Slide 71 text

Mutable State

Slide 72

Slide 72 text

Rails.has_many :bugs — @a_matsuda

Slide 73

Slide 73 text

Inconsistent Values sp = ActionController::Parameters.new({ foo:[Object.new] }) ! sp.fetch(:foo).equal?(sp[:foo]) # => false sp.fetch(:foo).equal?(sp[:foo]) # => true lolw ut

Slide 74

Slide 74 text

No content

Slide 75

Slide 75 text

Memory Leaks params = ActionController::Parameters.new({ foo:[Object.new] }) ! loop do params.fetch(:foo) params.delete :foo params[:foo] = [Object.new] end

Slide 76

Slide 76 text

Yikes!

Slide 77

Slide 77 text

Leak Source def convert_value_to_parameters(value) if value.is_a?(Array) && !converted_arrays.member? (value) converted = value.map { |_| convert_value_to_parameters(_) } converted_arrays << converted converted elsif value.is_a?(Parameters) || !value.is_a?(Hash) value else self.class.new(value) end end Mutation

Slide 78

Slide 78 text

class HashWithIndifferentAccess < Hash end ! class Parameters < HashWithIndifferentAccess end

Slide 79

Slide 79 text

No content

Slide 80

Slide 80 text

Inconsistent Return Person.create name: "teelo green" ! relation = Person.group(:id) ! relation.size # => {1=>1} relation.to_a # => [#] relation.size # => 1 wut? wut?

Slide 81

Slide 81 text

Cause def size loaded? ? @records.length : count(:all) end Executes! G RO UP Returns # of records

Slide 82

Slide 82 text

No content

Slide 83

Slide 83 text

wtf SQL courtesy of @eileencodes

Slide 84

Slide 84 text

Guess the SQL! author = authors :david 100.times { |i| author.thinking_posts.create!(:body => i.to_s) } author.thinking_posts.delete_all

Slide 85

Slide 85 text

Output SQL DELETE FROM \"posts\" WHERE \"posts\".\"author_id\" = ?

Slide 86

Slide 86 text

Guess the SQL! author = authors :david 100.times { |i| author.thinking_posts.create!(:body => i.to_s) } author.thinking_posts.inspect author.thinking_posts.delete_all Inspect first

Slide 87

Slide 87 text

Output SQL DELETE FROM \"posts\" WHERE \"posts\".\"author_id \" = ? AND \"posts\".\"id\" IN (112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211)

Slide 88

Slide 88 text

Cause if loaded? || dependent == :destroy delete_or_destroy(load_target, dependent) else delete_records(:all, dependent) end

Slide 89

Slide 89 text

No content

Slide 90

Slide 90 text

Avoid Subclassing Built-in’s

Slide 91

Slide 91 text

Be Conservative About Your API

Slide 92

Slide 92 text

Be Extremely Careful With Mutations

Slide 93

Slide 93 text

I Ruby

Slide 94

Slide 94 text

AdequateRecord

Slide 95

Slide 95 text

What is "Adequate Record"?

Slide 96

Slide 96 text

What people are saying:

Slide 97

Slide 97 text

"It’s like ActiveRecord, but more Adequate" — Aaron Patterson, Rails Core Team

Slide 98

Slide 98 text

"It’s alright. I’ve seen better, but this is fine." — Jeremy Kemper, Rails Core Team

Slide 99

Slide 99 text

"It seems to work" — Yehuda Katz, Ember Core Team

Slide 100

Slide 100 text

"Sometimes good enough is good enough" — Josh Susser, Ruby Rogue

Slide 101

Slide 101 text

"Huh? What is that?" — Yukihiro Matz, Creator of Ruby

Slide 102

Slide 102 text

Why "Adequate"?

Slide 103

Slide 103 text

Humility.

Slide 104

Slide 104 text

Anti-Marketing

Slide 105

Slide 105 text

Why a fork?

Slide 106

Slide 106 text

I don’t want new features.

Slide 107

Slide 107 text

Adequate History

Slide 108

Slide 108 text

3 years.

Slide 109

Slide 109 text

Adequate?

Slide 110

Slide 110 text

How ActiveRecord works.

Slide 111

Slide 111 text

ActiveRecord::Relation Post.find(10) ARel::SQL::Node Database ActiveRecord::Base SELECT * FROM …

Slide 112

Slide 112 text

Adequate Change: Part 1

Slide 113

Slide 113 text

Bind Parameter Introduction

Slide 114

Slide 114 text

Post.find(1) Post.find(3) Post.find(5) SELECT * FROM posts WHERE id = 1 SELECT * FROM posts WHERE id = 3 SELECT * FROM posts WHERE id = 5

Slide 115

Slide 115 text

Post.find(1) Post.find(3) Post.find(5) SELECT * FROM posts WHERE id = ? [id, 1] SELECT * FROM posts WHERE id = ? [id, 3] SELECT * FROM posts WHERE id = ? [id, 5]

Slide 116

Slide 116 text

Separate static and dynamic content

Slide 117

Slide 117 text

Adequate Theory

Slide 118

Slide 118 text

ActiveRecord::Relation Post.find(10) ARel::SQL::Node Database ActiveRecord::Base SELECT * FROM … Cacheable

Slide 119

Slide 119 text

Adequate Change: Part 2

Slide 120

Slide 120 text

Code Decoupling

Slide 121

Slide 121 text

def add_constraints(scope) tables = construct_tables ! chain.each_with_index do |reflection, i| table, foreign_table = tables.shift, tables.first ! if reflection.source_macro == :has_and_belongs_to_many join_table = tables.shift ! scope = scope.joins(join( join_table, table[reflection.association_primary_key]. eq(join_table[reflection.association_foreign_key]) )) ! table, foreign_table = join_table, tables.first end ! if reflection.source_macro == :belongs_to if reflection.options[:polymorphic] key = reflection.association_primary_key(klass) else key = reflection.association_primary_key end ! foreign_key = reflection.foreign_key else key = reflection.foreign_key foreign_key = reflection.active_record_primary_key end ! if reflection == chain.last bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key] scope = scope.where(table[key].eq(bind_val)) ! if reflection.type value = owner.class.base_class.name bind_val = bind scope, table.table_name, reflection.type.to_s, value scope = scope.where(table[reflection.type].eq(bind_val)) end else constraint = table[key].eq(foreign_table[foreign_key]) ! if reflection.type type = chain[i + 1].klass.base_class.name constraint = constraint.and(table[reflection.type].eq(type)) end ! scope = scope.joins(join(foreign_table, constraint)) end ! # Exclude the scope of the association itself, because that # was already merged in the #scope method. (scope_chain[i] - [self.reflection.scope]).each do |scope_chain_item| item = eval_scope(reflection.klass, scope_chain_item) ! scope.includes! item.includes_values scope.where_values += item.where_values end end ! scope end Dynamic Dynamic Dynamic

Slide 122

Slide 122 text

One Method to Rule Them All, And In Legacy Code Bind Them.

Slide 123

Slide 123 text

rm habtm R ails 4.1

Slide 124

Slide 124 text

habtm.is_a?(hm:t) #=> true

Slide 125

Slide 125 text

commit 88c009377851912c60fd16ec4bfab3001ac2cf9f Author: Aaron Patterson Date: Wed Oct 2 15:53:56 2013 -0700 ! remove HABTM special cases from associations classes ! diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/ association_scope.rb index 8027acf..d862a5f 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -44,18 +44,6 @@ module ActiveRecord chain.each_with_index do |reflection, i| table, foreign_table = tables.shift, tables.first - if reflection.source_macro == :has_and_belongs_to_many - join_table = tables.shift - - scope = scope.joins(join( - join_table, - table[reflection.association_primary_key]. - eq(join_table[reflection.association_foreign_key]) - )) - - table, foreign_table = join_table, tables.first - end - if reflection.source_macro == :belongs_to if reflection.options[:polymorphic] key = reflection.association_primary_key(self.klass) diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/ join_dependency.rb index 5aa17e5..fa212f3 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -216,7 +216,7 @@ module ActiveRecord else association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil? case macro - when :has_many, :has_and_belongs_to_many + when :has_many other = record.association(join_part.reflection.name) other.loaded! other.target.push(association) if association diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/

Slide 126

Slide 126 text

No content

Slide 127

Slide 127 text

Adequate Change: Part 3

Slide 128

Slide 128 text

Introduce a cache

Slide 129

Slide 129 text

Cache Code Example cache = ActiveRecord::StatementCache.new do |params| Person.where(name: params.bind).limit(1) end ! cache.execute ["Aaron"] cache.execute ["Ebi"] AR::Relation Execute

Slide 130

Slide 130 text

Cache Object Internals #, #]], @indexes=[0]>, @query_builder= #> Binds C om piled SQ L

Slide 131

Slide 131 text

Update Internals, Cache Relation Objects

Slide 132

Slide 132 text

Things That Use Relations • Post.find() • Post.find_by_* • has_many • has_many :through • has_and_belongs_to_many • belongs_to All Cacheable

Slide 133

Slide 133 text

Example Implementation

Slide 134

Slide 134 text

Person.find(id) s = find_by_statement_cache.synchronize { find_by_statement_cache[key] ||= StatementCache.create(connection) { |params| where(primary_key => params.bind).limit(1) } } record = s.execute([id], self, connection).first R elation! (static) Parameter! (dynamic)

Slide 135

Slide 135 text

Fun Facts!

Slide 136

Slide 136 text

Ways to call `Post.find` • Post.find(1) • Post.find([1]) • Post.find([[1]]) • Post.find({1 => 1}) • Post.find(post) • Post.find(1) { … } • scoping { Post.find(1) } Cacheable wtf WHAT DO THESE EVEN MEAN???

Slide 137

Slide 137 text

No content

Slide 138

Slide 138 text

Rejection Code def find(*ids) # We don't have cache keys for this stuff yet return super unless ids.length == 1 return super if block_given? || primary_key.nil? || default_scopes.any? || columns_hash.include?(inheritance_column) || ids.first.kind_of?(Array)

Slide 139

Slide 139 text

Measuring Performance

Slide 140

Slide 140 text

benchmark/ips

Slide 141

Slide 141 text

benchmark Benchmark.bm do |x| x.report('some test') { N.times { some_test }} end How big?

Slide 142

Slide 142 text

Output user system total real some test 0.000000 0.000000 0.000000 ( 0.000098)

Slide 143

Slide 143 text

benchmark/ips Benchmark.ips do |x| x.report('some test') { some_test } end

Slide 144

Slide 144 text

Output Calculating ------------------------------------- some test 114186 i/100ms ------------------------------------------------- some test 7677966.1 (±3.0%) i/s - 38366496 in 5.002452s

Slide 145

Slide 145 text

Sttdev is important

Slide 146

Slide 146 text

GC.stat()

Slide 147

Slide 147 text

Total allocations GC.stat(:total_allocated_object)

Slide 148

Slide 148 text

Measure Allocations Person.find id before = GC.stat(:total_allocated_object) N.times { Person.find id } after = GC.stat(:total_allocated_object) puts (after - before) / N W arm up Benchmark Objects / Call Count O bjs C ount O bjs

Slide 149

Slide 149 text

Performance of AdequateRecord

Slide 150

Slide 150 text

Post.find() Post.find_by_name()

Slide 151

Slide 151 text

`Post.find` Calls Per Second over Time Calls Per Second (higher is better) 0 4000 8000 12000 16000 2-3-stable 3-0-stable 3-1-stable 3-2-stable 4-0-stable 4-1-stable master adequaterecord SQLite3 MySQL2 MySQL PostgreSQL

Slide 152

Slide 152 text

`Post.find_by_name` Calls Per Second over Time Calls Per Second (higher is better) 0 3500 7000 10500 14000 2-3-stable 3-0-stable 3-1-stable 3-2-stable 4-0-stable 4-1-stable master adequaterecord SQLite3 MySQL2 MySQL PostgreSQL

Slide 153

Slide 153 text

Calls Per Second (MySQL) Calls Per Second (higher is better) 0 2000 4000 6000 8000 2-3-stable 3-0-stable 3-1-stable 3-2-stable 4-0-stable 4-1-stable master adequaterecord find by id find by name

Slide 154

Slide 154 text

% Faster than 4-1-stable 0.00% 45.00% 90.00% 135.00% 180.00% SQLite3 MySQL2 MySQL PostgreSQL find by id find by name

Slide 155

Slide 155 text

% Faster than 2-3-stable 0.00% 35.00% 70.00% 105.00% 140.00% SQLite3 MySQL PostgreSQL find by id find by name

Slide 156

Slide 156 text

Objects Allocated Per Call (find by id) Objects Allocated (lower is better) 0 75 150 225 300 2-3-stable 3-0-stable 3-1-stable 3-2-stable 4-0-stable 4-1-stable master adequate SQLite3 MySQL2 MySQL PostgreSQL

Slide 157

Slide 157 text

Object Allocated Per Call (find by name) Objects Allocated (lower is better) 0 75 150 225 300 2-3-stable 3-0-stable 3-1-stable 3-2-stable 4-0-stable 4-1-stable master adequate SQLite3 MySQL2 MySQL PostgreSQL

Slide 158

Slide 158 text

70% Fewer Objects Compared to 4-1-stable

Slide 159

Slide 159 text

55% Fewer Objects Compared to 2-3-stable

Slide 160

Slide 160 text

belongs_to

Slide 161

Slide 161 text

belongs_to calls per second Calls Per Second (higher is better) 0 3000 6000 9000 12000 2-3-stable 3-0-stable 3-1-stable 3-2-stable 4-0-stable 4-1-stable master adequaterecord SQLite3 MySQL MySQL2 PostgreSQL

Slide 162

Slide 162 text

belongs_to Percent Faster 0.00% 45.00% 90.00% 135.00% 180.00% SQLite3 MySQL MySQL2 PostgreSQL 2-3-stable 4-1-stable

Slide 163

Slide 163 text

has_many has_many :through

Slide 164

Slide 164 text

`has_many` Call Speed Over Time Calls Per Second (higher is better) 0 2750 5500 8250 11000 2-3-stable 3-0-stable 4-1-stable master adequaterecord SQlite3 MySQL MySQL2 PostgreSQL

Slide 165

Slide 165 text

`hm:t` Call Speed Over Time Calls Per Second (higher is better) 0 3000 6000 9000 12000 2-3-stable 3-0-stable 4-1-stable master adequaterecord SQLite3 MySQL MySQL2 PostgreSQL

Slide 166

Slide 166 text

Percent Faster than 2-3-stable 0% 25% 50% 75% 100% SQLite3 MySQL PostgreSQL has_many hm:t

Slide 167

Slide 167 text

Percent Faster than 4-1-stable 0% 65% 130% 195% 260% SQLite3 MySQL MySQL2 PostgreSQL has_many hm:t

Slide 168

Slide 168 text

has_many :through growth

Slide 169

Slide 169 text

Growth Test class A < ActiveRecord::Base has_many :bs has_many :cs, :through => :bs end ! class B < ActiveRecord::Base belongs_to :a has_many :cs end ! class C < ActiveRecord::Base belongs_to :b end

Slide 170

Slide 170 text

Growth Test class A < ActiveRecord::Base has_many :bs has_many :cs, :through => :bs has_many :ds, :through => :cs end ! class B < ActiveRecord::Base belongs_to :a has_many :cs end ! class C < ActiveRecord::Base belongs_to :b has_many :ds end ! class D < ActiveRecord::Base belongs_to :b end as we add more,! what’s the speed?

Slide 171

Slide 171 text

Time Taken to Call hm:t 100k times Seconds Taken (lower is better) 0 40 80 120 160 Nuber of hm:t associations 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 y = 0.5783x + 5.9815 R² = 0.9971 y = 7.448x + 16.506 R² = 0.9974 4-1-stable adequate AWWWWW YEAH, CONSTANT TIME!

Slide 172

Slide 172 text

TL;DR: ~100% faster

Slide 173

Slide 173 text

TL;DR: 9001% better

Slide 174

Slide 174 text

Adequate Pricing Model

Slide 175

Slide 175 text

$2.99 + in-app purchases. Act! Now!

Slide 176

Slide 176 text

Adequate Challenges

Slide 177

Slide 177 text

Trade Memory For Speed

Slide 178

Slide 178 text

Total Cache Size = count(find_by_*) * Size

Slide 179

Slide 179 text

Raw Relations

Slide 180

Slide 180 text

Example Controller class PeopleController def index @people = Person.where(name: params[:name]).to_a end end

Slide 181

Slide 181 text

Can we cache it?

Slide 182

Slide 182 text

Experimental Branch Person.where(name: params[:name]).to_a Iterations Per Second (higher is better) 0 2000 4000 6000 8000 where(:name) Master Experimental Branch

Slide 183

Slide 183 text

~30% faster

Slide 184

Slide 184 text

Key Calculation def cached_query if [joins_values, having_values, group_values, order_values, select_values, ].any?(&:any?) return yield end ! if limit_value || offset_value || distinct_value || from_value || lock_value return yield end ! if where_values.length != bind_values.length return yield end ! key = if bind_values.any? "bv_#{bind_values.map { |bv| bv.first.name }.join}" else :find_all_stuff end ! connection = @klass.connection cache = @klass.find_by_statement_cache ! s = cache[key] || cache.synchronize { cache[key] ||= begin bvs = bind_values.map { |bv| bv = bv.dup bv[1] = StatementCache::Substitute.new bv } StatementCache.create_with_binds(connection, arel, bvs) end } s.execute bind_values.map(&:last), @klass, connection end Key Calculation Query Execution

Slide 185

Slide 185 text

Over 11 variables impact the key

Slide 186

Slide 186 text

This code only handles two types of queries

Slide 187

Slide 187 text

One more experiment

Slide 188

Slide 188 text

Finder Comparison Benchmark.ips do |x| x.report('Person.where') { Person.where(name: 'Aaron').first } x.report('Person.find_by') { Person.find_by_name('Aaron') } end

Slide 189

Slide 189 text

Finder Comparison Calls Per Second 0 3500 7000 10500 14000 Finder .where() .find_by_name

Slide 190

Slide 190 text

find_by_name is 3x faster

Slide 191

Slide 191 text

Finder Comparison Benchmark.ips do |x| x.report('Person.where') { Person.where(name: 'Aaron').first } x.report('Person.find_by') { Person.find_by_name('Aaron') } end New AR::Relation Allocation

Slide 192

Slide 192 text

Should we cache all relations?

Slide 193

Slide 193 text

I’m not sure.

Slide 194

Slide 194 text

I want a new API.

Slide 195

Slide 195 text

New API class Person < ActiveRecord::Base cached_query(:find_all_by_name) do |name| where(name: name) end end ! Person.find_all_by_name("foo") Person.find_all_by_name("bar") Build a! cached relation Execute it

Slide 196

Slide 196 text

Cache Key is Easy

Slide 197

Slide 197 text

Relation API maintained

Slide 198

Slide 198 text

–Steve Jobs “One other more thing.”

Slide 199

Slide 199 text

Let’s merge Adequate Record

Slide 200

Slide 200 text

No content

Slide 201

Slide 201 text

AdequateRecord has been merged.

Slide 202

Slide 202 text

Rails 4.2 Will Be the Fastest Ever.

Slide 203

Slide 203 text

This is just the beginning.

Slide 204

Slide 204 text

Thank you!