How to make helpers for models - ruby-on-rails

I have this private method in my User model
class User < ActiveRecord::Base
attr_accessible :email, :fullname, :password, :username
has_secure_password
before_create { generate_token(:remember_token) }
.
.
private
def generate_token(col)
begin
self[col] = SecureRandom.urlsafe_base64
end while User.exists?(col => self[col])
end
end
How can i make generate_token available In other models?
Thanks

many options here are some:
use a plain, simple ruby module.
module TokenRememberable
private
def generate_token(col)
begin
self[col] = SecureRandom.urlsafe_base64
end while User.exists?(col => self[col])
end
end
class User < ActiveRecord::Base
include TokenRememberable
before_create { generate_token(:remember_token) }
end
for more complex functionnality, use ActiveSupport::Concern :
module TokenRememberable
extend ActiveSupport::Concern
# thanks to Concern, this block wil be evaluated
# in the context of the including class
included do
before_create { generate_token(:remember_token) }
end
private
def generate_token(col)
begin
self[col] = SecureRandom.urlsafe_base64
end while self.class.exists?(col => self[col])
end
end
class User < ActiveRecord::Base
include TokenRememberable
end
extract the functionnality in a dedicated class, and possibly use composition
# we only need a class method here, but you can also build
# full-fledged objects as you need
class TokenGenerator
def self.generate_token
# your generation logic here
end
end
class User
def after_initialize
#token_generator = TokenGenerator # or TokenGenerator.new( self ), for instance
end
attr_reader :token_generator
delegate :generate_token, to: :token_generator # optionnaly use delegation
end
NOTE :
this is not really a rails issue, more a Ruby one. You should document yourself more on the language you are using... Modules are a very common idiom. I can give you some good reference books if you need

a few approaches:
slightly dependent:
do:
class User < ActiveRecord::Base
before_create { User::Helpers.new(self).generate_token(:remember_token) }
class Helpers < Struct.new(:user)
def generate_token(col)
begin
user.send("#{col}=", SecureRandom.urlsafe_base64)
end while User.exists?(col => user.send(col))
end
end
end
independent: don't pass the user object to the class, just make the method render a random token and assign it from the model.

Related

Rails, issue with auto loading

So im having this weird issue for some reason i dont know why it is happening.
So, i have this extension in conifg/initializers/has_friendly_token.rb
ActiveRecord::Base.define_singleton_method :has_friendly_token do
extend FriendlyId
friendly_id :token
before_validation :set_unique_token, if: :set_token?
define_method :set_token? do
new_record? || saved_change_to_token?
end
define_method :set_unique_token do
table_name = self.model_name.route_key
scope = persisted? ? self.class.where("#{table_name}.id != ?", id) : self.class.default_scoped
while token.nil? || scope.where(token: token).exists?
self.token = SecureRandom.base58(24)
end
end
end
And I'm using it in two different models
class Business < ApplicationRecord
include Colorful, Channels, Rails.application.routes.url_helpers
has_friendly_token
end
and
class Conversation < ApplicationRecord
has_friendly_token
include AASM, Colorful, Rails.application.routes.url_helpers
end
the thing is, whenver i try to to run i get this error
`method_missing': undefined local variable or method `has_friendly_token' for Business (call 'Business.connection' to establish a
connection):Class (NameError)
Did you mean? has_secure_token
but it works in conversation. Any ideas why this happenes?
I can't see a good reason to use an initializer to monkeypatch ActiveRecord::Base here in the first place. Instead create a module and extend your ApplicationRecord class with it as this is simply a "macro" method.
module FriendlyToken
def has_friendly_token
extend FriendlyId
friendly_id :token
before_validation :set_unique_token, if: :set_token?
define_method :set_token? do
new_record? || saved_change_to_token?
end
define_method :set_unique_token do
table_name = self.model_name.route_key
scope = persisted? ? self.class.where("#{table_name}.id != ?", id) : self.class.default_scoped
while token.nil? || scope.where(token: token).exists?
self.token = SecureRandom.base58(24)
end
end
end
end
class ApplicationRecord < ActiveRecord::Base
extend FriendlyToken
end
Unlike a monkeypatch its easy to test this code and it will be picked up by documentation tools like rdoc or yard as well as coverage tools.
You could also just simply write it as a concern since your macro method does not take any arguments anyways:
module FriendlyToken
extend ActiveSupport::Concern
included do
extend FriendlyId
friendly_id :token
before_validation :set_unique_token, if: :set_token?
end
def set_token?
new_record? || saved_change_to_token?
end
def set_unique_token?
table_name = self.model_name.route_key
scope = persisted? ? self.class.where("#{table_name}.id != ?", id) : self.class.default_scoped
while token.nil? || scope.where(token: token).exists?
self.token = SecureRandom.base58(24)
end
end
end
class Business < ApplicationRecord
include Colorful,
Channels,
Rails.application.routes.url_helpers,
FriendlyToken
end
The advantage here is easier debugging since the methods are not defined dynamically.
See 4 Ways To Avoid Monkey Patching.

How do a custom method applicable to various models

I have the following method called capitalizeEachWord. Inside this method there is an attribute called company
class BusCompany < ActiveRecord::Base
attr_accessible :company
before_save :capitalizeEachWord
validates :company,presence: true,
uniqueness: { case_sensitive: false },
format: /^([a-zA-z0-9]+\s?){1,}$/
def capitalizeEachWord
self.company=self.company.downcase.split.map(&:capitalize).join(' ')
end
end
I would like that this method not use the attribute company directly, but receives this attribute as a parameter for doesn't do it dependent of the model BusCompany. Something as the following. The problem is that this method I going to use in various models and don't want to write it in each model but use the inheritance
class BusCompany < ActiveRecord::Base
attr_accessible :company
before_save :capitalizeEachWord(self.company)
validates :company,presence: true,
uniqueness: { case_sensitive: false },
format: /^([a-zA-z0-9]+\s?){1,}$/
def capitalizeEachWord(attribute)
self.attribute=self.attribute.downcase.split.map(&:capitalize).join(' ')
end
end
Add the following code into config/initializers/capitalizer.rb
module Capitalizer
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def capitalize(*attributes)
#attributes_to_capitalize = attributes
before_save :capitalize_each_word
end
def attributes_to_capitalize
Array.new(#attributes_to_capitalize)
end
end
def capitalize_each_word
self.class.attributes_to_capitalize.each do |attr|
if value = send(attr)
self.send("#{attr}=", value.strip.titleize)
end
end
end
end
And then in your class:
class BusCompany < ActiveRecord::Base
include Capitalizer
capitalize :company
...
end
First, I'd recommend you override the setter for company instead of using error prone callbacks, like this:
class BusCompany < ActiveRecord::Base
# you can also use #titleize instead of capitalize each word
# also use try, in case `arg` is nil
def company=(arg)
super arg.try(:titleize)
end
end
Then you can use modules to wrap this functionality into a reusable unit. Throw this in a file in your concerns folder, or just in to the models folder:
module CapitalizedSetter
def capitalize_setter(*attr_names)
# for each attr name, redifine the setter so it supers the titleized argument instead
attr_names.each do |attr|
define_method(:"#{attr}=") { |arg| super arg.try(:titleize) }
end
end
end
Finally extend it into the desired models:
class BusCompany
extend CapitalizedSetter
capitalized_setter :company
end

How do I add global Rails helpers like has_many and devise_for?

In Rails, I'd like to apply functionality to many models at the same time, having the ability to add it to other models in the future.
I'm looking for something like...
class Stuff < ActiveRecord::Base
some_tag
end
class Thing < ActiveRecord::Base
some_tag
end
to give those and any other models with
some_tag
the functionality of
class Functionality
has_many :other_things, polymorphic: true
def does_something
end
end
First step: Create a module with the method you want to add to your classes:
module FooFunction
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def does_something(options = {})
...
end
end
end
Next step: Include that module into the class
# in config/initializers/foo_function.rb
class Object
include FooFunction
end

ActiveRecord Problems using callbacks and STI

Hey folks, following problem with Rails and STI:
I have following classes:
class Account < AC::Base
has_many :users
end
class User < AC::Base
extend STI
belongs_to :account
class Standard < User
before_save :some_callback
end
class Other < User
end
end
module STI
def new(*args, &block)
type = args.dup.extract_options!.with_indifferent_access.delete(:type)
if type.blank? or (type = type.constantize) == self
super(*args, &block)
else
type.new(*args, &block)
end
end
end
And now the problem:
Without rewriting User.new (in module STI), the callback inside User::Standard gets never called, otherwise the account_id is always nil if I create users this way:
account.users.create([{ :type => 'User::Standard', :firstname => ... }, { :type => 'User::Other', :firstname => ... }])
If I'm using a different approach for the module like:
module STI
def new(*args, &block)
type = args.dup.extract_options!.with_indifferent_access.delete(:type)
if type.blank? or (type = type.constantize) == self
super(*args, &block)
else
super(*args, &block).becomes(type)
end
end
end
Then instance variables are not shared, because it's creating a new object.
Is there any solution for this problem without moving the callbacks to the parent class and checking the type of class?
Greetz
Mario
Maybe there's something I don't know, but I've never seen Rails STI classes defined in that manner. Normally it looks like...
app/models/user.rb:
class User < AC::Base
belongs_to :account
end
app/models/users/standard.rb:
module Users
class Standard < User
before_save :some_callback
end
end
app/models/users/other.rb:
module Users
class Other < User
end
end
It looks as though you are conflating class scope (where a class "lives" in relation to other classes, modules, methods, etc.) with class inheritance (denoted by "class Standard < User"). Rails STI relationships involve inheritance but do not care about scope. Perhaps you are trying to accomplish something very specific by nesting inherited classes and I am just missing it. But if not, it's possible it's causing some of your issues.
Now moving on to the callbacks specifically. The callback in Standard isn't getting called because the "account.users" relationship is using the User class, not the Standard class (but I think you already know that). There are several ways to deal with this (I will be using my class structure in the examples):
One:
class Account
has_many :users, :class_name => Users::Standard.name
end
This will force all account.users to use the Standard class. If you need the possibility of Other users, then...
Two:
class Account
has_many :users # Use this to look up any user
has_many :standard_users, :class_name => Users::Standard.name # Use this to look up/create only Standards
has_many :other_users, :class_name => Users::Other.name # Use this to look up/create only Others
end
Three:
Just call Users::Standard.create() and Users::Other.create() manually in your code.
I'm sure there are lots of other ways to accomplish this, but there are probably the simplest.
So I solved my problems after moving my instance variables to #attributes and using my second approach for the module STI:
module STI
def new(*args, &block)
type = args.dup.extract_options!.with_indifferent_access.delete(:type)
if type.blank? or (type = type.constantize) == self
super(*args, &block)
else
super(*args, &block).becomes(type)
end
end
end
class User < AR:Base
extend STI
belongs_to :account
validates :password, :presence => true, :length => 8..40
validates :password_digest, :presence => true
def password=(password)
#attributes['password'] = password
self.password_digest = BCrypt::Password.create(password)
end
def password
#attributes['password']
end
class Standard < User
after_save :some_callback
end
end
Now my instance variable (the password) is copied to the new User::Standard object and callbacks and validations are working. Nice! But it's a workaround, not really a fix. ;)

adding class methods to ActiveRecord::Base

I have created an instance method which is also a callback (if that makes sense) which does some stuff thats irrelevant. I would love to be able to just call:
class Model < ActiveRecord::Base
fix_camelcase_columns
end
Instead at the moment I have this:
def after_find
self.class.columns.each do |column|
self.instance_eval("def #{column.name.to_underscore}; self.#{column.name}; end;")
end
end
I would love to abstract this and use it on other classes. Any pointers?
Well, you can open up ActiveRecord::Base and throw a method there:
class ActiveRecord::Base
def self.fix_camelcase_columns
define_method :after_find do
...
end
end
end
For a cleaner way, create a module:
module CamelcaseFixer
def self.included(base)
base.extend(self)
end
def fix_camelcase_columns
define_method :after_find do
...
end
end
end
and then in your model do
class Model < ActiveRecord::Base
include CamelcaseFixer
fix_camelcase_columns
end
Didn't test the code, see if it works.

Resources