Query through Active Record Associations.

 

While working on an Ad Server for a current client which I've thoroughly enjoyed writing. I came across some interesting techniques to efficiently query the database using some advanced active record.

These won't be a surprise to the advanced rails developer and I've been using it pretty often in my queries for a while now.

Take the below active record model.

    class Campaign < ActiveRecord::Base

      has_many :advertisements, dependent: :destroy

      has_many :ad_targetings, dependent: :destroy

      has_many :ad_targets, through: :ad_targetings

    end

    class Advertisement < ActiveRecord::Base

      belongs_to :campaign

    end

The ad class has some additionally associations to keep track of ad stats like click counts and impressions counts.

The client and I decided on having the campaigns be the main targeting object where we can target our users with specific criteria and serve them the appropriate ad.

So normally you could pull all advertisements matching the requested targeting params by searching all active campaigns. The system then would subsequently pool all those campaigns' ads into one array. This would cause two queries or one eager loaded query with some additionaly computation to aggregate the ads.

Nah, that's too much work and pretty inelegant, so allow me to present you with a way to query for advertisements with a where clause on it's association.

This technique is essentially querying through an association and it's pretty flexible. It's all active record syntax and one could even go further with SQL strings.

Example query.

    scope :by_target, ->(t) {

      joins(campaign: [:ad_targets])

      .where(ad_targets: {name: t})

    }

 


STI with Rails 4.0 beta and Devise

Two weeks of consecutive articles? Nice, I'm actually sticking with this blog for once. Now lets get to the business at hand.

I recently setup Devise with Rails 4.0 and I wanted to show the interwebs how I accomplished while using single table inheritance in my user models. It was surprisingly easier then I thought it would be. Rails 4 has improved their STI support, so a good amount of custom code needed is not necessary anymore.

Gemfile

ruby 2.0.0-p0
gem 'rails', '4.0.0.beta1'
......
gem 'devise', git: "git://github.com/plataformatec/devise.git", branch: "rails4"
gem 'protected_attributes'

We are using the Rails 4 branch from the Devise repo and the protected attributes gem to avoid overriding the default Devise controllers. Of course normally this is not a dangerous thing to do but I preferred to not have to write a bunch of custom routes and keep my routes simpler. Also, we can still use the strong parameters with our other models which I'm actually liking, only our User model will have some attr_accessible code. But, if you so choose to not use protected attributes gem, I will show you you how to override the necessary Devise functions but most likely they will have updated their code to not use protected attributes.

Models

Given you've a similar situation as below:

class User < ActiveRecord::Base
end

class Admin < User
end

class Seller < User
end

class Buyer < User
end

run the devise generator, note: this will fail without the necassary Devise rails4 branch.

rails g devise:install

Your User.rb model should be like so now:

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :token_authenticatable, :confirmable,
  # :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
     :recoverable, :rememberable, :trackable, :validatable

  attr_accessible :name, :email, :password, :password_confirmation
end

Routes

This is the most important part and using the default Devise helpers we can accomplish what we want. Note how I've namespaced the user scopes. Feel free to customize to your needs, but you should have something similar to the below.

  devise_for :users, skip: [:registrations]
  devise_for :admins, skip: [:sessions, :registrations]
  devise_for :sellers, skip: :sessions
  devise_for :buyers, skip: :sessions

  namespace :admin do
    root to: "home#index"
    resources :sellers
    resources :profile, only: [:edit, :update]
  end
  namespace :seller do
    root to: "home#index"
    resources :profile, only: [:edit, :update]
  end
  namespace :buyer do
    root to: "home#index"
    resources :profile, only: [:edit, :update]
  end

Of course you do not need to do namespace your user subclasses, it just makes more sense to me and gives me a good base to keep my views more OO and organized down the road.

Now in my situation, only sellers and buyers will be able to register but all of our subclasses need links to be generated to the Devise controllers but we want to keep the session pass scoped to Users only and not generate routes for a users registrations In the past to customize the session paths "login, logout etc" you'd pass a block to the devisefor helper but this practice has been depreciated so if you still want to override those paths, you must use the devisescope helper.

For example:

devise_for :users do
  delete 'logout', to 'sessions#destroy', as :destroy_user_session
  get 'login', to: 'sessions#new', as: :new_user_session
  put 'login', to: 'sessions#create', as: :user_session
end

Doing this necessitates overriding the Devise sessions controller

'app/controllers/sessions_controller.rb

class SessionsController < Devise::SessionsController
end

Now, with our routes in place and our models setup. We're actually good, just need those default devise views

rails g devise:views

Caveat, some of the links in the views/devise/shared/_links.erb partial will throw exceptions so you'll either want to customize them or remove

Helpers

Devise gives you some default helpers to use in your controllers and views, usersignedin?, currentuser, and even currentadmin but current admin requires the base devise db model to actually be a Admin base so we'll want to override this becuase of our STI situation.

Here is an example of my application_controller.rb file

class ApplicationController < ActionController::Base

  protect_from_forgery with: :exception
  helper_method :current_admin, :current_seller, :current_buyer
                :require_admin!, :require_seller!, :require_buyer!

  def account_url
    return new_user_session_url unless user_signed_in?
    case current_user.class.name
    when "Admin"
      admin_root_url
    when "Buyer"
      carrier_root_url
    when "seller"
      seller_root_url
    else
      root_url
    end if user_signed_in?
  end

  def after_sign_in_path_for(resource)
    stored_location_for(resource) || account_url
  end

  private

    def current_admin
      @current_admin ||= current_user if user_signed_in? and current_user.class.name == "Admin"
    end

    def current_buyer
      @current_buyer ||= current_user if user_signed_in? and current_user.class.name == "Buyer"
    end

    def current_seller
      @seller_seller ||= current_user if user_signed_in? and current_user.class.name == "Seller"
    end

    def buyer_logged_in?
      @Buyer_logged_in ||= user_signed_in? and current_buyer
    end

    def admin_logged_in?
      @admin_logged_in ||= user_signed_in? and current_admin
    end

    def seller_logged_in?
      @seller_logged_in ||= user_signed_in? and current_seller
    end

    def require_admin
      require_user_type(:admin)
    end

    def require_Buyer
      require_user_type(:buyer)
    end

    def require_seller
      require_user_type(:seller)
    end

    def require_user_type(user_type)
      if (user_type == :admin and !admin_logged_in?) or
        (user_type == :seller and !seller_logged_in?) or
        (user_type == :buyer and !buyer_logged_in?)
        redirect_to root_path, status: 301, notice: "You must be logged in a#{'n' if user_type == :admin} #{user_type} to access this content"
        return false
      end
    end
end

Of course, I'll eventually want to move this into a controller concern to clean up the application controller. And notice how I'm using different "authenticateuser!" methods, I find it convenient to scope buy the subclasses in my controllers. And not how I've overridden the devise aftersignoutfor(resource)

Departing tidbits

The last techinique I wanted to show you is what I promised earlier where we wouldn't use the protected_attributes gem. Just override the devise/registrations and devise/passwords controller and add the below method to it.

#registrations controller example
def resource_params
  params.require(resource_name).permit(:email, :password, :password_confirmation)
end
private :resource_params

Make sure to specify your controller routes in the routes.rb file

devise_for :users, controllers: {
  registrations: "my_devise/registrations",
  passwords: "my_devise/passwords"
}

I look forward to your thoughts and suggestions on how to improve my example.

Harmony be onto you

Make sure to specify

Multiple Table Inheritance with Active Record

​Single Table Inheritance in Active Record is something to not be afraid of. Let me show you one of the ways it completely makes since. Throw in some Postgresql Hstore magic and you have a flexible and fairly straightforward solution to a couple different problems.

Read More

Revelations at 30

Things I know:

You'll never change anyone's mind about anything until you accept that nobody has ever considered themselves stupid.

Always be asking questions. We find truth in the things we come back to.

Human significance is largely predicated by our inability to fully grasp how insignificant … we are

Women loved to be carried but one slip and they'll never let you do so again.

We’re a community with a lot of keyboard courage.

Minimalism is a worthy pursuit.

Egotism always comes from a negative place.

The only intuitive interface is the nipple. Everything after that is learned.

The only limits are the ones we create. So, stop creating them.

I still have much to learn.

Valuable lessons.

I manage a team of two. As lead developer, I am responsible for making important design decisions. My teamates are large working on the frontend of our Rails app and they're newcomers to the wonderful world of Rails. So not only do I manage, I mentor. It has been a learning experience for me as well as for them.

I'm not always right so when I suggest a particular gem or advise a particular way of writing a particular feature, most of the time I'm correct but there will also be times I'm completely wrong. I'll find myself checking out there work and coming up against the walls they previously wrestled with and also finding myself scratching my head. I swallow my pride, admit I was wrong and move on. We'll find a solution as a time and implement in a timely manner.

I wanted to make this point, these are lessons, valuable lessons. We must learn from these lessons and just attempt to not repeat them in the future. All I can do as a mentor and show them the Rails way, provide good coding styles, and impart proper OO design.

I intend to be a stronger team leader/manager, so when I make mistakes and learn from them, my team must also learn from them.

Apple is going after Ticketmaster.

The upcoming release of iOS6 will feature an app by Apple called Passbook. It will be a stepping stone to Apples entrance into the Digital Wallet arena. A digital wallet allows users to make electronic commerce transactions quickly and securely. There are already many players in the mobile payment space: Square, Sprint, Visa, Mastercard, Amazon, AT&T, Paypal etc.. They all are developing or have developed diverse platforms utilizing different technologies.

Passbook

Passbook

Out of the gate Passbook supports the following:

  • Boarding Passes
  • Gift Cards
  • Coupons
  • Event Tickets

What Passbook will offer:

  • Groupon and Living Social vouchers
  • Possible credit cards aka Mobile payments

Passbook in the words of Apple:

Your boarding passes, movie tickets, retail coupons, loyalty cards, and more are now all in one place. With Passbook, you can scan your iPhone or iPod touch to check in for a flight, get into a movie, and redeem a coupon. You can also see when your coupons expire, where your concert seats are, and the balance left on that all-important coffee bar card. Wake your iPhone or iPod touch, and passes appear on your Lock screen at the appropriate time and place — like when you reach the airport or walk into the store to redeem your gift card or coupon. And if your gate changes after you’ve checked in for your flight, Passbook will even alert you to make sure you’re not relaxing in the wrong terminal.

Apple plays the long game. It does not release game changing apps that have an affect right off the bat. They improve on the idea through timely updates that before anyone knew it, the intended consequence come to light to the public. I believe Passport is one of those ideas by Apple. They already have a robust virtual ecosystem in the App Store. Adding an iTunes section for Passport makes perfect sense. Record companies will be able to sell tickets through the iTunes Store, completely bypassing Ticketmaster and other venues. The complete cutting out of those middlemen will reduce scalping, reduce ticket prices without hurting the artists. The business model may be tricky and negotiations will still be ongoing.

I envisage a time where I can buy my movie tickets, my Starbucks coffee, redeem my gift cards to various retailers, my Living Social vouchers, and those Buddy Guy tickets in November; all through the Apple iTunes Store.