question on my helper method using self - ruby-on-rails

I created this helper method. In my view I call it with days_left(duedate). I dont really like my helper. Is it possible to use it with self. Since I dont really know how self is being used. Is it the same as this in java or javascript? What object is it related to? Feel free to tune this method. Thx for your time!
def days_left(duedate)
(if duedate.date == Date.today
"Today"
elsif duedate.date-Date.today < 1
"expired"
elsif duedate.date-Date.today == 1
"Tomorrow"
else
"#{(duedate.date-Date.today).to_i}"
end).to_s.html_safe
end

You might try moving this method to your model.
This would be similar to adding a 'full_name' method to a model with the attributes 'first_name' and 'last_name.' You wouldn't store 'full_name' separately in your database, because that would result in redundant, denormalized data.
For example:
class Employee < ActiveRecord::Base
def full_name
"#{first_name} #{last_name}"
end
end
So you could similarly add the 'days_left' method to your model, which fits there because it's adding a friendlier version of an existing data attribute.

Related

class << self in ruby and its methods

I have a model in ruby on rails with the below code, which uses a singelton class definition. Also, som metaprogramming logic. But, I don't understand when this code will invoke.Is it when an attribute below specified is editing?
class Product < ApplicationRecord
class << self
['cat_no', 'effort', 'impact', 'effect', 'feedback'].each do |attr|
define_method "update_#{attr}" do |pr, count, user_id|
pr.order=pr.cat_no
pr.idea=pr.description
pr.update("#{attr}"=>count,:last_modified_by=>user_id)
end
end
end
end
Please help.
Thanks
This code generates five methods, one for each attribute name in the list. All these generated methods take three arguments and will basically look like this (I use the impact attribute name as an example):
def self.update_impact(pr, count, user_id)
pr.order = pr.cat_no
pr.idea = pr.description
pr.update("impact" => count, :last_modified_by => user_id)
end
That means there are five methods generated that update the passed in pr with some data from itself and with a count and a user_id.
Note that this method only deals with a specific pr therefore it is certainly better to use an instance instead of a class method as Stefan already suggested in his comment. And IMO there is not really a benefit in meta-programming here. I would change the logic to
def update_count(type, count, user_id) # or any another name that makes sense in the domain
if type.in?(%i[cat_no effort impact effect feedback])
update(
:order => cat_no,
:idea => description,
:last_modified_by => user_id,
type => count
)
else
raise ArgumentError, "unsupported type '#type'"
end
end
and call it instead of
Model.update_impact(pr, count, user_id)
like this
pr.update_count(:impact, count, user_id)

ruby string formatting in rails

So the goal is to turn for instance "ProductCustomer", which comes from the class, into "product customer".
I used to have this:
notification.notifiable.model_name.human.downcase
It didn't work out of course, since if the notifiable is nil it breaks. I don't
want to use try or something similar since it can be solved with using notifiable_type.
So now I changed to this:
notification.notifiable_type.split(/(?=[A-Z])/).join(' ').downcase
But this is way too complex to use every time in the view. So either I would like to define this as a view helper or using some ruby formatting method if there is a simple one.
Can somebody tell me what the Rails convention is in this case? If it's a helper, how does the method looks like and where should I put it?
Options:
Initializer
/your_app/config/initializers/my_initializer.rb
module MyModule
def human_model_name
self.class.to_s.tableize.singularize.humanize.downcase
end
end
ActiveRecord::Base.send(:include, MyModule)
Including MyModule in ActiveRecord::Base will add human_model_name in all ActiveRecord instances. So, you will be able to do...
user.human_model_name #=> user
notification.human_model_name #=> notification
notification.notifiable.human_model_name #=> product customer
any_active_record_instance.human_model_name #=> etc.
To avoid exceptions when notifiable is nil, you can use try method.
notification.try(:notifiable).try(:human_model_name)
A cleaner way can be use delegate
class Notification < ActiveRecord::Base
delegate :human_model_name, to: :notifiable, prefix: true, allow_nil: true
end
Then, you can do:
notification.notifiable_human_model_name # if notifiable is nil will return nil as result instead of an exception
A simple method in your Notification model
class Notification < ActiveRecord::Base
def human_notifable_name
return unless self.notifiable # to avoid exception with nil notifiable
self.notifiable.class.to_s.tableize.singularize.humanize.downcase
end
end
Then...
notification.human_notifable_name
View Helper (If you think this is a view related method only)
module ApplicationHelper # or NotificationHelper
def human_model_name(instance)
return unless instance # to avoid exception with nil instance
instance.class.to_s.tableize.singularize.humanize.downcase
end
end
Then, in your view...
<%= human_model_name(notification.notifiable) %>
Either option is fine. I would use one or the other depending on the case. In this case, I would use the first option. I think you are adding behaviour that can be useful in any model. I mean your method is not directly related with something about notifications. In a more generic way you want a method to return the class name of an ActiveRecord's instance. Today you want the model name of the notifiable ActiveRecord's instance. But, tomorrow you may want the model name of any ActiveRecord model.
To answer the question "Where should I put a method?" I suggest to break (without fear) a little bit the MVC pattern and read about:
Decorators, presenters and delegators
Services
(a little bit old, but you can get the idea)
"ProductCustomer".tableize.singularize.humanize.downcase

How to read attribute only if it is present?

I am trying to display a model attribute only if it is present. If it is not, then a placeholder should be displayed. This is what I've got:
class Person < ActiveRecord::Base
def name
if self.name.blank?
"[You have no name yet]"
else
read_attribute(:name)
end
end
end
However, I am getting a stack level too deep error.
How can this be done?
Thanks for any help.
I agree with Ishank but you can call super to use Rails' getter and then use ActiveSupport's presence method which will return the value if it is present? or otherwise return nil (which will trigger the statement after the ||).
def name
super.presence || "[You have no name yet]"
end
To be clear, stack level too deep is happening because you are checking self.name.blank? - when you use self.name here, that is calling the name method on self (which is the method you are currently in) - so that results in an infinite loop.
This should not be a part of Model. You should write this method in your views.
You can have something like #person.name || "You have no name yet"
You are getting exception stack level too deep because read_attribute[:name] again calls the name method.
Also a thing to keep in mind for using self. According to Ruby style guide:
Avoid self where not required. (It is only required when calling a
self write accessor.)
# bad
def ready?
if self.last_reviewed_at > self.last_updated_at
self.worker.update(self.content, self.options)
self.status = :in_progress
end
self.status == :verified
end
# good
def ready?
if last_reviewed_at > last_updated_at
worker.update(content, options)
self.status = :in_progress
end
status == :verified
end
Can you try this for stack too deep?
def name
if self.first.name.blank?
"[You have no name yet]"
else
read_attribute[:name]
end
end
Personally, i always do something like this, using self[:name] as a way of accessing the database rather than the .name method:
def name
if self[:name].blank?
"[You have no name yet]"
else
self[:name]
end
end
I am still using rails 2.2, so this may function differently for you.
Having said that, a cleaner and more transparent way to do it is to set "[You have no name yet]" as the default value for the name column in the database. Then you don't need to override the accessor method, which always feels a bit dirty to me.
It is not a good practise to include presentation logic in data models.
You should instead use decorators, view objects, or similar, or just do it in the view, but not in the model.
Examples using the Draper gem:
class PersonDecorator < Draper::Decorator
delegate_all
def name
object.name.presence || I18n.t('warnings.no_name_yet')
end
end
In the view:
<%= #person.name.presence || I18n.t('warnings.no_name_yet') %>
See the "Introduce View Objects" section in http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/.

How to query Rails / ActiveRecord model based on custom model method?

Disclaimer: I'm relatively new to rails.
I have a custom method in my model that I'd like to query on. The method, called 'active?', returns a boolean. What I'd really like to do is create an ActiveRecord query of the following form:
Users.where(:active => true)
Naturally, I get a "column does not exist" when I run the above as-is, so my question is as follows:
How do I do the equivalent of the above, but for a custom method on the model rather than an actual DB column?
Instead of using the active? method, you would have a scope to help find items that match.
Something like this...
def self.active
joins(:parent_table).where(:archived => false).where("? BETWEEN parent_table.start_date AND parent_table.end_date ", Time.now)
end
And, you should be able to do this
def active?
User.active.exists?(self)
end
If you would like to reuse this scope for the instance test.
An easy way to do this would be by using the select method with your exiting model method.
Users.select{|u| u.active}
This will return an array so you won't be able to use Active Record Query methods on it. To return the results as an ActiveRecord_Relation object, you can use the where function to query instances that have matching ids:
class User < ActiveRecord::Base
def self.active
active_array = self.select{|r| r.active?}
active_relation = self.where(id: active_array.map(&:id))
return active_relation
end
end

Tracking model changes in Rails, automatically

In my rails app I would like to track who changes my model and update a field on the model's table to reflect.
So, for example we have:
class Foo < ActiveRecord::Base
before_create :set_creator
belongs_to :creator, :class_name => "User"
protected
def set_creator
# no access to session[:user_id] here...
end
end
What's a good testable way for me to get at the user_id from my model? Should I be wacking this data in Thread.current ?
Is it a better practice to hand this information from the controller?
Best practice in MVC is to have your Models be stateless, the controller gets to handle state. If you want the information to get to your models, you need to pass it from the controller. Using a creation hook here isn't really the right way to go, because you are trying to add stateful data, and those hooks are really for stateless behavior.
You can pass the info in from the controller:
Foo.new(params[:foo].merge {:creator_id => current_user.id})
Or you can create methods on User to handle these operations:
class User
def create_foo(params)
Foo.new(params.merge! {:creator_id => self.id})
end
end
If you find yourself writing a lot of permissions code in the controller, I'd go with option 2, since it will let you refactor that code to the model. Otherwise option 1 is cleaner.
Omar points out that it's trickier to automate, but it can still be done. Here's one way, using the create_something instance method on user:
def method_missing(method_sym, *arguments, &block)
meth = method_sym.to_s
if meth[0..6] == "create_"
obj = meth[7..-1].classify.constantize.new(*arguments)
obj.creator_id = self.id
else
super
end
end
You could also override the constructor to require user_ids on construction, or create a method inside ApplicationController that wraps new.
There's probably a more elegant way to do things, but I definitely don't like trying to read state from inside Model code, it breaks MVC encapsulation. I much prefer to pass it in explicitly, one way or another.
Yeah, something like that would work, or having a class variable on your User model
cattr_accessor :current_user
Then in your controller you could have something like:
User.current_user = current_user
inside a before filter (assuming current_user is the logged in user).
You could then extend AR:Base's create/update methods to check for the existence of a created_by/updated_by field on models and set the value to User.current_user.
I'd create new save, update, etc methods that take the user_id from everything that calls them (mainly the controller).
I'd probably extend ActiveRecord:Base into a new class that handles this for all the models that need this behaviour.
I wouldn't trust Thread.current, seems a bit hackish. I would always call a custom method which takes an argument:
def create_with_creator(creator, attributes={})
r = new(attributes)
r.creator = creator
r.save
end
As it follows the MVC pattern. The obviously inherient problem with this is that you're going to be calling create_with_creator everywhere.
You might find PaperTrail useful.
Probably you could check out usertamp plugins, found two in github
http://github.com/delynn/userstamp/tree/master
http://github.com/jnunemaker/user_stamp/tree/master

Resources