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

Keep your ActiveRecord models manageable the Rails way

Keep your ActiveRecord models manageable the Rails way

Rails is awesome! It makes very easy and fun to start new projects. However, as your application grows, you will eventually need to come off the Rails. Otherwise your codebase will become completely unmanageable. Everyone knows that.

You'll need presenters and a service layer, including role and use-case objects. DCI will be great too or, alternatively, you can go Hexagonal. After all, the web is just a delivery mechanism, the database is a mere persistence strategy and, of course, “Rails is a detail”.

But… Wait a minute! Is all that really true? Does the Golden Path no longer work as your application becomes larger? How is it, then, that Rails claims to be “optimised for sustainable productivity”?

Criticising Rails is so last year! This time we'll revisit the patterns and conventions that Rails encourages and push them to the limit to see what happens. We'll seek examples of large Rails applications keeping their models manageable without derailing. We'll also discuss the trade-offs of running on and off the Rails. And, maybe, we'll finally learn how to stop worrying and love the Rails way!

Luismi Cavallé

June 07, 2013
Tweet

More Decks by Luismi Cavallé

Other Decks in Programming

Transcript

  1. require_dependency,'slug' require_dependency,'avatar_lookup' require_dependency,'topic_view' require_dependency,'rate_limiter' require_dependency,'text_sentinel' require_dependency,'text_cleaner' class,Topic,<,ActiveRecord::Base ,,include,ActionView::Helpers ,,include,RateLimiter::OnCreateRecord ,,def,self.max_sort_order

    ,,,,2**31,7,1 ,,end ,,def,self.featured_users_count ,,,,4 ,,end ,,versioned,if:,:new_version_required? ,,acts_as_paranoid ,,after_recover,:update_flagged_posts_count ,,after_destroy,:update_flagged_posts_count ,,rate_limit,:default_rate_limiter ,,rate_limit,:limit_topics_per_day https://github.com/discourse/discourse/blob/master/app/models/topic.rb
  2. 431

  3. !!!!end !!!!def!pending_review !!!!!!where!:state!=>!'pending_review' !!!!end !!!!def!spam !!!!!!where!:state!=>!'spam' !!!!end !!!! !!!!def!tagged_with(tag) !!!!!!joins(:taggings!=>!:tags).where(:tags!=>!{!:name!=>!tag!})

    !!!!end !!!!def!visible !!!!!!joins(:topic).where(:forem_topics!=>!{!:hidden!=>!false!}) !!!!end !!end !! !!def!tag_names=(tag_list) !!!!assign_tag_list!tag_list !!end !!private
  4. !!class!Article!<!ActiveRecord::Base !!!!attr_accessor!:moderation_option ! !!!!attr_accessible!:text,!:reply_to_id,!:tag_names ! !!!!belongs_to!:topic !!!!belongs_to!:user,!!!!!:class_name!=>!Forem.user_class.to_s !!!!belongs_to!:reply_to,!:class_name!=>!"Post" ! !!!!has_many!:replies,!:class_name!!=>!"Post",

    !!!!!!!!!!!!!!!!!!!!!!!:foreign_key!=>!"reply_to_id", !!!!!!!!!!!!!!!!!!!!!!!:dependent!!!=>!:nullify !!!! !!!!has_many!:taggings !!!!has_many!:tags,!:through!=>!:taggings ! !!!!validates!:text,!:presence!=>!true ! !!!!delegate!:forum,!:to!=>!:topic ! !!!!after_create!:set_topic_last_post_at !!!!after_create!:skip_pending_review !!!!after_save!:email_topic_subscribers ! !!!!class!<<!self !!!!!!def!by_created_at !!!!!!!!order!:created_at !!!!!!end ! !!!!!!def!pending_review !!!!!!!!where!:state!=>!'pending_review' !!!!!!end ! !!!!!!def!spam !!!!!!!!where!:state!=>!'spam' !!!!!!end !!!!!! !!!!!!def!tagged_with(tag) !!!!!!!!joins(:taggings!=>!:tags).where(:tags!=>!{!:name!=>!tag!}) !!!!!!end ! !!!!!!def!visible !!!!!!!!joins(:topic).where(:forem_topics!=>!{!:hidden!=>!false!}) !!!!!!end !!!!end !!!! !!!!def!tag_names=(tag_list) !!!!!!assign_tag_list!tag_list !!!!end ! !!!!private ! !!!!def!subscribe_replier !!!!!!if!topic!&&!user !!!!!!!!topic.subscribe_user(user.id) !!!!!!end !!!!end ! !!!!def!set_topic_last_post_at !!!!!!topic.update_attribute(:last_post_at,!created_at) !!!!end ! !!!!def!blacklist_user !!!!!!user.update_attribute(:forem_state,!"spam")!if!user !!!!end !!!! !!!!def!assign_tag_list(tag_list) !!!!!!tag_names!=!tag_list.gsub(/\s+/,!"").split(",") !!!!!!existing!=!self.tags.map!{|t|!t.name!} !!!!!!(existing!@!tag_names).each!do!|name| !!!!!!!!self.tags.delete!Tag.find_by_name(name) !!!!!!end !!!!!!tag_names.each!do!|name| !!!!!!!!self.tags!<<!Tag.find_or_create_by_name(name) !!!!!!end! !!!!end !!end
  5. module!Taggable !!extend!ActiveSupport::Concern !!included!do !!!!has_many!:taggings !!!!has_many!:tags,!through:!:taggings !!!!scope!:tagged_with,!B>(tag_name)!do !!!!!!joins(:tags).where(tags:!{!name:!tag_name!})! !!!!end !!end !!def!tag_list

    !!!!tags.pluck(:name).join(',!') !!end !!def!tag_list=(tag_list) !!!!assign_tag_list!tag_list !!end !!private !!def!assign_tag_list(tag_list) !!!!tag_names!=!tag_list.split(',').map(&:strip).uniq !!!!self.tags!=!tag_names.map!do!|tag_name|! !!!!!!Tag.where(name:!tag_name).first_or_initialize !!!!end !!end end
  6. module!Taggable !!extend!ActiveSupport::Concern !!included!do !!!!has_many!:taggings !!!!has_many!:tags,!through:!:taggings !!!!scope!:tagged_with,!B>(tag_name)!do !!!!!!joins(:tags).where(tags:!{!name:!tag_name!})! !!!!end !!end !!def!tag_list

    !!!!tags.pluck(:name).join(',!') !!end !!def!tag_list=(tag_list) !!!!assign_tag_list!tag_list !!end !!private !!def!assign_tag_list(tag_list) !!!!tag_names!=!tag_list.split(',').map(&:strip).uniq !!!!self.tags!=!tag_names.map!do!|tag_name|! !!!!!!Tag.where(name:!tag_name).first_or_initialize !!!!end !!end end
  7. .JYJO *OIFSJUBODF JT B DPNNPO NFUIPE PG SFVTF JO PCKFDUPSJFOUFE

    TPGUXBSF 3VCZ TVQQPSUT TJOHMF JOIFSJUBODF VTJOH TVCDMBTTFT BOE NVMUJQMF JOIFSJUBODF VTJOH NJYJOT .JYJOT DBO CF VTFE UP QBDLBHF DPNNPO IFMQFST PS QSPWJEF B DPNNPO QVCMJD JOUFSGBDF )PXFWFS NJYJOT IBWF TPNF ESBXCBDLT t 5IFZ VTF UIF TBNF OBNFTQBDF BT DMBTTFT UIFZSF NJYFE JOUP XIJDI DBO DBVTF OBNJOH DPOnJDUT t "MUIPVHI UIFZ IBWF BDDFTT UP JOTUBODF WBSJBCMFT GSPN DMBTTFT UIFZSF NJYFE JOUP NJYJOT DBOU FBTJMZ BDDFQU JOJUJBMJ[FS BSHVNFOUT TP UIFZ DBOU IBWF UIFJS PXO TUBUF t 5IFZ JOnBUF UIF OVNCFS PG NFUIPET BWBJMBCMF JO B DMBTT t 5IFZSF OPU FBTZ UP BEE BOE SFNPWF BU SVOUJNF https://learn.thoughtbot.com/products/13-ruby-science https://learn.thoughtbot.com/products/13-ruby-science
  8. *OIFSJUBODF JT B DPNNPO NFUIPE PG SFVTF JO PCKFDUPSJFOUFE TPGUXBSF

    3VCZ TVQQPSUT TJOHMF JOIFSJUBODF VTJOH TVCDMBTTFT BOE NVMUJQMF JOIFSJUBODF VTJOH NJYJOT .JYJOT DBO CF VTFE UP QBDLBHF DPNNPO IFMQFST PS QSPWJEF B DPNNPO QVCMJD JOUFSGBDF )PXFWFS NJYJOT IBWF TPNF ESBXCBDLT t 5IFZ VTF UIF TBNF OBNFTQBDF BT DMBTTFT UIFZSF NJYFE JOUP XIJDI DBO DBVTF OBNJOH DPOnJDUT t "MUIPVHI UIFZ IBWF BDDFTT UP JOTUBODF WBSJBCMFT GSPN DMBTTFT UIFZSF NJYFE JOUP NJYJOT DBOU FBTJMZ BDDFQU JOJUJBMJ[FS BSHVNFOUT TP UIFZ DBOU IBWF UIFJS PXO TUBUF t 5IFZ JOnBUF UIF OVNCFS PG NFUIPET BWBJMBCMF JO B DMBTT t 5IFZSF OPU FBTZ UP BEE BOE SFNPWF BU SVOUJNF t 5IFZSF EJʺDVMU UP UFTU JO JTPMBUJPO TJODF UIFZ DBOU CF JOTUBOUJBUFE 4ZNQUPNT https://learn.thoughtbot.com/products/13-ruby-science https://learn.thoughtbot.com/products/13-ruby-science
  9. TVQQPSUT TJOHMF JOIFSJUBODF VTJOH TVCDMBTTFT BOE NVMUJQMF JOIFSJUBODF VTJOH NJYJOT

    .JYJOT DBO CF VTFE UP QBDLBHF DPNNPO IFMQFST PS QSPWJEF B DPNNPO QVCMJD JOUFSGBDF )PXFWFS NJYJOT IBWF TPNF ESBXCBDLT t 5IFZ VTF UIF TBNF OBNFTQBDF BT DMBTTFT UIFZSF NJYFE JOUP XIJDI DBO DBVTF OBNJOH DPOnJDUT t "MUIPVHI UIFZ IBWF BDDFTT UP JOTUBODF WBSJBCMFT GSPN DMBTTFT UIFZSF NJYFE JOUP NJYJOT DBOU FBTJMZ BDDFQU JOJUJBMJ[FS BSHVNFOUT TP UIFZ DBOU IBWF UIFJS PXO TUBUF t 5IFZ JOnBUF UIF OVNCFS PG NFUIPET BWBJMBCMF JO B DMBTT t 5IFZSF OPU FBTZ UP BEE BOE SFNPWF BU SVOUJNF t 5IFZSF EJʺDVMU UP UFTU JO JTPMBUJPO TJODF UIFZ DBOU CF JOTUBOUJBUFE 4ZNQUPNT t .FUIPET JO NJYJOT UIBU BDDFQU UIF TBNF QBSBNFUFST PWFS BOE PWFS https://learn.thoughtbot.com/products/13-ruby-science https://learn.thoughtbot.com/products/13-ruby-science
  10. !!def!persisted? !!!!false !!end !!def!save !!!!if!valid? !!!!!!persist! !!!!!!true !!!!else !!!!!!false !!!!end

    !!end private !!def!persist! http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
  11. !!!!!!true !!!!else !!!!!!false !!!!end !!end private !!def!persist! !!!!@company!=!Company.create!(name:!company_name) !!!!@[email protected]!(name:!name,!email:!email) !!end

    end class!SignupsController!<!ApplicationController !!def!create !!!!@signup!=!Signup.new(params[:signup]) [email protected] !!!!!!redirect_to!dashboard_path !!!!else !!!!!!render!"new" http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
  12. class!Signup !!extend!ActiveModel::Naming !!include!ActiveModel::Conversion !!include!ActiveModel::Validations !!attr_accessor!:name !!attr_accessor!:company_name !!attr_accessor!:email !!validates!:email,!presence:!true !!#"…"more"validations"… !!#"Virtual"models"are"never"themselves"persisted

    !!def!persisted? !!!!false !!end !!def!save !!!!if!valid? !!!!!!persist! !!!!!!true !!!!else !!!!!!false !!!!end !!end private !!def!persist! !!!!@company!=!Company.create!(name:!company_name) !!!!@[email protected]!(name:!name,!email:!email) !!end end class!SignupsController!<!ApplicationController !!def!create http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/