Im trying to create an before_save callback for certain models that will add links and formatting to text and that gets saved in a special field. It won't let me include the URL helpers in the callback.
Here's my code:
module SocialText
extend ActiveSupport::Concern
included do
before_save :action_before_save
end
def action_before_save
self.body_formatted = htmlizeBody(self.body)
end
def htmlizeBody(body)
include Rails.application.routes.url_helpers
include ActionView::Helpers
#replace all \ns with <br>
body = body.gsub(/\n/, ' <br/> ')
words = body.split(/\s/)
words.map! do |word|
if word.first == '#'
username = extractUsernameFromAtSyntax word
user = User.find_by! username: username
if not user.nil?
link_to(word, profile_path(user.username))
else
word
end
else
word
end
end
words.join " "
end
def extractUsernameFromAtSyntax(username)
matchData = username.match(/#(\w+)(['.,]\w*)?/)
if not matchData.nil?
matchData[1]
else
username
end
end
end
I'm getting:
NoMethodError (undefined method `include`)
How do I get the helper? is there a better way to do this?
In your htmlizeBody function:
include Rails.application.routes.url_helpers
include ActionView::Helpers
This is in the wrong scope, moving it underneath extend ActiveSupport::Concern will resolve your error.
Another question you could ask yourself is why you need to use view helpers at the concern level?
include Rails.application.routes.url_helpers is usually used when modifying the default url host options (typically when you need to interface with an external API). In this case, it would make sense to use it in the /lib directory.
See this SO post and this post on using url helpers for more info
include operates on a class instance object and you call it like an instance method.
You should take that include part outside of your methods.
Consider using require at your module scope.
Related
I have an EmailHelper class defined in /lib/email_helper.rb. the class can be used directly by a controller or a background job. It looks something like this:
class EmailHelper
include ActionView::Helpers::DateHelper
def self.send_email(email_name, record)
# Figure out which email to send and send it
time = time_ago_in_words(Time.current + 7.days)
# Do some more stuff
end
end
When time_ago_in_words is called, the task fails with the following error:
undefined method `time_ago_in_words' for EmailHelper
How can I access the time_ago_in_words helper method from the context of my EmailHelper class? Note that I've already included the relevant module.
I've also tried calling helper.time_ago_in_words and ActionView::Helpers::DateHelper.time_ago_in_words to no avail.
Ruby's include is adding ActionView::Helpers::DateHelper to your class instance.
But your method is a class method (self.send_email). So, you can replace include with extend, and call it with self , like this:
class EmailHelper
extend ActionView::Helpers::DateHelper
def self.send_email(email_name, record)
# Figure out which email to send and send it
time = self.time_ago_in_words(Time.current + 7.days)
# Do some more stuff
end
end
That's the difference between include and extend.
Or...
you can call ApplicationController.helpers, like this:
class EmailHelper
def self.send_email(email_name, record)
# Figure out which email to send and send it
time = ApplicationController.helpers.time_ago_in_words(Time.current + 7.days)
# Do some more stuff
end
end
I prefer to include this on the fly:
date_helpers = Class.new {include ActionView::Helpers::DateHelper}.new
time_ago = date_helpers.time_ago_in_words(some_date_time)
Guys today I'm trying to create global method for all my project models in rails 4
I created something like that under this path lib/query.rb
module Query
def custom my_query
self.where(my_query)
end
end
then added this code in this file lib/application.rb to allow rails to load the files under this path
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += %W(#{config.root}/lib)
then included my method in my model by using this command
include Query
now should every thing ready to use my custom method , but when I tried to call my method in the controller like that
def index
#users= Users.custom(params[:query])
end
I got the error
undefined method `custom'
what I should do now ??
why i got this error ??
I think you should use concern for your module. Add your file in app/models/concerns.
# app/models/concerns/query.rb
module Query
extend ActiveSupport::Concern
included do
#you can use a scope
scope :my_query, ->(just_a_param){ .... }
end
module ClassMethods
#or a method
def self.another_query
where(....)
end
end
end
Of course you need to include the module in your model. As concern erd default in rails, you no longer need to change config autoload paths.
As a class method, you'll need the "self."
def self.custom my_query
self.where(my_query)
end
EDIT: If you want this in all ActiveRecord models, you can add it as an initializer
#config/initializers/active_record_extensions.rb
class ActiveRecord::Base
def self.custom my_query
self.where(my_query)
end
end
If you just want this on a single class, a concern would work.
In your example, there is no reference given between your class Users and your method custom. First: if Users refers to a Ruby on Rails class it is probably called User (see also comment of japed). So change the call. Next, your User class must inherited from ActiveRecord else it would not be aware of the existence of 'where'. For details check your app/models/user.rb
Then Swards' suggestion should work for you. Stop your application and restart. Now it should work.
Guys I found the true way to make it
First my impropriety was the include that I set in the model
It should be extend Query
then it will work well
so the true code will be
create your method file under this path lib/query.rb
then set this code in it
module Query
def custom my_query
self.where(my_query)
end
end
then added this code in this file lib/application.rb
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += %W(#{config.root}/lib)
then extend the method in the model by using this command
extend Query
and in your controller query you can use the method like that
def index
#users= Users.custom(params[:query])
end
This is my solution, not exactly the 'Rails way', but using some sort of decorator pattern:
#user = CustomQuery.find_for(User.find(params[:search])).perform!
class CustomQuery
attr_reader :params, :klass
def initialize(klass)
#params = params
#klass = klass
end
def self.find_for(params)
CustomQuery.new(params)
find_model_for(params.tap {})
end
def perform!
return params unless params.nil?
klass.all
end
def find_model_for(klass)
#klass = klass
end
end
While I'm not sure about the process to create a global method, I can tell that your Ruby code is not valid:
def custom my_query
self.where(my_query)
end
It would need to be:
def custom (my_query)
self.where(my_query)
end
Suppose I have a function trim_string(string) that I want to use throughout my Rails app, in both a model and a controller. If I put it in application helper, it gets into the controller. But application helper isn't required from within models typically. So where do you put common code that you'd want to use in both models and controllers?
In answer to the specific question "where do you put common code that you'd want to use in both models and controllers?":
Put it in the lib folder. Files in the lib folder will be loaded and modules therein will be available.
In more detail, using the specific example in the question:
# lib/my_utilities.rb
module MyUtilities
def trim_string(string)
do_something
end
end
Then in controller or model where you want this:
# models/foo.rb
require 'my_utilities'
class Foo < ActiveRecord::Base
include MyUtilities
def foo(a_string)
trim_string(a_string)
do_more_stuff
end
end
# controllers/foos_controller.rb
require 'my_utilities'
class FoosController < ApplicationController
include MyUtilities
def show
#foo = Foo.find(params[:id])
#foo_name = trim_string(#foo.name)
end
end
It looks like you want to have a method on the String class to "trim" itself better than a trim_string function, right? can't you use the strip method? http://www.ruby-doc.org/core-2.1.0/String.html#method-i-strip
You can add new methods to the string class on an initializer, check this In Rails, how to add a new method to String class?
class String
def trim
do_something_and_return_that
end
def trim!
do_something_on_itself
end
end
That way you can do:
s = ' with spaces '
another_s = s.trim #trim and save to another
s.trim! #trim itself
but check the String class, it looks like you already have what you need there
In my rails model Post.rb I have the following methods setup:
def category_names(seperator = ", ")
categories.map(&:name).flatten.join(seperator).titleize
end
def publish_date
read_attribute(:publish_date).strftime('%A, %b %d')
end
I would like to move these into PostsHelper module under helpers. But get a no-method error when I do so because I guess the reference to self is lost.
So how can I fix this? Is a helper module the wrong place for these methods?
Helper methods are designed mainly to be used in views, if I'm not mistaking.
What I'm sure about is your helper methods are outside of the scope of your model, meaning you need to pass to them the attribute to work with. Example:
def category_names(categories, seperator = ", ")
categories.map(&:name).flatten.join(seperator).titleize
end
And call in your view:
category_names #post.categories
If you find your self writing "helper" method that are not exclusively used in your view, you could create a service object and include them in your model.
Edit: Service object
You can create a "services" directory under "app" directory and create your classes there.
Let me give you an example. I got a User model class, and I want to group all password related methods in a UserPassword service object.
User class:
class User < ActiveRecord::Base
include ::UserPassword
...
end
UserPassword service object:
require 'bcrypt'
module UserPassword
def encrypt_password
if password.present?
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
end
end
module ClassMethods
def authenticate(email, password)
user = find_by_email email
if user and user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
user
end
end
end
def self.included(base)
base.extend(ClassMethods)
end
end
So my User instanced object (i.e u = User.first) can call u.encrypt_password, and my User class can call User.authenticate.
There is maybe other way, but I find it flexible and easy to write and maintain :)
Helpers are always intended to help views, But if you want your Models to be clean and keep the unrelated methods as separate try using concerns in Rails 3. Concerns directory will be present by default in Rails 4.
There is a nice blogpost by DHH for same.
http://37signals.com/svn/posts/3372-put-chubby-models-on-a-diet-with-concerns
I'm installing a forum using the Forem gem. There's an option that allows avatar personalization, since it's possible to login with Facebook. You just specify your method in the User model and that's it.
# Forem initializer
Forem.avatar_user_method = 'forem_avatar'
# User model
def forem_avatar
unless self.user_pic.empty?
self.user_pic
end
end
But I want a fallback on Gravatar for normal, non-facebook accounts. I've found the method on Forem and in theory, I need to call the avatar_url method:
# User model
def forem_avatar
unless self.user_pic.empty?
self.user_pic
else
Forem::PostsHelper.avatar_url self.email
end
end
However, Forem isn't an instance, but a module and I can't call it nor create a new instance. The easy way is to copy the lines of that method, but that's not the point. Is there a way to do it?
Thanks
Update
Both answers are correct, but when I call the method either way, there's this undefined local variable or method 'request' error, which is the last line of the original avatar_url.
Is there a way to globalize that object like in PHP? Do I have to manually pass it that argument?
perhaps reopen the module like this:
module Forem
module PostsHelper
module_function :avatar_url
end
end
then call Forem::PostsHelper.avatar_url
if avatar_url call other module methods, you'll have to "open" them too via module_function
or just include Forem::PostsHelper in your class and use avatar_url directly, without Forem::PostsHelper namespace
If you want to be able to use those methods in the user class, include them and use
class User < ActiveRecord::Base
include Forem::PostsHelper
def forem_avatar
return user_pic if user_pic.present?
avatar_url email
end
end
Another way would be to set the Forem.avatar_user_method dynamically since the Forem code checks it it exists before using it and defaults to avatar_url if it does not.
class User < ActiveRecord::Base
# This is run after both User.find and User.new
after_initialize :set_avatar_user_method
# Only set avatar_user_method when pic is present
def set_avatar_user_method
unless self.user_pic.empty?
Forem.avatar_user_method = 'forem_avatar'
end
end
def forem_avatar
self.user_pic
end
end
This way you dont pollute your model with unnecessary methods from Forem and don't monkey patch Forem itself.