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

Clarity: You keep using that word...

Clarity: You keep using that word...

Recently, you may have watched a talk during which the speaker said: "A lot of people will gripe about 'ActiveRecord is too big, [...] has too many methods, the surface area is too big.'"

Hi, my name is "A. Lotofpeople", and I'd like to discuss with you why I've been griping. We'll talk about what "clarity" means to Rubyists, and its relationship to the hard problem of naming things. I'll share some principles that I've found helpful, coping mechanisms I've developed to allow me to "fight the framework" without suffering too badly, and thoughts on what some first steps toward bringing clarity to a Rails codebase might look like.

Or, I might just talk about cats. You'll have to attend to find out!

Ernie Miller

June 10, 2014
Tweet

More Decks by Ernie Miller

Other Decks in Programming

Transcript

  1. Class.new (2.1.1) Model View Controller Ancestors 4 56 50 63

    Public/Protected Methods 99 575 244 351 Private Methods 90 126 101 105 Public/Protected Methods 55 311 405 333 Private Methods 72 160 132 115 Class Instance
  2. –DHH “A lot of people will gripe about ‘ActiveRecord is

    too big, [...] has too many methods, the surface area is too big.’ Who gives a sh*t? Is it more readable? Is it more clear? That’s the only thing that matters.”
  3. –DHH “A lot of people will gripe about ‘ActiveRecord is

    too big, [...] has too many methods, the surface area is too big.’ Who gives a sh*t? Is it more readable? Is it more clear? That’s the only thing that matters.”
  4. class Post < ActiveRecord::Base end ! post = Post.first Post.attribute_names

    # => ["id", "title", "body", "created_at", "updated_at"] post.title # => "Hello, world!"
  5. class Post < ActiveRecord::Base end ! post = Post.first Post.attribute_names

    # => ["id", "title", "body", "created_at", "updated_at"] post.title # => "Hello, world!" Post.select(:body).first post.title # => ActiveModel::MissingAttributeError: missing attribute: title
  6. class Post < ActiveRecord::Base end ! post = Post.first Post.attribute_names

    # => ["id", "title", "body", "created_at", "updated_at"] post.title # => "Hello, world!" Post.select(:body).first post.title # => ActiveModel::MissingAttributeError: missing attribute: title Post.select('title as trololol').first post.title # => ActiveModel::MissingAttributeError: missing attribute: title post.trololol # => "Hello, world!"
  7. def inherited(child_class) child_class.initialize_generated_modules super end ! def initialize_generated_modules @generated_attribute_methods =

    Module.new { extend Mutex_m } @attribute_methods_generated = false include @generated_attribute_methods end active_record/attribute_methods.rb
  8. def inherited(child_class) child_class.initialize_generated_modules super end ! def initialize_generated_modules @generated_attribute_methods =

    Module.new { extend Mutex_m } @attribute_methods_generated = false include @generated_attribute_methods end active_record/attribute_methods.rb
  9. def inherited(child_class) child_class.initialize_generated_modules super end ! def initialize_generated_modules @generated_attribute_methods =

    Module.new { extend Mutex_m } @attribute_methods_generated = false include @generated_attribute_methods end active_record/attribute_methods.rb
  10. def method_missing(method, *args, &block) self.class.define_attribute_methods if respond_to_without_attributes?(method) if attribute_method =

    self.class.find_generated_attribute_method(method) attribute_method.bind(self).call(*args, &block) else send(method, *args, &block) end else super end end active_record/attribute_methods.rb
  11. def method_missing(method, *args, &block) self.class.define_attribute_methods if respond_to_without_attributes?(method) if attribute_method =

    self.class.find_generated_attribute_method(method) attribute_method.bind(self).call(*args, &block) else send(method, *args, &block) end else super end end active_record/attribute_methods.rb
  12. def define_attribute_methods generated_attribute_methods.synchronize do return false if @attribute_methods_generated superclass.define_attribute_methods unless

    self == base_class super(column_names) @attribute_methods_generated = true end true end active_record/attribute_methods.rb
  13. def define_attribute_methods generated_attribute_methods.synchronize do return false if @attribute_methods_generated superclass.define_attribute_methods unless

    self == base_class super(column_names) @attribute_methods_generated = true end true end active_record/attribute_methods.rb
  14. def define_attribute_methods generated_attribute_methods.synchronize do return false if @attribute_methods_generated superclass.define_attribute_methods unless

    self == base_class super(column_names) @attribute_methods_generated = true end true end active_record/attribute_methods.rb
  15. def column_names @column_names ||= columns.map { |column| column.name } end

    ! def columns @columns ||= connection.schema_cache.columns(table_name).map do |col| col = col.dup col.primary = (col.name == primary_key) col end end active_record/model_schema.rb
  16. def column_names @column_names ||= columns.map { |column| column.name } end

    ! def columns @columns ||= connection.schema_cache.columns(table_name).map do |col| col = col.dup col.primary = (col.name == primary_key) col end end active_record/model_schema.rb
  17. def column_names @column_names ||= columns.map { |column| column.name } end

    ! def columns @columns ||= connection.schema_cache.columns(table_name).map do |col| col = col.dup col.primary = (col.name == primary_key) col end end active_record/model_schema.rb
  18. def table_name reset_table_name unless defined?(@table_name) @table_name end ! def reset_table_name

    self.table_name = if abstract_class? superclass == Base ? nil : superclass.table_name elsif superclass.abstract_class? superclass.table_name || compute_table_name else compute_table_name end end active_record/model_schema.rb
  19. def table_name reset_table_name unless defined?(@table_name) @table_name end ! def reset_table_name

    self.table_name = if abstract_class? superclass == Base ? nil : superclass.table_name elsif superclass.abstract_class? superclass.table_name || compute_table_name else compute_table_name end end active_record/model_schema.rb
  20. def table_name reset_table_name unless defined?(@table_name) @table_name end ! def reset_table_name

    self.table_name = if abstract_class? superclass == Base ? nil : superclass.table_name elsif superclass.abstract_class? superclass.table_name || compute_table_name else compute_table_name end end active_record/model_schema.rb
  21. def compute_table_name base = base_class if self == base if

    parent < Base && !parent.abstract_class? contained = parent.table_name contained = contained.singularize if parent.pluralize_table_names contained += '_' end "#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{table_name_suffix}" else base.table_name end end active_record/model_schema.rb
  22. def compute_table_name base = base_class if self == base if

    parent < Base && !parent.abstract_class? contained = parent.table_name contained = contained.singularize if parent.pluralize_table_names contained += '_' end "#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{table_name_suffix}" else base.table_name end end active_record/model_schema.rb
  23. def undecorated_table_name(class_name = base_class.name) table_name = class_name.to_s.demodulize.underscore pluralize_table_names ? table_name.pluralize

    : table_name end active_record/model_schema.rb RubyGroups::BostonRb -> BostonRb -> boston_rb -> boston_rbs
  24. def undecorated_table_name(class_name = base_class.name) table_name = class_name.to_s.demodulize.underscore pluralize_table_names ? table_name.pluralize

    : table_name end active_record/model_schema.rb RubyGroups::BostonRb -> BostonRb -> boston_rb -> boston_rbs Animals::Moose -> Moose -> moose -> mooses
  25. module ActiveSupport Inflector.inflections(:en) do |inflect| inflect.plural(/$/, 's') inflect.plural(/s$/i, 's') inflect.plural(/^(ax|test)is$/i,

    '\1es') inflect.plural(/(octop|vir)us$/i, '\1i') inflect.plural(/(octop|vir)i$/i, '\1i') inflect.plural(/(alias|status)$/i, '\1es') inflect.plural(/(bu)s$/i, '\1ses') inflect.plural(/(buffal|tomat)o$/i, '\1oes') inflect.plural(/([ti])um$/i, '\1a') inflect.plural(/([ti])a$/i, '\1a') inflect.plural(/sis$/i, 'ses') inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves') inflect.plural(/(hive)$/i, '\1s') inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies') inflect.plural(/(x|ch|ss|sh)$/i, '\1es') inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices') inflect.plural(/^(m|l)ouse$/i, '\1ice') inflect.plural(/^(m|l)ice$/i, '\1ice') inflect.plural(/^(ox)$/i, '\1en') inflect.plural(/^(oxen)$/i, '\1') inflect.plural(/(quiz)$/i, '\1zes') ! inflect.singular(/s$/i, '') inflect.singular(/(ss)$/i, '\1') inflect.singular(/(n)ews$/i, '\1ews') inflect.singular(/([ti])a$/i, '\1um') inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '\1sis') inflect.singular(/(^analy)(sis|ses)$/i, '\1sis') inflect.singular(/([^f])ves$/i, '\1fe') inflect.singular(/(hive)s$/i, '\1') inflect.singular(/(tive)s$/i, '\1') inflect.singular(/([lr])ves$/i, '\1f') inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y') inflect.singular(/(s)eries$/i, '\1eries') inflect.singular(/(m)ovies$/i, '\1ovie') inflect.singular(/(x|ch|ss|sh)es$/i, '\1') inflect.singular(/^(m|l)ice$/i, '\1ouse') inflect.singular(/(bus)(es)?$/i, '\1') inflect.singular(/(o)es$/i, '\1') active_support/inflections.rb
  26. inflect.singular(/(ss)$/i, '\1') inflect.singular(/(n)ews$/i, '\1ews') inflect.singular(/([ti])a$/i, '\1um') inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '\1sis') inflect.singular(/(^analy)(sis|ses)$/i, '\1sis')

    inflect.singular(/([^f])ves$/i, '\1fe') inflect.singular(/(hive)s$/i, '\1') inflect.singular(/(tive)s$/i, '\1') inflect.singular(/([lr])ves$/i, '\1f') inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y') inflect.singular(/(s)eries$/i, '\1eries') inflect.singular(/(m)ovies$/i, '\1ovie') inflect.singular(/(x|ch|ss|sh)es$/i, '\1') inflect.singular(/^(m|l)ice$/i, '\1ouse') inflect.singular(/(bus)(es)?$/i, '\1') inflect.singular(/(o)es$/i, '\1') inflect.singular(/(shoe)s$/i, '\1') inflect.singular(/(cris|test)(is|es)$/i, '\1is') inflect.singular(/^(a)x[ie]s$/i, '\1xis') inflect.singular(/(octop|vir)(us|i)$/i, '\1us') inflect.singular(/(alias|status)(es)?$/i, '\1') inflect.singular(/^(ox)en/i, '\1') inflect.singular(/(vert|ind)ices$/i, '\1ex') inflect.singular(/(matr)ices$/i, '\1ix') inflect.singular(/(quiz)zes$/i, '\1') inflect.singular(/(database)s$/i, '\1') ! inflect.irregular('person', 'people') inflect.irregular('man', 'men') inflect.irregular('child', 'children') inflect.irregular('sex', 'sexes') inflect.irregular('move', 'moves') inflect.irregular('zombie', 'zombies') ! inflect.uncountable(%w(equipment information rice money species series fish sheep jeans police)) end end active_support/inflections.rb
  27. class Post < ImaginaryRecord::Record self.table_name 'posts' ! attribute :id, Attr::Integer

    attribute :title, Attr::String attribute :body, Attr::String attribute :created_at, Attr::Timestamp attribute :updated_at, Attr::Timestamp end in/your/imagination.rb
  28. class MagicRecord < ImaginaryRecord::Record def method_missing(method, *args, &block) self.class.define_attribute_methods #

    ... end ! def self.define_attribute_methods # ... klass.table_name(compute_table_name) columns_from_table(table_name).each do |column| klass.attribute column.name, column.type end # ... end end in/your/imagination.rb
  29. ActiveRecord Attribute typecasting Serialization Macro reflection Transactions Nested attributes Associations

    Secure passwords Timestamps Lifecycle callbacks Dirty tracking Validations Mass assignment sanitation
  30. ActiveRecord Attribute typecasting Serialization Macro reflection Transactions Nested attributes Associations

    Secure passwords Timestamps Lifecycle callbacks Dirty tracking Validations Mass assignment sanitation Querying Persisting
  31. MyModel #<Module:0x007fc42ad68878> ActiveRecord::Base ActiveRecord::Store ActiveRecord::Serialization ActiveModel::Serializers::Xml ActiveModel::Serializers::JSON ActiveModel::Serialization ActiveRecord::Reflection ActiveRecord::Transactions

    ActiveRecord::Aggregations ActiveRecord::NestedAttributes ActiveRecord::AutosaveAssociation ActiveModel::SecurePassword ActiveRecord::Associations ActiveRecord::Timestamp ActiveModel::Validations::Callbacks ActiveRecord::Callbacks ActiveRecord::AttributeMethods::Serialization ActiveRecord::AttributeMethods::Dirty
  32. ActiveModel::Validations ActiveRecord::Integration ActiveModel::Conversion ActiveRecord::AttributeAssignment ActiveModel::ForbiddenAttributesProtection ActiveRecord::Sanitization ActiveRecord::Scoping::Named ActiveRecord::Scoping::Default ActiveRecord::Scoping ActiveRecord::Inheritance

    ActiveRecord::ModelSchema ActiveRecord::ReadonlyAttributes ActiveRecord::NoTouching ActiveRecord::Persistence ActiveRecord::Core Object ActiveSupport::Dependencies::Loadable JSON::Ext::Generator::GeneratorMethods::Object Kernel BasicObject
  33. ActiveModel::Validations ActiveRecord::Integration ActiveModel::Conversion ActiveRecord::AttributeAssignment ActiveModel::ForbiddenAttributesProtection ActiveRecord::Sanitization ActiveRecord::Scoping::Named ActiveRecord::Scoping::Default ActiveRecord::Scoping ActiveRecord::Inheritance

    ActiveRecord::ModelSchema ActiveRecord::ReadonlyAttributes ActiveRecord::NoTouching ActiveRecord::Persistence ActiveRecord::Core Object ActiveSupport::Dependencies::Loadable JSON::Ext::Generator::GeneratorMethods::Object Kernel BasicObject “Clarity”
  34. “A Repository mediates between the domain and data mapping layers,

    acting like an in-memory domain object collection. Client objects construct query specifications declaratively and submit them to Repository for satisfaction.” http://martinfowler.com/eaaCatalog/repository.html
  35. def to_a load @records end ! def load exec_queries unless

    loaded? ! self end ! private ! def exec_queries @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bind_values) ! preload = preload_values preload += includes_values unless eager_loading? preloader = ActiveRecord::Associations::Preloader.new preload.each do |associations| preloader.preload @records, associations end ! @records.each { |record| record.readonly! } if readonly_value ! @loaded = true @records end lib/active_record/relation.rb
  36. def to_a load @records end ! def load exec_queries unless

    loaded? ! self end ! private ! def exec_queries @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bind_values) ! preload = preload_values preload += includes_values unless eager_loading? preloader = ActiveRecord::Associations::Preloader.new preload.each do |associations| preloader.preload @records, associations end ! @records.each { |record| record.readonly! } if readonly_value ! @loaded = true @records end lib/active_record/relation.rb
  37. You

  38. You

  39. IDD

  40. describe Monster do let(:teeth) { nil } let(:claws) { nil

    } subject { Monster.new(teeth, claws) } ! describe 'with no teeth or claws' do it 'cannot bite' do proc { subject.bite('Ernie') }.must_raise NoMethodError end ! it 'cannot scratch' do proc { subject.bite('Ernie') }.must_raise NoMethodError end end end class Monster def initialize(teeth = nil, claws = nil) @teeth, @claws = teeth, claws end ! def bite(target) @teeth.bite(target) end ! def scratch(target) @claws.scratch(target) end end
  41. class Monster < ActiveRecord::Base include ::Monster::Biting include ::Monster::Scratching ! belongs_to

    :teeth belongs_to :claws validates :teeth, :claws, :presence => true end
  42. describe 'Monster::Biting' do let(:biter) { Class.new { attr_reader :teeth include

    ::Monster::Biting def initialize(teeth) @teeth = teeth end } } ! it ‘inflicts puncture damage' do # ... end end spec/behaviors/monster/biting_spec.rb
  43. module Foo def self.included(base) base.class_eval do def self.method_injected_by_foo ... end

    end end end ! module Bar def self.included(base) base.method_injected_by_foo end end ! class Host include Foo # We need to include this dependency for Bar include Bar # Bar is the module that Host really needs end
  44. require 'active_support/concern' ! module Foo extend ActiveSupport::Concern included do def

    self.method_injected_by_foo ... end end end ! module Bar extend ActiveSupport::Concern include Foo ! included do self.method_injected_by_foo end end ! class Host include Bar # works, Bar takes care now of its dependencies end
  45. +

  46. SQL

  47. Principles Keep code size smaller by only writing the code

    we really need ActiveRecord + ActiveModel + ActiveSupport = 28152 LOC Norm = 2540 LOC
  48. Norm::Attributes class PersonAttributes < Norm::Attributes attribute :id, Norm::Attr::Integer attribute :name,

    Norm::Attr::String attribute :age, Norm::Attr::Integer attribute :created_at, Norm::Attr::Timestamp attribute :updated_at, Norm::Attr::Integer ! identity :id end ! attrs = PersonAttributes.new :name => 'Ernie', :age => 36 # => #<PersonAttributes # id: <DEFAULT>, name: "Ernie", age: 36, # created_at: <DEFAULT>, updated_at: <DEFAULT> # > attrs.identity? # => false attrs[:id] = 1 attrs.identity? # => true attrs.updated # => {:id => 1}
  49. Norm::Attributes class PersonAttributes < Norm::Attributes attribute :id, Norm::Attr::Integer attribute :name,

    Norm::Attr::String attribute :age, Norm::Attr::Integer attribute :created_at, Norm::Attr::Timestamp attribute :updated_at, Norm::Attr::Integer ! identity :id end ! attrs = PersonAttributes.new :name => 'Ernie', :age => 36 # => #<PersonAttributes # id: <DEFAULT>, name: "Ernie", age: 36, # created_at: <DEFAULT>, updated_at: <DEFAULT> # > attrs.identity? # => false attrs[:id] = 1 attrs.identity? # => true attrs.updated # => {:id => 1} .load(value)
  50. static VALUE pgconn_exec_params( int argc, VALUE *argv, VALUE self )

    { // ... ! for ( i = 0; i < nParams; i++ ) { param = rb_ary_entry(params, i); if (TYPE(param) == T_HASH) { param_type = rb_hash_aref(param, sym_type); param_value_tmp = rb_hash_aref(param, sym_value); if(param_value_tmp == Qnil) param_value = param_value_tmp; else param_value = rb_obj_as_string(param_value_tmp); param_format = rb_hash_aref(param, sym_format); } ! // ... } } ext/pg_connection.c
  51. static VALUE pgconn_exec_params( int argc, VALUE *argv, VALUE self )

    { // ... ! for ( i = 0; i < nParams; i++ ) { param = rb_ary_entry(params, i); if (TYPE(param) == T_HASH) { param_type = rb_hash_aref(param, sym_type); param_value_tmp = rb_hash_aref(param, sym_value); if(param_value_tmp == Qnil) param_value = param_value_tmp; else param_value = rb_obj_as_string(param_value_tmp); param_format = rb_hash_aref(param, sym_format); } ! // ... } } ext/pg_connection.c
  52. static VALUE pgconn_exec_params( int argc, VALUE *argv, VALUE self )

    { // ... ! for ( i = 0; i < nParams; i++ ) { param = rb_ary_entry(params, i); if (TYPE(param) == T_HASH) { param_type = rb_hash_aref(param, sym_type); param_value_tmp = rb_hash_aref(param, sym_value); if(param_value_tmp == Qnil) param_value = param_value_tmp; else param_value = rb_obj_as_string(param_value_tmp); param_format = rb_hash_aref(param, sym_format); } ! // ... } } ext/pg_connection.c
  53. static VALUE pgconn_exec_params( int argc, VALUE *argv, VALUE self )

    { // ... ! for ( i = 0; i < nParams; i++ ) { param = rb_ary_entry(params, i); if (TYPE(param) == T_HASH) { param_type = rb_hash_aref(param, sym_type); param_value_tmp = rb_hash_aref(param, sym_value); if(param_value_tmp == Qnil) param_value = param_value_tmp; else param_value = rb_obj_as_string(param_value_tmp); param_format = rb_hash_aref(param, sym_format); } ! // ... } } ext/pg_connection.c param_value_tmp.to_s;
  54. class Person < Norm::Record attribute :id, Attr::Integer attribute :name, Attr::String

    attribute :age, Attr::Integer attribute :created_at, Attr::Timestamp attribute :updated_at, Attr::Integer end ! person = Person.new(:name => 'Ernie', :age => 36) # => #<Person #<Person::Attributes # id: <DEFAULT>, name: "Ernie", age: 36, # created_at: <DEFAULT>, updated_at: <DEFAULT> # >> person.stored? # => false person.age += 1 # => 37 person.updated_attributes # => {:age => 37} Norm::Record
  55. module Norm class Connection # ... def exec_statement(stmt, result_format =

    0, &block) handling_errors do sql = finalize_placeholders(stmt.sql) @db.exec_params(sql, stmt.params, result_format) do |result| yield result, self if block_given? end end end # ... end end Norm::Connection
  56. Norm::SQL::* Default query objects to make you feel at home.

    query = Norm::SQL.insert(:people, [:name, :age]). values('Ernie', 36). returning('*') # => #<Norm::SQL::Insert:...> puts query.sql # => INSERT INTO "people" ("name", "age") # VALUES ($?, $?) # RETURNING * query.params # => ["Ernie", 36]
  57. Norm::Repository class PersonRepository < Norm::PostgreSQLRepository ! def select_statement Norm::SQL.select.from('people') end

    ! def insert_statement Norm::SQL.insert(:people, attribute_names).returning('*') end ! def update_statement Norm::SQL.update('people').returning('*') end ! def delete_statement Norm::SQL.delete('people').returning('*') end ! end
  58. Norm::Repository repo = PersonRepository.new(Person) person = Person.new(:name => 'Ernie', :age

    => 36) repo.insert(person) repo.fetch(person.id) #all #fetch(*primary_key(s)) #insert(record) #update(record) #store(record) #delete(record) #mass_insert(records) #mass_update(records, attrs) #mass_delete(records) Concepts:! records in/out connection checkout reader/writer connections atomicity result processing strategy
  59. Norm::RecordDelegator class AuthenticatingUser include Norm::RecordDelegator(User, railsify: true) ! attr_reader :password

    attr_accessor :password_confirmation, :original_password validates :password, :confirmation => true validates :password, :length => { :minimum => 6 }, :if => :setting_password? validate :original_password_valid?, :if => :updating_password? ! def self.authenticate(username, password) if user = UserRepository.new(self).with_username(username) user if BCrypt::Password.new(user.password_digest) == password end end ! def password=(unencrypted_password) @password = unencrypted_password.to_s if @password.present? self.password_digest = BCrypt::Password.create(unencrypted_password) end end ! # ... end
  60. Norm::RecordDelegator class AuthenticatingUser include Norm::RecordDelegator(User, railsify: true) ! attr_reader :password

    attr_accessor :password_confirmation, :original_password validates :password, :confirmation => true validates :password, :length => { :minimum => 6 }, :if => :setting_password? validate :original_password_valid?, :if => :updating_password? ! def self.authenticate(username, password) if user = UserRepository.new(self).with_username(username) user if BCrypt::Password.new(user.password_digest) == password end end ! def password=(unencrypted_password) @password = unencrypted_password.to_s if @password.present? self.password_digest = BCrypt::Password.create(unencrypted_password) end end ! # ... end
  61. class UsersController < ApplicationController before_filter :require_authentication!, :except => [:new, :create]

    ! def new @user = AuthenticatingUser.new end ! def create @user = AuthenticatingUser.new.set(user_params_for_create) if repository.insert(@user) session[:user_id] = @user.id redirect_to my_plan_path end end ! def edit @user = repository.fetch(session[:user_id]) end ! def update @user = repository.fetch(session[:user_id]) @user.set(user_params_for_update) if repository.update(@user) redirect_to my_profile_path end end ! # ... ! def repository @repository ||= UserRepository.new( AuthenticatingUser, processor: ValidatingProcessor.new(AuthenticatingUser) ) end end