Create random, unique tokens upon account creation in Rails - ruby-on-rails

I have a Rails 4 app using Devise (the most recent) and am trying to create a random token for each user (like the ID, but longer, etc.) Using this answer I was able to come up with the follow code:
# app/models/model_name.rb
class ModelName < ActiveRecord::Base
include Tokenable
end
# app/models/concerns/tokenable.rb
module Tokenable
extend ActiveSupport::Concern
included do
before_create :generate_token
end
protected
def generate_token
self.token = loop do
random_token = SecureRandom.urlsafe_base64(nil, false)
break random_token unless self.class.exists?(token: random_token)
end
end
end
This code works fantastically for tokens that are unique for any given model. I.e. All Users will have unique tokens, and all Admins will have unique tokens. But an Admin may have the same token as a User – this behavior is unwanted.
Is there an elegant way, short of abstracting the token into its own model and using "has_one" relationships, to ensure that the token does not exist in all the models it is a part of?
(I guess I could hard code unless (User.exists? ... or Admin.exists? ... ) into the unless clause, though this seems bulky.)
Any thoughts or suggestions are appreciated! Thanks!

Rails 5 comes with a new feaeture has_secure_tokenis really easy to use:
# Schema: User(token:string, auth_token:string)
class User < ActiveRecord::Base
has_secure_token :auth_token
end
user = User.new
user.save
user.auth_token # => "pX27zsMN2ViQKta1bGfLmVJE"
user.regenerate_auth_token # => true
Since Rails 5 isn't already released, you can use the Backport has_secure_token gem

I would create a method that lists each of the Classes that are including my concern and then test against the token for each. Something like this:
# app/models/concerns/tokenable.rb
module Tokenable
extend ActiveSupport::Concern
included do
before_create :generate_token
end
protected
def included_classes
ActiveRecord::Base.descendants.select do |c|
c.included_modules.include(Concerns::Tokenable)}.map(&:name)
end
end
def generate_token
self.token = loop do
random_token = SecureRandom.urlsafe_base64(nil, false)
break random_token unless included_classes.map {|c| c.constantize.exists?(token: random_token) }.include?(true)
end
end
end
So include_classes is going to return an array of names as strings of each of the classes that include the Tokenable concern. And then in the loop within generate_token is going to check against each of these classes generating an array of true or false which then we just check if any are true with include?(true).
Here is were I found how to get included classes (first answer).
EDIT
In Rails 5 the included_classes looks like this (note the ApplicationRecord and not needing the Concerns::Tokenable):
def included_classes
ApplicationRecord.descendants.select do |c|
c.included_modules.include?(Tokenable)
end
end

Related

Safest way to override the update method of a model

I have the following model:
class TwitterEngagement < ApplicationRecord
end
And I would like to override create (and create!), update (and
update!) methods of it so no one can manually entry fake data. I would like the help of someone more experienced with active record and rails so I don't mess anything up. Right now what I have is:
class TwitterEngagement < ApplicationRecord
belongs_to :page
def create
super(metrics)
end
def update
super(metrics)
end
private
def metrics
client.get_engagements(page.url)
def client
TwitterClient.new
end
end
Thank you.
TL;DR:
class FacebookEngagement < ApplicationRecord
def create_or_update(*args, &block)
super(metrics)
end
Probably depends on your Rails version, but I traced the ActiveRecord::Persistence sometime before in Rails 5, and found out that both create and update eventually calls create_or_update.
Suggestion:
If ever possible, I'll just do a validation, because it kinda makes more sense because you are validating the inputs, and then probably set an optional readonly?, to prevent saving of records. This will also prevent "silent failing" code / behaviour as doing TL;DR above would not throw an exception / populate the validation errors, if say an unsuspecting developer does: facebook_engagement.update(someattr: 'somevalue') as the arguments are gonna basically be ignored because it's instead calling super(metrics), and would then break the principle of least surprise.
So, I'll probably do something like below:
class FacebookEngagement < ApplicationRecord
belongs_to :page
validate :attributes_should_not_be_set_manually
before_save :set_attributes_from_facebook_engagement
# optional
def readonly?
# allows `create`, prevents `update`
persisted?
end
private
def attributes_should_not_be_set_manually
changes.keys.except('page_id').each do |attribute|
errors.add(attribute, 'should not be set manually!')
end
end
def set_attributes_from_facebook_engagement
assign_attributes(metrics)
end
def metrics
# simple memoization to prevent wasteful duplicate requests (or remove if not needed)
#metrics ||= graph.get_object("#{page.url}?fields=engagement")
end
def graph
Koala::Facebook::API.new
end
end

How to store custom class-level data on models, using Concerns

I'm trying to make a concern that checks if a user is subscribed to an appropriate plan for my SaaS app.
Here's basically what I'm trying to do:
module SubscriptionControlled extend ActiveSupport::Concern
class_methods do
def requires_subscription_to(perm)
##perms = [perm]
end
end
included do
validate :check_subscription
end
def check_subscription
##perms.each do |perm|
self.errors.add(:base, "Subscription upgrade required for access to this feature") unless self.user[perm]
end
end
end
This provides this api for a model:
class SomeModel < ApplicationModel
include SubscriptionControlled
requires_subscription_to :pro
end
The problem I'm having is that ##perms seems to be scoped to the CONCERN, rather than the MODEL. So this value is the same for all models. So whichever model is loaded last sets this value for all models.
eg: if loaded in this order:
Model1 -> sets ##perms to [:pro]
Model2 -> sets ##perms to [:business]
Both model 1 and model 2 will only require a subscription to :business
Is there a way of storing class-level variables in a concern that take effect on a per-model basis to accomplish this API?
I don't have a Ruby interpreter at hand right now but I'm fairly certain that using a single # in the class method should do the trick. Another thing that comes to mind is something along the lines of
included do
define_singleton_method :requires_subscription_to do |new_perm|
##perms ||= []
##perms << Array(new_perm)
end
end
Since that will create a new method every time the concern is included, it should work. I just seem to remember that methods defined like that are slightly slower - but since it will probably only be called during initialization, it shouldn't pose a problem in any case.
So I found the right way to do this using a ClassMethods module
module SubscriptionControlled extend ActiveSupport::Concern
module ClassMethods
#perms = []
def requires_subscription_to(perm)
#perms = [perm]
end
def perms
#perms
end
end
included do
validate :check_subscription
end
def check_subscription
self.class.perms.each do |perm|
self.errors.add(:base, "Subscription upgrade required for access to this feature") unless self.user[perm]
end
end
end
this keeps the permissions scoped to the class, not the concern.
I think you're overcomplicating this. You don't need the check_subscription method at all and that method is why you're trying to make ##perms (or #perm) work.
validate is just a class method like any other and you can give validate block. You can use that block to capture the perm and do away with all the extra machinery:
module SubscriptionControlled extend ActiveSupport::Concern
module ClassMethods
def requires_subscription_to(perm)
validate do
self.errors.add(:base, "Subscription upgrade required for access to this feature") unless self.user[perm]
end
end
end
end

Create single method in Rails that handles multiple method names?

Is it possible to DRY-up the following code:
def is_user?
is_role? ROLES[:user]
end
def is_mod?
is_role? ROLES[:mod]
end
def is_admin?
is_role? ROLES[:admin]
end
private
def is_role?(role)
self.roles & role == role
end
Into a single function, yet still have the ability to call the function names as currently (is_user?, is_mod?, etc)
UPDATE:
Using Aetherus' answer below I created the following for managing user roles (where a user can have multiple roles):
class User < ActiveRecord::Base
# Use bitwise values for more roles (double the previous values)
ROLES = { user: 1, dummy: 2, mod: 4, admin: 8 }
# Add the desired role
def add_role(role)
self.roles |= ROLES[role]
end
# eg: add_role :admin
# Removed the desired role
def remove_role(role)
self.roles &= ~ROLES[role]
end
# methods for each role (mod? admin? etc)
ROLES.keys.each do |role|
define_method("#{role}?") do
self.roles & ROLES[role] == ROLES[role]
end
end
end
You can define multiple methods with one single ruby code block.
%w(user mod admin).each do |role|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{role}?
role == '#{role}' && roles.include?('#{role}')
end
RUBY
end
Or a more clear way:
%w(user mod admin).each do |role|
define_method("#{role}?") do
self.role == role && roles.include?(role)
end
end
By the way, in ruby, the is_ prefix is not needed, since the trailing ? tells the programmers that method returns a true or a false.
You can go with method_missing.
The simplest possible solution would be something like:
class Roles
def method_missing(method_name, *args, &block)
if /^is_(?<role_name>\w+)\?$/ =~ method_name
is_role?(role_name.to_sym)
else
super
end
end
private
def is_role?(role_name)
# just for demo purposes
p "Checking #{role_name}"
end
end
roles = Roles.new
roles.is_user?
roles.is_mod?
roles.is_admin?
In method_missing I'm trying to catch any method that is not implemented (please note, I removed the proper methods is_user?, is_mod? and is_admin?), later, I'm checking if the name of method is of proper format with Regex (/^is_(?<role_name>\w+)\?$/), and if it is, I'm reusing captured role_name.
Slightly more restrictive method_missing.
Problem with this approach is, it will accept any method call, like let's say is_super_user?. In some cases this might be desirable, sometimes not. If you would like to restrict it only to the 3 type of users you've mentioned, you can change the Regex to:
/^is_(user|mod|admin)\?$/
One last thing. When implementing method_missing, you should also take care about respond_to_missing?, which is quite crucial when you would like to assert if the object responds to those magic methods:
class Roles
# ...
def respond_to_missing?(method_name, include_private = false)
/^is_(user|mod|admin)\?$/ =~ method_name
end
end
With this in place, you are able to do:
roles = Roles.new
roles.respond_to? :is_admin? # => true
roles.respond_to? :is_super_user? # => false
Read more here.
Hope that helps!
#Jacob, if you use rails 4, you can use AR#enum feature (http://api.rubyonrails.org/classes/ActiveRecord/Enum.html), no need to implement this by hands.

Single Table Inheritance or Type Table

I am facing a design decision I cannot solve. In the application a user will have the ability to create a campaign from a set of different campaign types available to them.
Originally, I implemented this by creating a Campaign and CampaignType model where a campaign has a campaign_type_id attribute to know which type of campaign it was.
I seeded the database with the possible CampaignType models. This allows me to fetch all CampaignType's and display them as options to users when creating a Campaign.
I was looking to refactor because in this solution I am stuck using switch or if/else blocks to check what type a campaign is before performing logic (no subclasses).
The alternative is to get rid of CampaignType table and use a simple type attribute on the Campaign model. This allows me to create Subclasses of Campaign and get rid of the switch and if/else blocks.
The problem with this approach is I still need to be able to list all available campaign types to my users. This means I need to iterate Campaign.subclasses to get the classes. This works except it also means I need to add a bunch of attributes to each subclass as methods for displaying in UI.
Original
CampaignType.create! :fa_icon => "fa-line-chart", :avatar=> "spend.png", :name => "Spend Based", :short_description => "Spend X Get Y"
In STI
class SpendBasedCampaign < Campaign
def name
"Spend Based"
end
def fa_icon
"fa-line-chart"
end
def avatar
"spend.png"
end
end
Neither way feels right to me. What is the best approach to this problem?
A not very performant solution using phantom methods. This technique only works with Ruby >= 2.0, because since 2.0, unbound methods from modules can be bound to any object, while in earlier versions, any unbound method can only be bound to the objects kind_of? the class defining that method.
# app/models/campaign.rb
class Campaign < ActiveRecord::Base
enum :campaign_type => [:spend_based, ...]
def method_missing(name, *args, &block)
campaign_type_module.instance_method(name).bind(self).call
rescue NameError
super
end
def respond_to_missing?(name, include_private=false)
super || campaign_type_module.instance_methods(include_private).include?(name)
end
private
def campaign_type_module
Campaigns.const_get(campaign_type.camelize)
end
end
# app/models/campaigns/spend_based.rb
module Campaigns
module SpendBased
def name
"Spend Based"
end
def fa_icon
"fa-line-chart"
end
def avatar
"spend.png"
end
end
end
Update
Use class macros to improve performance, and keep your models as clean as possible by hiding nasty things to concerns and builder.
This is your model class:
# app/models/campaign.rb
class Campaign < ActiveRecord::Base
include CampaignAttributes
enum :campaign_type => [:spend_based, ...]
campaign_attr :name, :fa_icon, :avatar, ...
end
And this is your campaign type definition:
# app/models/campaigns/spend_based.rb
Campaigns.build 'SpendBased' do
name 'Spend Based'
fa_icon 'fa-line-chart'
avatar 'spend.png'
end
A concern providing campaign_attr to your model class:
# app/models/concerns/campaign_attributes.rb
module CampaignAttributes
extend ActiveSupport::Concern
module ClassMethods
private
def campaign_attr(*names)
names.each do |name|
class_eval <<-EOS, __FILE__, __LINE__ + 1
def #{name}
Campaigns.const_get(campaign_type.camelize).instance_method(:#{name}).bind(self).call
end
EOS
end
end
end
end
And finally, the module builder:
# app/models/campaigns/builder.rb
module Campaigns
class Builder < BasicObject
def initialize
#mod = ::Module.new
end
def method_missing(name, *args)
value = args.shift
#mod.send(:define_method, name) { value }
end
def build(&block)
instance_eval &block
#mod
end
end
def self.build(module_name, &block)
const_set module_name, Builder.new.build(&block)
end
end

how to add an omnipotent user level to Pundit

I've looked through the documentation and did some searching but i don't see an option for an omnipotent user (super user) level, or how one can be created.
Has anyone seen or created a why of doing this? im thinking it may be possible to tie into core authentication system but im not sure where to do the tie-in.
many thanks..
The only way to do this is to have your authorization checks return true for a user or role that has been designated a "super user." So, it would look like this:
def update?
*normal authorization logic* or is_superuser?
end
def edit?
*normal authorization logic* or is_superuser?
end
#etc...
private
def is_superuser?
# configure how to determine who the super users are and return true/false
end
You can define the is_superuser? private method in the ApplicationPolicy assuming that you inherit your class level policies from the Application Policy; otherwise, you will need to define it in each policy.
I've found a way a little DRYer, by using the inheritance of ApplicationPolicy. I alias access methods and I tie a superuser test before invoking any of them. If user is a super user, I simply return true. I do that at initialization before I need the instance method to be defined to be aliased.
ALIAS_PREFIX = '__original_'
def initialize(user, record)
#user = user
#record = record
[:index?,:show?,:create?,:new?, :update?, :edit?, :destroy?].each do |access_method|
alias_name = ALIAS_PREFIX+access_method.to_s
aliasing_original_method(access_method,alias_name)
self.class.send(:define_method, access_method) do |*args|
superuser? ? (return true) : send(alias_name, *args)
end
end
end
private
def superuser?
#whatever you want to define a super user
end
def aliasing_original_method(old_name, new_name)
self.class.send(:alias_method, new_name, old_name)
self.class.send(:private, new_name)
end
And in [AnyFile]Policy I do:
def initialize(user, record)
super(user, record)
end
This will ensure the true return for each method in sub-policy.
[UPDATE]
The first solution is a little messy, and my knowledge in ruby (and deadlines) doesn't permit me to push it farther. Anyway, I've found another way. As I was always switch-casing the roles of users, I've implemented in ApplicationPolicy a for_roles method.
def for_roles(*args,&block)
return true if superuser?
if args.include?(:all) || (#user.role_symbols & args).any?
block.call
else false
end
end
Then, in any policy, you can do for example
for_roles(:client_admin,:technician) do
#any rule computation, DB request you want
end
#or
for_roles(:all) do
#any rule computation, DB request you want
end

Resources