<NME> user.rb
<BEF> # frozen_string_literal: true

class User < ApplicationRecord
  include Auth0Omniauthable
  include Mongoid::Paranoia

  # Concerns
  include ::Concerns::User::Authentication
  include ::Concerns::User::Facebook

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :omniauthable, omniauth_providers: [:facebook] #:database_authenticatable, :registerable,
  #:recoverable, :rememberable, :trackable, :validatable

  ## Database authenticatable
  field :email, type: String, default: ''
  has_many :itineraries, dependent: :destroy

  has_many :conversations, ->(user) { unscope(:where).where(sender: user).or(where(receiver: user)) }, inverse_of: :sender, dependent: :destroy

  validates :gender, inclusion: GENDER, allow_blank: true
  validates :time_zone, inclusion: ActiveSupport::TimeZone.all.map(&:name)
  validates :vehicle_avg_consumption, numericality: { greater_than: 0, less_than: 10 }, presence: true

  def age
    ((Time.current.to_fs(:number).to_i - birthday.at_midnight.to_fs(:number).to_i) / 1e10.to_i) if birthday?
  end

  def female?
    gender == 'female'
  end

  def first_name
    @first_name ||= name.split[0] if name?
  end

  def to_s
    name || id.to_s
  end

  # field :unlock_token,    type: String # Only if unlock strategy is :email or :both
  # field :locked_at,       type: Time

  # The gender selected by this person, male or female. This value will be omitted if the gender is set to a custom value
  # Ref: https://developers.facebook.com/docs/graph-api/reference/user/
  GENDER = %w(male female).freeze
  end
end

  has_and_belongs_to_many :conversations

  has_many :itineraries, dependent: :destroy
  has_many :feedbacks

  embeds_many :notifications
  embeds_many :references, cascade_callbacks: true

  # Auth / Credentials / Permissions
  field :provider
  field :uid
  field :oauth_token
  field :oauth_expires_at
  field :facebook_permissions, type: Hash, default: {}

  # Cached Facebook data
  field :facebook_friends, type: Array, default: []
  field :facebook_favorites, type: Array, default: []
  field :facebook_data_cached_at, type: DateTime, default: '2012-09-06'

  # Info
  field :name
  field :image
  field :facebook_verified, type: Boolean, default: false

  # Extra
  field :gender
  field :bio
  field :languages, type: Array, default: []

  # More info requiring special permissions
  field :birthday, type: Date
  field :work, type: Array, default: []
  field :education, type: Array, default: []

  # Icare
  field :vehicle_avg_consumption, type: Float, default: APP_CONFIG.fuel_consumption

  # Account
  field :locale
  field :time_zone, default: 'UTC' # NOTE think about it
  field :telephone
  field :admin, type: Boolean, default: false

  # field :access_level, type: Integer, default: 0
  field :banned, type: Boolean, default: false

  field :send_email_messages, type: Boolean, default: false
  field :send_email_references, type: Boolean, default: false

  validates :gender, inclusion: GENDER, allow_blank: true
  validates :time_zone, inclusion: ActiveSupport::TimeZone.zones_map(&:name).keys, allow_blank: true
  validates :vehicle_avg_consumption, numericality: { greater_than: 0, less_than: 10 }, presence: true
  # validates :access_level, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 5 }

  def age
    ((Time.current.to_s(:number).to_i - birthday.at_midnight.to_s(:number).to_i) / 10e9.to_i) if birthday?
  end

  def first_name
    @first_name ||= name.split[0] if name?
  end
    references.unread.size
  end

  def female?
    gender == 'female'
  end
end

<MSG> Refactor authentication and User model

Renames authentication.rb to omniauthable.rb.
Moves Facebook related fields and methods to Facebook concern.
Sorts up fields in User.
<DFF> @@ -5,13 +5,13 @@ class User
   include Mongoid::Paranoia
 
   # Concerns
-  include ::Concerns::User::Authentication
-  include ::Concerns::User::Facebook
+  include ::Concerns::User::FacebookOmniauthable
 
   # Include default devise modules. Others available are:
+  devise :omniauthable, omniauth_providers: [:facebook]
   # :confirmable, :lockable, :timeoutable and :omniauthable
-  devise :omniauthable, omniauth_providers: [:facebook] #:database_authenticatable, :registerable,
-  #:recoverable, :rememberable, :trackable, :validatable
+  # :database_authenticatable, :registerable,
+  # :recoverable, :rememberable, :trackable, :validatable
 
   ## Database authenticatable
   field :email, type: String, default: ''
@@ -42,6 +42,10 @@ class User
   # field :unlock_token,    type: String # Only if unlock strategy is :email or :both
   # field :locked_at,       type: Time
 
+  ## Omniauthable
+  field :provider
+  field :uid
+
   # The gender selected by this person, male or female. This value will be omitted if the gender is set to a custom value
   # Ref: https://developers.facebook.com/docs/graph-api/reference/user/
   GENDER = %w(male female).freeze
@@ -50,63 +54,46 @@ class User
 
   has_and_belongs_to_many :conversations
 
-  has_many :itineraries, dependent: :destroy
   has_many :feedbacks
+  has_many :itineraries, dependent: :destroy
 
   embeds_many :notifications
   embeds_many :references, cascade_callbacks: true
 
-  # Auth / Credentials / Permissions
-  field :provider
-  field :uid
-  field :oauth_token
-  field :oauth_expires_at
-  field :facebook_permissions, type: Hash, default: {}
-
-  # Cached Facebook data
-  field :facebook_friends, type: Array, default: []
-  field :facebook_favorites, type: Array, default: []
-  field :facebook_data_cached_at, type: DateTime, default: '2012-09-06'
-
-  # Info
-  field :name
-  field :image
-  field :facebook_verified, type: Boolean, default: false
-
-  # Extra
-  field :gender
-  field :bio
-  field :languages, type: Array, default: []
-
-  # More info requiring special permissions
-  field :birthday, type: Date
-  field :work, type: Array, default: []
+  # Uneditable user info
+  field :bio,       type: String
+  field :birthday,  type: Date
   field :education, type: Array, default: []
-
-  # Icare
-  field :vehicle_avg_consumption, type: Float, default: APP_CONFIG.fuel_consumption
-
-  # Account
-  field :locale
-  field :time_zone, default: 'UTC' # NOTE think about it
-  field :telephone
-  field :admin, type: Boolean, default: false
-
-  # field :access_level, type: Integer, default: 0
+  field :gender,    type: String
+  field :image,     type: String
+  field :languages, type: Array, default: []
+  field :name,      type: String
+  field :work,      type: Array, default: []
+
+  # Editable user info
+  field :locale,                  type: String
+  field :send_email_messages,     type: Boolean, default: false
+  field :send_email_references,   type: Boolean, default: false
+  field :telephone,               type: String
+  field :time_zone,               type: String,  default: 'UTC'
+  field :vehicle_avg_consumption, type: Float,   default: APP_CONFIG.fuel_consumption
+
+  # Account status
+  field :admin,  type: Boolean, default: false
   field :banned, type: Boolean, default: false
 
-  field :send_email_messages, type: Boolean, default: false
-  field :send_email_references, type: Boolean, default: false
-
   validates :gender, inclusion: GENDER, allow_blank: true
   validates :time_zone, inclusion: ActiveSupport::TimeZone.zones_map(&:name).keys, allow_blank: true
   validates :vehicle_avg_consumption, numericality: { greater_than: 0, less_than: 10 }, presence: true
-  # validates :access_level, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 5 }
 
   def age
     ((Time.current.to_s(:number).to_i - birthday.at_midnight.to_s(:number).to_i) / 10e9.to_i) if birthday?
   end
 
+  def female?
+    gender == 'female'
+  end
+
   def first_name
     @first_name ||= name.split[0] if name?
   end
@@ -123,7 +110,11 @@ class User
     references.unread.size
   end
 
-  def female?
-    gender == 'female'
+  def self.from_omniauth(auth_hash)
+    where(provider: auth_hash.provider, uid: auth_hash.uid).first_or_create do |user|
+      user.email = auth_hash.info.email
+      user.image = auth_hash.info.image
+      user.name  = auth_hash.info.name
+    end
   end
 end

{'commit': 'diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb\nindex a33ef633..c1548384 100644\n--- a/app/controllers/users/omniauth_callbacks_controller.rb\n+++ b/app/controllers/users/omniauth_callbacks_controller.rb\n@@ -2,8 +2,10 @@\n module Users\n   class OmniauthCallbacksController < ::Devise::OmniauthCallbacksController\n     def facebook\n-      @user = User.from_omniauth(request.env[\'omniauth.auth\'])\n-      if @user\n+      @user = User.from_omniauth(auth_hash)\n+\n+      if @user.persisted?\n+        @user.update_auth_hash_info! auth_hash\n         sign_in_and_redirect @user, event: :authentication\n       else\n         redirect_to root_path, flash: { error: \'flash.sessions.error.create\' }\n@@ -15,5 +17,9 @@ module Users\n     def after_omniauth_failure_path_for(_scope)\n       root_path\n     end\n+\n+    def auth_hash\n+      request.env[\'omniauth.auth\']\n+    end\n   end\n end\ndiff --git a/app/models/concerns/user/authentication.rb b/app/models/concerns/user/authentication.rb\ndeleted file mode 100644\nindex 53634ba4..00000000\n--- a/app/models/concerns/user/authentication.rb\n+++ /dev/null\n@@ -1,77 +0,0 @@\n-# frozen_string_literal: true\n-module Concerns\n-  module User\n-    module Authentication\n-      extend ActiveSupport::Concern\n-\n-      included do\n-        def update_fields_from_omniauth(auth)\n-          # Auth / Credentials (token, expires_at)\n-          update_credentials auth.credentials\n-\n-          # Info (email, name, verified)\n-          update_info auth.info\n-\n-          # Extra raw info (username, gender, bio, languages)\n-          # set_extra_raw_info auth.extra.raw_info\n-\n-          # Extra raw info requiring special permissions (birthday, work, education)\n-          # set_extra_raw_info_special_permissions auth.extra.raw_info\n-\n-          # Locale, with priority to application setting\n-          # self.locale = auth.extra.raw_info.locale.tr(\'_\', \'-\') if auth.extra.raw_info.locale && !locale?\n-\n-          # Permissions\n-          update_permissions\n-\n-          # Schedule facebook data cache\n-          CacheFacebookDataJob.perform_later id.to_s\n-        end\n-\n-        private\n-\n-        def update_credentials(credentials)\n-          self.oauth_token = credentials.token\n-          self.oauth_expires_at =\n-            (Time.zone.at credentials.expires_at if credentials.expires)\n-        end\n-\n-        def update_info(info)\n-          self.email = info.email\n-          self.name  = info.name\n-          self.image = info.image\n-        end\n-\n-        # def set_extra_raw_info(raw_info)\n-        #   self.username  = raw_info.username\n-        #   self.gender    = raw_info.gender\n-        #   self.bio       = raw_info.bio\n-        #   self.languages = raw_info.languages || []\n-        # end\n-\n-        # def set_extra_raw_info_special_permissions(raw_info)\n-        #   self.birthday  = Date.strptime(raw_info.birthday, \'%m/%d/%Y\').at_midnight if raw_info.birthday\n-        #   self.work      = raw_info.work || []\n-        #   self.education = raw_info.education || []\n-        # end\n-\n-        def update_permissions\n-          facebook do |fb|\n-            self.facebook_permissions = fb.get_connections(\'me\', \'permissions\')[0]\n-          end\n-        end\n-      end\n-\n-      module ClassMethods\n-        def from_omniauth(auth_hash)\n-          user = where(provider: auth_hash.provider, uid: auth_hash.uid).first_or_initialize\n-\n-          user.update_fields_from_omniauth auth_hash\n-          user.save!\n-\n-          user\n-        end\n-      end\n-    end\n-  end\n-end\ndiff --git a/app/models/concerns/user/facebook.rb b/app/models/concerns/user/facebook.rb\ndeleted file mode 100644\nindex b3a47207..00000000\n--- a/app/models/concerns/user/facebook.rb\n+++ /dev/null\n@@ -1,46 +0,0 @@\n-# frozen_string_literal: true\n-module Concerns\n-  module User\n-    module Facebook\n-      extend ActiveSupport::Concern\n-\n-      included do\n-        def facebook\n-          @facebook ||= Koala::Facebook::API.new(oauth_token)\n-          block_given? ? yield(@facebook) : @facebook\n-        rescue Koala::Facebook::APIError => e\n-          logger.info e.to_s\n-          nil # or consider a custom null object\n-          # TODO: Investigate this error:\n-          # Koala::Facebook::APIError: OAuthException: Error validating access token: Session does not match current stored session. This may be because the user changed the password since the time the session was created or Facebook has changed the session for security reasons.\n-        end\n-\n-        def facebook_permission?(scope)\n-          facebook_permissions? && facebook_permissions[scope.to_s].to_i == 1\n-        end\n-\n-        def cache_facebook_data!\n-          facebook do |fb|\n-            result = fb.batch do |batch_api|\n-              batch_api.get_connections(\'me\', \'friends\')\n-\n-              %w(music books movies television games).each do |favorite|\n-                batch_api.get_connections(\'me\', favorite)\n-              end\n-            end\n-            if result.any?\n-              self.facebook_friends   = result[0].to_a\n-              self.facebook_favorites = result[1..-1].select { |r| r.class == Koala::Facebook::API::GraphCollection }.flatten\n-            end\n-            self.facebook_data_cached_at = Time.now.utc\n-            return save\n-          end\n-          false\n-        end\n-      end\n-\n-      module ClassMethods\n-      end\n-    end\n-  end\n-end\ndiff --git a/app/models/concerns/user/facebook_omniauthable.rb b/app/models/concerns/user/facebook_omniauthable.rb\nnew file mode 100644\nindex 00000000..cb91cdfb\n--- /dev/null\n+++ b/app/models/concerns/user/facebook_omniauthable.rb\n@@ -0,0 +1,100 @@\n+# frozen_string_literal: true\n+module Concerns\n+  module User\n+    module FacebookOmniauthable\n+      extend ActiveSupport::Concern\n+\n+      included do\n+        field :access_token\n+        field :access_token_expires_at\n+\n+        field :facebook_data_cached_at, type: DateTime, default: \'2012-09-06\'\n+        field :facebook_favorites,      type: Array,    default: []\n+        field :facebook_friends,        type: Array,    default: []\n+        field :facebook_permissions,    type: Hash,     default: {}\n+        field :facebook_verified,       type: Boolean,  default: false\n+\n+        def facebook\n+          @facebook ||= Koala::Facebook::API.new(access_token)\n+          block_given? ? yield(@facebook) : @facebook\n+        rescue Koala::Facebook::APIError => e\n+          logger.info e.to_s\n+          nil # or consider a custom null object\n+          # TODO: Investigate this error:\n+          # Koala::Facebook::APIError: OAuthException: Error validating access token: Session does not match current stored session. This may be because the user changed the password since the time the session was created or Facebook has changed the session for security reasons.\n+        end\n+\n+        def facebook_permission?(scope)\n+          facebook_permissions? && facebook_permissions[scope.to_s].to_i == 1\n+        end\n+\n+        def cache_facebook_data!\n+          facebook do |fb|\n+            result = fb.batch do |batch_api|\n+              batch_api.get_connections(\'me\', \'friends\')\n+\n+              %w(music books movies television games).each do |favorite|\n+                batch_api.get_connections(\'me\', favorite)\n+              end\n+            end\n+            if result.any?\n+              self.facebook_friends   = result[0].to_a\n+              self.facebook_favorites = result[1..-1].select { |r| r.class == Koala::Facebook::API::GraphCollection }.flatten\n+            end\n+            self.facebook_data_cached_at = Time.now.utc\n+            return save\n+          end\n+          false\n+        end\n+\n+        def update_auth_hash_info!(auth_hash)\n+          update_attributes(\n+            email:                   auth_hash.info.email,\n+            name:                    auth_hash.info.name,\n+            image:                   auth_hash.info.image,\n+            access_token:            auth_hash.credentials.token,\n+            access_token_expires_at: Time.zone.at(auth_hash.credentials.expires_at)\n+          )\n+        end\n+\n+        # def update_facebook_info(auth_hash)\n+        #   # Extra raw info (username, gender, bio, languages)\n+        #   # set_extra_raw_info auth.extra.raw_info\n+        #\n+        #   # Extra raw info requiring special permissions (birthday, work, education)\n+        #   # set_extra_raw_info_special_permissions auth.extra.raw_info\n+        #\n+        #   # Locale, with priority to application setting\n+        #   # self.locale = auth.extra.raw_info.locale.tr(\'_\', \'-\') if auth.extra.raw_info.locale && !locale?\n+        #\n+        #   # Permissions\n+        #   update_permissions\n+        #\n+        #   # Schedule facebook data cache\n+        #   CacheFacebookDataJob.perform_later id.to_s\n+        # end\n+\n+        private\n+\n+        def update_permissions\n+          facebook do |fb|\n+            self.facebook_permissions = fb.get_connections(\'me\', \'permissions\')[0]\n+          end\n+        end\n+\n+        # def set_extra_raw_info(raw_info)\n+        #   self.username  = raw_info.username\n+        #   self.gender    = raw_info.gender\n+        #   self.bio       = raw_info.bio\n+        #   self.languages = raw_info.languages || []\n+        # end\n+\n+        # def set_extra_raw_info_special_permissions(raw_info)\n+        #   self.birthday  = Date.strptime(raw_info.birthday, \'%m/%d/%Y\').at_midnight if raw_info.birthday\n+        #   self.work      = raw_info.work || []\n+        #   self.education = raw_info.education || []\n+        # end\n+      end\n+    end\n+  end\n+end\ndiff --git a/app/models/user.rb b/app/models/user.rb\nindex 7a58bcda..649116fc 100644\n--- a/app/models/user.rb\n+++ b/app/models/user.rb\n@@ -5,13 +5,13 @@ class User\n   include Mongoid::Paranoia\n \n   # Concerns\n-  include ::Concerns::User::Authentication\n-  include ::Concerns::User::Facebook\n+  include ::Concerns::User::FacebookOmniauthable\n \n   # Include default devise modules. Others available are:\n+  devise :omniauthable, omniauth_providers: [:facebook]\n   # :confirmable, :lockable, :timeoutable and :omniauthable\n-  devise :omniauthable, omniauth_providers: [:facebook] #:database_authenticatable, :registerable,\n-  #:recoverable, :rememberable, :trackable, :validatable\n+  # :database_authenticatable, :registerable,\n+  # :recoverable, :rememberable, :trackable, :validatable\n \n   ## Database authenticatable\n   field :email, type: String, default: \'\'\n@@ -42,6 +42,10 @@ class User\n   # field :unlock_token,    type: String # Only if unlock strategy is :email or :both\n   # field :locked_at,       type: Time\n \n+  ## Omniauthable\n+  field :provider\n+  field :uid\n+\n   # The gender selected by this person, male or female. This value will be omitted if the gender is set to a custom value\n   # Ref: https://developers.facebook.com/docs/graph-api/reference/user/\n   GENDER = %w(male female).freeze\n@@ -50,63 +54,46 @@ class User\n \n   has_and_belongs_to_many :conversations\n \n-  has_many :itineraries, dependent: :destroy\n   has_many :feedbacks\n+  has_many :itineraries, dependent: :destroy\n \n   embeds_many :notifications\n   embeds_many :references, cascade_callbacks: true\n \n-  # Auth / Credentials / Permissions\n-  field :provider\n-  field :uid\n-  field :oauth_token\n-  field :oauth_expires_at\n-  field :facebook_permissions, type: Hash, default: {}\n-\n-  # Cached Facebook data\n-  field :facebook_friends, type: Array, default: []\n-  field :facebook_favorites, type: Array, default: []\n-  field :facebook_data_cached_at, type: DateTime, default: \'2012-09-06\'\n-\n-  # Info\n-  field :name\n-  field :image\n-  field :facebook_verified, type: Boolean, default: false\n-\n-  # Extra\n-  field :gender\n-  field :bio\n-  field :languages, type: Array, default: []\n-\n-  # More info requiring special permissions\n-  field :birthday, type: Date\n-  field :work, type: Array, default: []\n+  # Uneditable user info\n+  field :bio,       type: String\n+  field :birthday,  type: Date\n   field :education, type: Array, default: []\n-\n-  # Icare\n-  field :vehicle_avg_consumption, type: Float, default: APP_CONFIG.fuel_consumption\n-\n-  # Account\n-  field :locale\n-  field :time_zone, default: \'UTC\' # NOTE think about it\n-  field :telephone\n-  field :admin, type: Boolean, default: false\n-\n-  # field :access_level, type: Integer, default: 0\n+  field :gender,    type: String\n+  field :image,     type: String\n+  field :languages, type: Array, default: []\n+  field :name,      type: String\n+  field :work,      type: Array, default: []\n+\n+  # Editable user info\n+  field :locale,                  type: String\n+  field :send_email_messages,     type: Boolean, default: false\n+  field :send_email_references,   type: Boolean, default: false\n+  field :telephone,               type: String\n+  field :time_zone,               type: String,  default: \'UTC\'\n+  field :vehicle_avg_consumption, type: Float,   default: APP_CONFIG.fuel_consumption\n+\n+  # Account status\n+  field :admin,  type: Boolean, default: false\n   field :banned, type: Boolean, default: false\n \n-  field :send_email_messages, type: Boolean, default: false\n-  field :send_email_references, type: Boolean, default: false\n-\n   validates :gender, inclusion: GENDER, allow_blank: true\n   validates :time_zone, inclusion: ActiveSupport::TimeZone.zones_map(&:name).keys, allow_blank: true\n   validates :vehicle_avg_consumption, numericality: { greater_than: 0, less_than: 10 }, presence: true\n-  # validates :access_level, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 5 }\n \n   def age\n     ((Time.current.to_s(:number).to_i - birthday.at_midnight.to_s(:number).to_i) / 10e9.to_i) if birthday?\n   end\n \n+  def female?\n+    gender == \'female\'\n+  end\n+\n   def first_name\n     @first_name ||= name.split[0] if name?\n   end\n@@ -123,7 +110,11 @@ class User\n     references.unread.size\n   end\n \n-  def female?\n-    gender == \'female\'\n+  def self.from_omniauth(auth_hash)\n+    where(provider: auth_hash.provider, uid: auth_hash.uid).first_or_create do |user|\n+      user.email = auth_hash.info.email\n+      user.image = auth_hash.info.image\n+      user.name  = auth_hash.info.name\n+    end\n   end\n end\ndiff --git a/spec/features/sessions_spec.rb b/spec/features/sessions_spec.rb\nindex 2f09d453..c268d70a 100644\n--- a/spec/features/sessions_spec.rb\n+++ b/spec/features/sessions_spec.rb\n@@ -44,11 +44,13 @@ describe \'Sessions\' do\n \n     user.reload\n \n-    expect(user.oauth_token).to eq OMNIAUTH_MOCKED_AUTHHASH.credentials.token\n-    expect(user.oauth_expires_at).to eq Time.zone.at OMNIAUTH_MOCKED_AUTHHASH.credentials.expires_at\n+    expect(user.access_token).to eq OMNIAUTH_MOCKED_AUTHHASH.credentials.token\n+    expect(user.access_token_expires_at).to eq Time.zone.at(OMNIAUTH_MOCKED_AUTHHASH.credentials.expires_at)\n \n     expect(user.email).to eq OMNIAUTH_MOCKED_AUTHHASH.info.email\n+    expect(user.image).to eq OMNIAUTH_MOCKED_AUTHHASH.info.image\n     expect(user.name).to eq OMNIAUTH_MOCKED_AUTHHASH.info.name\n+\n     expect(user.facebook_verified).to be false\n \n     expect(user.username).to eq OMNIAUTH_MOCKED_AUTHHASH.extra.raw_info.username\ndiff --git a/spec/workers/cache_facebook_data_job_spec.rb b/spec/workers/cache_facebook_data_job_spec.rb\nindex 720d2ffd..9be69f29 100644\n--- a/spec/workers/cache_facebook_data_job_spec.rb\n+++ b/spec/workers/cache_facebook_data_job_spec.rb\n@@ -2,7 +2,7 @@\n require \'spec_helper\'\n \n describe CacheFacebookDataJob do\n-  let(:user) { create :user, oauth_token: \'test\' }\n+  let(:user) { create :user, access_token: \'test\' }\n \n   before do\n     stub_http_request(:post, /graph.facebook.com/).to_return body: [\n@@ -31,7 +31,7 @@ describe CacheFacebookDataJob do\n   end\n \n   it "doesn\'t cache user data when they are still valid" do\n-    user_with_fresh_data = create :user, oauth_token: \'test\', facebook_data_cached_at: Time.current\n+    user_with_fresh_data = create :user, access_token: \'test\', facebook_data_cached_at: Time.current\n     expect(CacheFacebookDataJob.perform_now(user_with_fresh_data.id)).to be nil\n   end\n \ndiff --git a/spec/workers/share_on_facebook_timeline_job_spec.rb b/spec/workers/share_on_facebook_timeline_job_spec.rb\nindex 553bdf4d..bbab1c09 100644\n--- a/spec/workers/share_on_facebook_timeline_job_spec.rb\n+++ b/spec/workers/share_on_facebook_timeline_job_spec.rb\n@@ -2,7 +2,7 @@\n require \'spec_helper\'\n \n describe ShareOnFacebookTimelineJob do\n-  let(:user) { create :user, oauth_token: \'test\', facebook_permissions: { \'publish_actions\' => 1 } }\n+  let(:user) { create :user, access_token: \'test\', facebook_permissions: { \'publish_actions\' => 1 } }\n   let(:itinerary) { create :itinerary, user: user }\n \n   it \'publishes itinerary on facebook timeline\' do\n', 'commit_subject': 'Refactor authentication and User model', 'repo_name': 'diowa/icare', 'committer': {'name': 'Geremia Taglialatela', 'email': '37202d6068035727e15d6c4bd05dbee26d50f5e4@gmail.com', 'time_sec': '1464253507', 'tz_offset': '120', 'date': {'seconds': '1464253507'}}, 'commit_tree': 'f296dabc5b4c000279545ddb8a3e8670b53432d5', 'commit_message': 'Refactor authentication and User model\n\nRenames authentication.rb to omniauthable.rb.\nMoves Facebook related fields and methods to Facebook concern.\nSorts up fields in User.', 'language_name': 'Ruby', 'license': 'bsd-2-clause', 'hunks': "@@ -5,13 +5,13 @@ class User\n   include Mongoid::Paranoia\n \n   # Concerns\n-  include ::Concerns::User::Authentication\n-  include ::Concerns::User::Facebook\n+  include ::Concerns::User::FacebookOmniauthable\n \n   # Include default devise modules. Others available are:\n+  devise :omniauthable, omniauth_providers: [:facebook]\n   # :confirmable, :lockable, :timeoutable and :omniauthable\n-  devise :omniauthable, omniauth_providers: [:facebook] #:database_authenticatable, :registerable,\n-  #:recoverable, :rememberable, :trackable, :validatable\n+  # :database_authenticatable, :registerable,\n+  # :recoverable, :rememberable, :trackable, :validatable\n \n   ## Database authenticatable\n   field :email, type: String, default: ''\n@@ -42,6 +42,10 @@ class User\n   # field :unlock_token,    type: String # Only if unlock strategy is :email or :both\n   # field :locked_at,       type: Time\n \n+  ## Omniauthable\n+  field :provider\n+  field :uid\n+\n   # The gender selected by this person, male or female. This value will be omitted if the gender is set to a custom value\n   # Ref: https://developers.facebook.com/docs/graph-api/reference/user/\n   GENDER = %w(male female).freeze\n@@ -50,63 +54,46 @@ class User\n \n   has_and_belongs_to_many :conversations\n \n-  has_many :itineraries, dependent: :destroy\n   has_many :feedbacks\n+  has_many :itineraries, dependent: :destroy\n \n   embeds_many :notifications\n   embeds_many :references, cascade_callbacks: true\n \n-  # Auth / Credentials / Permissions\n-  field :provider\n-  field :uid\n-  field :oauth_token\n-  field :oauth_expires_at\n-  field :facebook_permissions, type: Hash, default: {}\n-\n-  # Cached Facebook data\n-  field :facebook_friends, type: Array, default: []\n-  field :facebook_favorites, type: Array, default: []\n-  field :facebook_data_cached_at, type: DateTime, default: '2012-09-06'\n-\n-  # Info\n-  field :name\n-  field :image\n-  field :facebook_verified, type: Boolean, default: false\n-\n-  # Extra\n-  field :gender\n-  field :bio\n-  field :languages, type: Array, default: []\n-\n-  # More info requiring special permissions\n-  field :birthday, type: Date\n-  field :work, type: Array, default: []\n+  # Uneditable user info\n+  field :bio,       type: String\n+  field :birthday,  type: Date\n   field :education, type: Array, default: []\n-\n-  # Icare\n-  field :vehicle_avg_consumption, type: Float, default: APP_CONFIG.fuel_consumption\n-\n-  # Account\n-  field :locale\n-  field :time_zone, default: 'UTC' # NOTE think about it\n-  field :telephone\n-  field :admin, type: Boolean, default: false\n-\n-  # field :access_level, type: Integer, default: 0\n+  field :gender,    type: String\n+  field :image,     type: String\n+  field :languages, type: Array, default: []\n+  field :name,      type: String\n+  field :work,      type: Array, default: []\n+\n+  # Editable user info\n+  field :locale,                  type: String\n+  field :send_email_messages,     type: Boolean, default: false\n+  field :send_email_references,   type: Boolean, default: false\n+  field :telephone,               type: String\n+  field :time_zone,               type: String,  default: 'UTC'\n+  field :vehicle_avg_consumption, type: Float,   default: APP_CONFIG.fuel_consumption\n+\n+  # Account status\n+  field :admin,  type: Boolean, default: false\n   field :banned, type: Boolean, default: false\n \n-  field :send_email_messages, type: Boolean, default: false\n-  field :send_email_references, type: Boolean, default: false\n-\n   validates :gender, inclusion: GENDER, allow_blank: true\n   validates :time_zone, inclusion: ActiveSupport::TimeZone.zones_map(&:name).keys, allow_blank: true\n   validates :vehicle_avg_consumption, numericality: { greater_than: 0, less_than: 10 }, presence: true\n-  # validates :access_level, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 5 }\n \n   def age\n     ((Time.current.to_s(:number).to_i - birthday.at_midnight.to_s(:number).to_i) / 10e9.to_i) if birthday?\n   end\n \n+  def female?\n+    gender == 'female'\n+  end\n+\n   def first_name\n     @first_name ||= name.split[0] if name?\n   end\n@@ -123,7 +110,11 @@ class User\n     references.unread.size\n   end\n \n-  def female?\n-    gender == 'female'\n+  def self.from_omniauth(auth_hash)\n+    where(provider: auth_hash.provider, uid: auth_hash.uid).first_or_create do |user|\n+      user.email = auth_hash.info.email\n+      user.image = auth_hash.info.image\n+      user.name  = auth_hash.info.name\n+    end\n   end\n end\n", 'addition_count': 38, 'deletion_count': 47, 'src_file': 'a/app/models/user.rb', 'tgt_file': 'b/app/models/user.rb', 'file_extension': '.rb', 'before_file': "# frozen_string_literal: true\n\nclass User < ApplicationRecord\n  include Auth0Omniauthable\n  include Mongoid::Paranoia\n\n  # Concerns\n  include ::Concerns::User::Authentication\n  include ::Concerns::User::Facebook\n\n  # Include default devise modules. Others available are:\n  # :confirmable, :lockable, :timeoutable and :omniauthable\n  devise :omniauthable, omniauth_providers: [:facebook] #:database_authenticatable, :registerable,\n  #:recoverable, :rememberable, :trackable, :validatable\n\n  ## Database authenticatable\n  field :email, type: String, default: ''\n  has_many :itineraries, dependent: :destroy\n\n  has_many :conversations, ->(user) { unscope(:where).where(sender: user).or(where(receiver: user)) }, inverse_of: :sender, dependent: :destroy\n\n  validates :gender, inclusion: GENDER, allow_blank: true\n  validates :time_zone, inclusion: ActiveSupport::TimeZone.all.map(&:name)\n  validates :vehicle_avg_consumption, numericality: { greater_than: 0, less_than: 10 }, presence: true\n\n  def age\n    ((Time.current.to_fs(:number).to_i - birthday.at_midnight.to_fs(:number).to_i) / 1e10.to_i) if birthday?\n  end\n\n  def female?\n    gender == 'female'\n  end\n\n  def first_name\n    @first_name ||= name.split[0] if name?\n  end\n\n  def to_s\n    name || id.to_s\n  end\n\n  # field :unlock_token,    type: String # Only if unlock strategy is :email or :both\n  # field :locked_at,       type: Time\n\n  # The gender selected by this person, male or female. This value will be omitted if the gender is set to a custom value\n  # Ref: https://developers.facebook.com/docs/graph-api/reference/user/\n  GENDER = %w(male female).freeze\n  end\nend\n\n  has_and_belongs_to_many :conversations\n\n  has_many :itineraries, dependent: :destroy\n  has_many :feedbacks\n\n  embeds_many :notifications\n  embeds_many :references, cascade_callbacks: true\n\n  # Auth / Credentials / Permissions\n  field :provider\n  field :uid\n  field :oauth_token\n  field :oauth_expires_at\n  field :facebook_permissions, type: Hash, default: {}\n\n  # Cached Facebook data\n  field :facebook_friends, type: Array, default: []\n  field :facebook_favorites, type: Array, default: []\n  field :facebook_data_cached_at, type: DateTime, default: '2012-09-06'\n\n  # Info\n  field :name\n  field :image\n  field :facebook_verified, type: Boolean, default: false\n\n  # Extra\n  field :gender\n  field :bio\n  field :languages, type: Array, default: []\n\n  # More info requiring special permissions\n  field :birthday, type: Date\n  field :work, type: Array, default: []\n  field :education, type: Array, default: []\n\n  # Icare\n  field :vehicle_avg_consumption, type: Float, default: APP_CONFIG.fuel_consumption\n\n  # Account\n  field :locale\n  field :time_zone, default: 'UTC' # NOTE think about it\n  field :telephone\n  field :admin, type: Boolean, default: false\n\n  # field :access_level, type: Integer, default: 0\n  field :banned, type: Boolean, default: false\n\n  field :send_email_messages, type: Boolean, default: false\n  field :send_email_references, type: Boolean, default: false\n\n  validates :gender, inclusion: GENDER, allow_blank: true\n  validates :time_zone, inclusion: ActiveSupport::TimeZone.zones_map(&:name).keys, allow_blank: true\n  validates :vehicle_avg_consumption, numericality: { greater_than: 0, less_than: 10 }, presence: true\n  # validates :access_level, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 5 }\n\n  def age\n    ((Time.current.to_s(:number).to_i - birthday.at_midnight.to_s(:number).to_i) / 10e9.to_i) if birthday?\n  end\n\n  def first_name\n    @first_name ||= name.split[0] if name?\n  end\n    references.unread.size\n  end\n\n  def female?\n    gender == 'female'\n  end\nend\n", 'message': 'Refactor authentication and User model\n\nRenames authentication.rb to omniauthable.rb.\nMoves Facebook related fields and methods to Facebook concern.\nSorts up fields in User.', 'author_name': 'Geremia Taglialatela', 'lang': 'rb'}