-
-
Save modsaid/8273218 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # If the last parameter in a method definition is prefixed with an ampersand, any associated block | |
| # is converted to a Proc object and that object is assigned to the parameter. | |
| # It must be the last argument in the parameter list | |
| def do_math_operation(amount, &block) | |
| block.call(amount) # OR yield(amount) | |
| end | |
| result = do_math_operation(5) {|amount| amount * 2} | |
| => 10 | |
| # A proc is a reusable object oriented block , a block is actually a proc that can't be saved | |
| block.class => Proc | |
| # The same above can be done using proc | |
| def do_math_operation(amount, proc) | |
| block.call(amount) # yield won't work! | |
| end | |
| multiply_by_2 = Proc.new do |n| | |
| n * 2 | |
| end | |
| result = do_math_operation(5, multiply_by_2) | |
| => 10 | |
| # The same above can be done using lambda | |
| def do_math_operation(amount, lambda) | |
| block.call(amount) # yield won't work! | |
| end | |
| multiply_by_2 = lambda{ |n| n * 2 } | |
| result = do_math_operation(5, multiply_by_2) | |
| => 10 | |
| # Differences between Lambda and Procs | |
| # 1- Lambda checks for number of parameters , throw an exception if less or more parameters were passed | |
| # ex: | |
| lambda = lambda{ |str1, str2| "#{str1}, #{str2}"} | |
| lambda.call('str1', 'str2') | |
| => str1, str2 | |
| lambda.call('str2','str2','str3') | |
| ArgumentError: wrong number of arguments (3 for 2) | |
| proc = Proc.new{ |str1, str2| "#{str1}, #{str2}" } | |
| proc.call("str1") | |
| => str1, | |
| # 2- Lambda in a method will return the value to the method and the method continues normally, while Proc stops method execution | |
| def proc_return | |
| Proc.new{ |n| puts n } | |
| puts 'see me if you can' | |
| end | |
| def lambda_return | |
| lambda{ |n| puts n } | |
| puts 'hi, i am here' | |
| end | |
| proc_return | |
| => Proc | |
| lambda_return | |
| => hi, i am here | |
| # Note proc can't have a return in it , while lambda can | |
| # The same can be done using method objects | |
| def do_math_operation(amount, method) | |
| block.call(amount) # yield won't work! | |
| end | |
| def multiply_by_2(n) | |
| n * 2 | |
| end | |
| result = do_math_operation(5, method(:multiply_by_2)) | |
| => 10 | |
| # Method objects will act like lambda , only lambda is anonymous |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # app/models/concerns/users/csv_conversion.rb | |
| class User | |
| module CsvConversion | |
| extend ActiveSupport::Concern | |
| included do | |
| def to_csv(options = {}) | |
| CSV.generate(options) do |csv| | |
| csv << %w[id username email] | |
| all.each do |user| | |
| csv << [user.id, user.username, user.email] | |
| end | |
| end | |
| end | |
| end | |
| end | |
| end | |
| # app/models/user.rb | |
| include CsvConversion | |
| # Use modules for more complex and widely used logic | |
| # Using modules/plugins | |
| # in lib/plugins/sponsorable.rb | |
| module Sponsorable | |
| extend ActiveSupport::Concern | |
| module ClassMethods | |
| def acts_as_sponsorable(configuration = {}) | |
| # Do your logic and define the needed associations ex: product has many sponsors, | |
| # A sponsor may sponsor many products , yes a polymorphic association | |
| end | |
| # If you forgot this line all instances of ActiveRecord::Base will have these methods ! | |
| include InstanceMethods | |
| end | |
| module InstanceMethods | |
| def sponsors_count | |
| end | |
| end | |
| end | |
| ActiveRecord::Base.send(:include, Sponsorable) | |
| # config/initializers/extension.rb | |
| require 'sponsorable' | |
| # app/models/product.rb | |
| acts_as_sponsorable({ max_count: 5, sponsors_type: 'exclusive' }) | |
| # Use modules/plugins for shared complex logic and use concerns for model related simple logic |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Imagine we have this logic on stackoverflow: | |
| # A user can post a question on a stackoverflow, after posting an email notification is sent to | |
| # all users have interest in the question topic, a post is shared on the user profile with a link to the question he posted | |
| class SOFQuestionNotifier | |
| def initialize(question) | |
| @question = question | |
| end | |
| def save | |
| @question.save && post_to_wall && queue_emails | |
| end | |
| private | |
| def post_to_wall | |
| end | |
| def queue_emails | |
| end | |
| end | |
| # app/controllers/questions_controller | |
| def create | |
| @question = SOFQuestionNotifier.new(Question.new(params[:question])) | |
| if @question.save | |
| # Success | |
| else | |
| # Error | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Law Of Demeter: Every unit should have limited knowledge about other units, simply don't talk to strangers! | |
| # BAD | |
| class Invoice < ActiveRecord::Base | |
| belongs_to :user | |
| end | |
| invoice.user.name | |
| # Good | |
| class Invoice < ActiveRecord::Base | |
| belongs_to :user | |
| delegate :name, to: :user, prefix: true | |
| end | |
| invoice.user_name |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class ContactForm | |
| include ActiveModel::Validations | |
| include ActiveModel::Conversions | |
| attr_accessor :name, :email, :body | |
| validates_presence_of :name, :email, :body | |
| def initialize(attributes = {}) | |
| attributes.each do |name, value| | |
| send("#{name}=",value) | |
| end | |
| end | |
| def persisted? | |
| false | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Imagine that your application requires a layout that differs slightly from your regular application layout to support one particular controller | |
| # The solution is nested layout! | |
| #inside your different layout controller add: | |
| layout 'custom' | |
| # app/views/layouts/custom.html.erb | |
| <% content_for :stylesheets do %> | |
| <%= stylesheet_link_tag "custom" %> | |
| <% end %> | |
| <% content_for :content do %> | |
| <div id="right_menu">Right menu items here</div> | |
| <%= yield %> | |
| <% end %> | |
| <%= render "layouts/application" %> | |
| # app/views/layouts/application.html.erb | |
| <html> | |
| <head> | |
| <title>My Application</title> | |
| <%= stylesheet_link_tag "layout" %> | |
| <style><%= yield :stylesheets %></style> | |
| </head> | |
| <body> | |
| <div id="top_menu">Top menu items here</div> | |
| <div id="menu">Menu items here</div> | |
| <div id="content"><%= content_for?(:content) ? yield(:content) : yield %></div> | |
| </body> | |
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Avoid N+1 Queries | |
| # Example Application: | |
| # User has many followers | |
| # BAD | |
| user.followers.collect{ |f| f.user.name } | |
| # Queries generated | |
| # select followers where user_id = 1 | |
| # select user where id=2 | |
| # select user where id=3 | |
| # .................. | |
| # Good | |
| user.followers.includes(:user){ |f| f.user.name } | |
| # Queries generated | |
| # select followers where user_id = 1 | |
| # select user where id in(2,3,..) | |
| # Counter Caching | |
| # we want to get the number of followers of a user | |
| # BAD | |
| user.followers.length # Load the followers into array and then call .length on that array | |
| # Better | |
| user.followers.size OR user.followers.count # make a count query to the db | |
| # What if the above query is made in a loop? many queries right? | |
| # Best using counter caching | |
| # Add column followers_count to the user model | |
| belongs_to "user", class_name "User", foreign_key: "user_id", counter_cache: :followers_count | |
| user.followers.size # no query , only look at the cache |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # app/policies/twitter_policy.rb | |
| class TwitterPolicy < Struct.new(:auth) | |
| def first_name | |
| auth['info']['name'].split(' ').first | |
| end | |
| def last_name | |
| ..... | |
| end | |
| .... | |
| end | |
| # app/policies/facebook_policy.rb | |
| class FacebookPolicy < Struct.new(:auth) | |
| def first_name | |
| auth['info']['name'].split(' ').first | |
| end | |
| def last_name | |
| ..... | |
| end | |
| .... | |
| end | |
| # app/models/user.rb | |
| def self.from_oauth(auth) | |
| policy = "#{auth['provider']}_policy".classify.constantize.new(auth) | |
| create_user_from_policy(policy) | |
| end | |
| # check https://github.com/elabs/pundit for maximum usage of policies |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Presenters are created by Controllers/Views and know the current state of the view and the model | |
| # Can be used to filter away out of tangled logic in the view | |
| # Also can be used to skin the controller | |
| # An example of a presenter used to remove logic from view(this is a small example , views can get it even more bloated with logic) | |
| # app/views/categories/index.html | |
| <div class="images"> | |
| <%if @category.image_url%> | |
| <%= link_to image_tag("/images/#{category.id}.png") + "some extra text", category_path(@category), :class => 'class-name', :remote => true %> | |
| <%else%> | |
| <%= link_to image_tag("/images/default.png") + "some extra text", category_path(@category), :class => 'class-name', :remote => true %> | |
| <%end%> | |
| <div> | |
| # app/presenters/category_presenter.rb | |
| class CategoryPresenter < BasePresenter | |
| presents :category | |
| def top_brands | |
| @top_brands ||= category.top_brands | |
| end | |
| def top_keywords | |
| @top_keywords ||= category.top_keywords | |
| end | |
| def featured_image | |
| image_path = category.image_url ? category.id : "default" | |
| link_to image_tag("/images/#{image_path}.png") + "some extra text", category_path(@category), :class => 'class-name', :remote => true | |
| end | |
| end | |
| class BasePresenter | |
| def initialize(object, template) | |
| @object = object | |
| @template = template | |
| end | |
| def self.presents(name) | |
| define_method(name) do | |
| @object | |
| end | |
| end | |
| def method_missing?(*args, &block) | |
| @template.send(*args, &block) | |
| end | |
| end | |
| # app/views/category/index.html | |
| <% present @category do |category_presenter|%> | |
| <div class="images"> | |
| <%= category_presenter.featured_image%> | |
| <div> | |
| <% end %> | |
| # app/helpers/application_helper.rb | |
| def present(object, klass = nil) | |
| klass ||= "#{object.class}Presenter".constantize | |
| presenter = object.new(object, self) | |
| yield presenter if block_given? | |
| presenter | |
| end | |
| # To access presenters from controller | |
| # app/controllers/application_controller.rb | |
| private | |
| def present(object, klass = nil) | |
| klass ||= "#{object.class}Presenter".constantize | |
| klass.new(view_context, object) | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Example Application: | |
| # Product: name, price, brand, slug, description, delete_flag, image_url | |
| # Category: name,slug | |
| # Product belongs to Category | |
| # Use scopes instead of chained queries scattered all over your application | |
| # Get all available apple products with price > 1000 and not older than 1 week | |
| # BAD | |
| products = Product.where(brand: "apple").where("price > ?", 1000).where(delete_flag: false).where("created_at > ?", 1.week.ago) | |
| # GOOD | |
| scope :price_bigger_than, -> { |min_price| where("price > ?", min_price) } # Yes you can pass parameter to scope | |
| scope :for_brand, -> { |brand| where(brand: brand) } | |
| default_scope :un_deleted, where(delete_flag: false) | |
| scope :new, -> { |start_date = 1.week.ago| where("created_at > ?", start_date) } # Yes the scope parameter can have a default | |
| default_scope :available, where(delete_flag: false) | |
| default_scope :unavailable, where(delete_flag: true) | |
| products = Product.for_brand("apple").price_bigger_than(1000).available.new | |
| # What if we want to override the default scope?! | |
| # Get all apple unavailable products | |
| products = Product.for_brand("apple") # Wont work, will get only the available | |
| products = Product.for_brand("apple").unavailable # Wont work too, will get only the available | |
| products = Product.unscoped.for_brand("apple").unavailable # works perfectly (Y) | |
| # Now we want to get products updated today | |
| # One can make | |
| scope :most_updated, where("updated_at > ?", Date.today) # Watch Out!!! | |
| # A scope is defined at initialisation, so what you start up your server, console, or in my case the DelayedJob worker, Date.today gets executed once and a scope is created called due that will return all the Books that are due on the day you start the process | |
| # This must be me done using Lambda OR without scopes | |
| scope :most_updated, -> { where("updated_at > ?", Date.today) } | |
| # Note: -> AND lambda are equivalent | |
| # You can define scope dynamically | |
| # Example for an api with api_keys | |
| Status = {:normal => 0, :whitelisted => 1, :blacklisted => 2} | |
| Status.each_pair do |k,v| | |
| scope "status_#{k}", where(:status => v) # ApiKey.status_whitelisted,... | |
| define_singleton_method "status_#{k}?" do |st| # ApiKey.status_whitelisted?(status),... | |
| v == st | |
| end | |
| define_method "status_#{k}?" do # api_key.status_whitelisted?,... | |
| self.status == v | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| v# User service object when: | |
| # 1- The action is complex | |
| # 2- The action reaches across multiple models (e.g. an e-commerce purchase using Order, CreditCard and Customer objects) | |
| # 3- The action interacts with an external service (e.g. posting to social networks) | |
| # 4- The action is not a core concern of the underlying model (e.g. sweeping up outdated data after a certain time period). | |
| # 5- There are multiple ways of performing the action (e.g. authenticating with an access token or password). | |
| class UserCSVDump < Struct.new(:users) | |
| def csv | |
| CSV.generate do |csv| | |
| csv << %w[id username email] | |
| users.each do |user| | |
| csv << [user.id, user.username, user.email] | |
| end | |
| end | |
| end | |
| end | |
| # Another example would be a class to manage authentication |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| s# Not good | |
| class User < ActiveRecord::Base | |
| validates :appropriate_content | |
| def appropriate_content | |
| unless # some validation check on name | |
| self.errors.add(:name, "is inappropriate") | |
| end | |
| end | |
| end | |
| # Better | |
| require 'appropriate_validator' | |
| class User < ActiveRecord::Base | |
| validate :name, appropriate: true | |
| end | |
| # /lib/appropriate_validator | |
| class AppropriateValidator < ActiveRecord::EachValidator | |
| def validate_each(record, attr, value) | |
| unless # some validation check on value | |
| record.errors.add(:attr, "is inappropriate") | |
| end | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment