Be a Zeek

Posts tagged ‘web’

Spree Uname Login/Signup

Spree default provides login/signup with just email.

But now a days many E-Coms are taking a trend to introduce username or phone number as alternate login options.

This article is all about how to use alternate options (other than email) for login/signup in a Spree app. I am targeting username here but using other options will be much similar to that.

Start with adding DB migration to add uname attribute to User model.

$ rails g migration add_uname_to_spree_users
class AddUnameToSpreeUsers < ActiveRecord::Migration
  def up
    add_column :spree_users, :uname, :string
    add_index :spree_users, :uname

    Spree::User.where(uname: nil).each do |user|
      user.send(:set_uname)
      user.save!
    end
  end

  def down
    remove_index :spree_users, :uname
    remove_column :spree_users, :uname
  end
end

In migration, you will notice a loop to set_uname for existing users.

# In User model (user_decorator.rb)
def set_uname
  if self.uname.blank? && self.email.present?
    # Fetch string prior to @ from emailID
    username = self.email.split(/@/)[0]
    # remove chars other than numbers and alphabets
    username = username.titleize.gsub(/[^a-zA-Z0-9]/, '')
    count = 0
    # add counter with username if some other user already has it
    while Spree::User.find_by('lower(uname) = :value', value: username.downcase)
      username += (count += 1).to_s
    end

    self.uname = username
  end
end

This set_uname ensures username for each user to be extracted out from its email and only allowing alphabets and numbers to be a part of uname. You can change logic here to suit your need.

If you want to auto assign username to user, you can

# In User model (user_decorator.rb)
before_validation :set_uname, on: :create

We are set now to code/customise Spree login/signup process.

Signup
If you want user to set uname of their choice while signing up set

# In config/initializers/spree.rb
Spree::PermittedAttributes.user_attributes.push(:uname)

Update signup form accordingly to have uname text field.

Login
Change login form email text_field to login text field,

# CHANGE
<%= f.email_field :email %>
# TO
<%= f.text_field :login %>

Add login authentication key to devise,

# In config/initializers/devise.rb
Devise.setup do |config|
  config.authentication_keys = [ :login ]
end

Change the way devise finds user,

# In User model (user_decorator.rb)
def self.find_for_database_authentication(warden_conditions)
  conditions = warden_conditions.dup
  if login = conditions.delete(:login)
    where(conditions.to_hash).where('lower(uname) = :value OR lower(email) = :value', value: login.downcase).first
  else
    conditions[:email].downcase! if conditions[:email]
    where(conditions.to_hash).first
  end
end

And you should be good to go.

Advertisements

Permittribute

https://github.com/techzeek/permittribute

We sometimes face scenarios where we need same permitted attributes for a model in different controllers and end up re-writing all attributes again (violating DRY).

Permittribute is meant to:

  • reuse same permitted attributes at different locations/controllers.
  • scope permitted attributes base on roles like admin, api etc.
  • group permitted attributes at a single reference point in their respective scopes.

Usage

This is used with rails/strong_parameters which is now incorporated since rails 4.x.
We use strong parameters something like:

#in controllers/articles_controller.rb
def article_params
  params.require(:article).permit([:title, :author_id, :published_at, :publication])
end
#in controllers/admin/articles_controller.rb
def article_params
  params.require(:article).permit([:title, :author_id, :published_at, :publication])
end
# in controllers/api/articles_controller.rb
def article_params
  params.require(:article).permit([:title, :author_id, :published_at, :publication])
end

With Permittribute we can do:

# in lib/permittribute/default.rb
Permittribute.configure do
  config.articles = [:title, :author_id, :published_at, :publication]
  # Use as #permittribute_articles
end
# in lib/permittribute/admin.rb
Permittribute.configure(:admin) do
  config.articles = [:title, :author_id, :published_at, :publication]
  # Use as #permittribute_admin_articles
end
# in lib/permittribute/api.rb
Permittribute.configure(:api) do
  config.articles = [:title, :author_id, :published_at, :publication]
  # Use as #permittribute_api_articles
end

And

# in controllers/articles_controller.rb
def article_params
  params.require(:article).permit(permittribute_articles)
end
# in controllers/admin/articles_controller.rb
def article_params
  params.require(:article).permit(permittribute_admin_articles)
end
# in controllers/api/articles_controller.rb
def article_params
  params.require(:article).permit(permittribute_api_articles)
end

Getting Started

Add this line to your application’s Gemfile:

gem 'permittribute'

And then execute:

$ bundle

Or install it yourself as:

$ gem install permittribute

After you have installed Permittribute or added it to your Gemfile, you need to run the generator:

$ rails generate permittribute:install
  • This will create three configurations file, lib/permittribute/default.rb, lib/permittribute/admin.rb and lib/permittribute/api.rb. These contains some example configurations.
  • adds config.eager_load_paths += ["#{Rails.root}/lib/permittribute"] in config/application.rb. To eager load created config files.

Rails tricky timezones

Configuring timezones in rails can be tricky if one doesn’t understand the actual config settings and their use.

Ruby’s Time can also get really confusing when used in Rails app.

Consider Time.now and Time.zone.now

Time is a ruby library hence Time.now will return system’s time. To change that for your Rails app use time_zone config.

Say, I have a server in UTC and all my users are in IST then my rails app time_zone should be like,

#config/application.rb
config.time_zone = ‘IST’

In Rails console,
Time.now #returns time in UTC
=> 2015-07-12 11:26:05 +0000

Time.zone.now #returns time in IST
=> Sun, 12 Jul 2015 16:56:46 IST +05:30

In fact, in rails use Time.current which is a mix of both. Read code here.

ActiveRecord and DB timezones
(Considering MySQL here)

By default, our DB picks system’s timezone (here UTC) and our application here is using IST. Hence when we save a time using Rails app in IST, it actually is saved in UTC in our DB.

So ideally, if we have a time saved in DB as 2015-07-12 11:26:05 +0000 then we should retrieve this in our rails app (in IST) as datetime object Sun, 12 Jul 2015 16:56:05 IST +05:30.

Tricky active_record.default_timezone

There is another setting in Rails, config.active_record.default_timezone, with default value as :local (picks system’s timezone). This config is responsible to identify timezone for our ActiveRecord datetime objects.

So lets say, our DB is on another server with system timezone as UTC and our application is now on a server with system and application timezone as IST, which now means Time.now and Time.zone.now are same.

In this case, config.active_record.default_timezone has value as IST (:local) which will tell our system that the datetime values we are getting from DB are in IST timezone. Hence it will just convert the timezone of the value and not the datetime value itself resulting in wrong results,

  • Actual datetime value in DB, 2015-07-12 11:26:05 +0000 (which is considered in IST timezone by our app due to active_record.default_timezone)
  • Retrieved datetime value in app, Sun, 12 Jul 2015 11:26:05 IST +05:30

To handle these situation we just have to set,

#config/application.rb
config.active_record.default_timezone = :utc

And now the expectation of ActiveRecord datetime object’s timezone from our app will be of UTC which is actually same as DB’s timezone.

Mina (with roles)

http://nadarei.co/mina/

Mina is a competitive fast deployer and server automation tool.

It basically is a deploy Bash script generator. It generates an entire procedure as a Bash script and runs it remotely onto the server.

Example Script for Mina (with whenever roles, as in Capistrano) can be found here.

Capistrano vs Mina

Capistrano runs each command separately on their own SSH session. Whereas, Mina only creates one SSH session per deploy minimising the SSH connection overhead resulting in decreased deploy time.

Visit here for side by side comparison.

Setting up Mina is really easy (Rails)

In Gemfile, gem ‘mina’

$ mina init

Edit config/deploy.rb

$ mina setup

$ mina deploy

This will do the job.

Pros

  • Much easier to understand and setup
  • Much Faster deploys
  • Supports many 3rd party modules

Awesome for small/medium sized application deploys.

Cons (as of now)

  • No support for rolling deploys.
  • No support for multiple server roles as of now. (Gist here using whenever)

Mina is involving and can be seen competing with Capistrano in near future.

HTTP PATCH

Last week’s announcement that HTTP PATCH has been adopted as an official verb via RFC 5789 has generated a lot of excitement. As a summary, the intention of each verb is:

  • POST to create a new resource when the client cannot predict the identity on the origin server (think a new order)
  • PUT to override the definition of a specified resource with what is passed in from the client
  • PATCH to override a portion of a specified resource in a predictable and effectively transactional way (if the entire patch cannot be performed, the server should not do any part of it)

The goal is to convey the intent of the patch more clearly than is possible with the more generic POSTing of modifications. Specific patch diff formats will emerge for modifying plain text, HTML, XML, etc.

Also see discussion at https://github.com/rails/rails/pull/505

This is huge. Finally we have more verb to refine REST APIs operation.