Skip to content

Instantly share code, notes, and snippets.

@sxross
Created July 4, 2014 18:38
Show Gist options
  • Save sxross/755f4efc17a42a2ea1e1 to your computer and use it in GitHub Desktop.
Save sxross/755f4efc17a42a2ea1e1 to your computer and use it in GitHub Desktop.

This code works with the Firebase simple-auth interface. It depends on the following Gemfile:

gem 'ruby_motion_query', :git => 'git@github.com:infinitered/rmq.git'
gem 'motion-firebase'
 'motion-firebase',
 'motion-firebase-auth',
]
gem 'motion-map'

gem 'motion-callback'

gem 'motion-stump'
gem 'bacon-expect'
gem 'motion-redgreen'
gem 'guard'
gem 'guard-test'

gem 'motion-cocoapods'

A bit about the motion-callback stuff. What happens here is that the calling method looks a bit like this:

login_from_keychain do |on|
  on.success { 
    # take success action
  }
  on.failure {|status|
    # take failure action
  }
end

Note that we don't have a clue how motion-firebase or, indeed, Firebase will communicate or how long they will take. It's not feasible to provide a WebMock to fake the responses out because we don't know what the right responses are. I suspect Firebase uses sockets anyhow.

One could make that case that the specs are trying to reach too deep and test code that is not in the business domain. I would argue the contrary: Authorization, creation and removal of users, etc. are in the business domain and they have to brush up against some foreign interface (in this case keychain and motion-firebase) to accomplish their goals.

I've deliberately left the specs blank to see what people come up with. If there's something boneheaded in the code, I'm happy to hear about it, but the point is that this code, empirically, works and with tests I can refactor it with more confidence.

describe "user" do
  describe "logging in" do
    it "logs in from keychain if there are keychain credentials" do
    end

    it "successfully logs in from correct user id and password" do
    end

    it "fails to log in from incorrect user id or password" do
    end

    it "creates a user successfully" do
    end

    it "politely logs the user in after successfully creating him/her" do
    end
  end
end

and the User code

class User
  @@auth      = nil
  @@logged_in = false
  @@keychain  = nil

  class << self
    def check(&block)
      auth ||= rmq.app.delegate.auth

      status = blank_status

      auth.check{|error, user|
        status.error = error
        status.user  = user

        if error.nil? && user.respond_to?(:email)
          @@logged_in = status.logged_in = error.nil?
          block.call Callback.new(:success, status)
        else
          block.call Callback.new(:failure, status)
        end
      }
    end

    def logged_in?
      @@logged_in
    end

    def login_from_keychain(&block)
      block.call Callback.new(:failure) unless Persistence[:remember_me]

      @@keychain ||= KeychainItemWrapper.alloc.initWithIdentifier 'Supercharger', accessGroup: nil

      username = @@keychain.objectForKey KSecAttrAccount
      password = @@keychain.objectForKey KSecValueData

      login(username, password) do |status|
        if status.error == 'success'
          status.logged_in = true
          block.call Callback.new(:success)
        else
          block.call Callback.new(:failure)
        end
      end

    end

    def add_to_keychain(email, password)
      @@keychain.setObject email,     forKey: KSecAttrAccount
      @@keychain.setObject password,  forKey: KSecValueData
    end

    def remove_from_keychain
      @@keychain ||= KeychainItemWrapper.alloc.initWithIdentifier 'Supercharger', accessGroup: nil

      @@keychain.setObject nil,       forKey: KSecAttrAccount
      @@keychain.setObject nil,       forKey: KSecValueData
    end

    def login(email, password, &block)
      status = blank_status

      auth.login(email: email, password: password) { |error, user|
        status.user = user

        if error.nil?
          if user.nil?
            status.error = "no error but user is nil"
          end
          block.call(status)
        else
          status.error = error.localizedDescription
          block.call(status)
        end
      }
    end

    def create(email, password, &block)
      credentials = {
        email:email,
        password:password
      }

      rmq.app.delegate.auth.create(credentials) do |error, user|
        if error
          block.call Callback.new(:failure, error.localizedDescription)
        else
          block.call Callback.new(:success, error)
        end
      end
    end

    def remove(email, password)
      rmq.app.delegate.auth.remove(email: email, password: password){|error, user|
      }
    end

    private

    def auth
      @@auth ||= rmq.app.delegate.auth
    end

    def blank_status
      MotionMap::Map.new({error: 'success', user: nil, logged_in: false})
    end

  end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment