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

Concerned about Concerns?

Concerned about Concerns?

Presented at LRUG (London) on September 2013

With Rails 4, Concerns have become the “official” solution to the big-models problem. However, there's a fair amount of controversy about this topic in the community. Not everyone is convinced that Concerns are the “right“ solution to the problem of AR models becoming too big.

In this talk we will see what Rails Concerns are and how can we use them to keep our models fit. More interestingly, we'll discuss the trade-offs of this technique, the different views in the community and a couple of alternatives.

Luismi Cavallé

September 09, 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. WARNING THE FOLLOWING SLIDE MAY BE FOUND DISTURBING BY SOME

    VIEWERS WARNING THE FOLLOWING SLIDE MAY BE FOUND DISTURBING BY SOME VIEWERS
  6. module DogFort def call_dog puts "this is dog!" end end

    class Dog include DogFort end http://schneems.com/post/21380060358/concerned-about-code-reuse
  7. module M extend ActiveSupport::Concern included do scope :disabled, where(:disabled =>

    true) end module ClassMethods # class methods end # instance methods end http://api.rubyonrails.org/classes/ActiveSupport/Concern.html
  8. module Taggable extend ActiveSupport::Concern included do has_many :taggings has_many :tags,

    through: :taggings scope :tagged_with, ->(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
  9. module Taggable extend ActiveSupport::Concern included do has_many :taggings has_many :tags,

    through: :taggings scope :tagged_with, ->(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
  10. .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
  11. *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
  12. 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
  13. http://slides.jcoglan.com/di-eurucamp#13 class Github::Client def initialize(http_client) @http = http_client end def

    get_user(name) data = @http.get("/users/#{name}").json_data Github::User.new(data) end end
  14. http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/ class UserAuthenticator def initialize(user) @user = user end def

    authenticate(unencrypted_password) return false unless @user if BCrypt::Password.new(@user.password_digest) == unencrypted_password @user else false end end end
  15. https://gist.github.com/brynary/4670393 user!=!User.where(email:!params[:email]).first UserAuthenticator.new(user).authenticate(params[:password]) class!ActiveUserPolicy !!LOGIN_PERIOD!=!14.days !! !!def!initialize(user) !!!!@user!=!user !!end !!def!active?

    [email protected]_confirmed?!&& [email protected]_login_at!<!LOGIN_PERIOD.ago !!end !!class!Query !!!!def!initialize(relation!=!User.scoped) !!!!!!@relation!=!relation !!!!end !!!!def!find_each(&block) !!!!!!@relation. !!!!!!!!where(email_confirmed:!true). !!!!!!!!where('last_login!<!?',!LOGIN_PERIOD.ago). !!!!!!!!find_each(&block) !!!!end !!end end ActiveUserPolicy.active?(user)
  16. module User::Active extend ActiveSupport::Concern included do scope :active, -> do

    where(email_confirmed: true). where('last_login < ?', 14.days.ago) end end def active? user.email_confirmed? && user.last_login_at < 14.days.ago end end https://gist.github.com/cavalle/4660239
  17. https://gist.github.com/brynary/4670393 user!=!User.where(email:!params[:email]).first UserAuthenticator.new(user).authenticate(params[:password]) class!ActiveUserPolicy !!LOGIN_PERIOD!=!14.days !! !!def!initialize(user) !!!!@user!=!user !!end !!def!active?

    [email protected]_confirmed?!&& [email protected]_login_at!<!LOGIN_PERIOD.ago !!end !!class!Query !!!!def!initialize(relation!=!User.scoped) !!!!!!@relation!=!relation !!!!end !!!!def!find_each(&block) !!!!!!@relation. !!!!!!!!where(email_confirmed:!true). !!!!!!!!where('last_login!<!?',!LOGIN_PERIOD.ago). !!!!!!!!find_each(&block) !!!!end !!end end ActiveUserPolicy.active?(user)
  18. class Commenter def initialize(person) @person = person end def recent_comments

    @person.comments.where("created_at > ?", 3.days.ago) end def post_comment(text) @person.comments.create(:body => text) end def update_comment(new_text) raise "Comment not owned by this person" unless comment.author == @person comment.update_attribute(:body, new_text) end end https://gist.github.com/4341122