Two modules with same method names included in same class - ruby-on-rails

I am working with ruby on rails and I am basically trying to include two modules into the same model/class with both modules having the same method names. An example will be demonstrated below, however my questions are:
Is there a way to include module conditionally? or
Is there a way to invoke based on the specific instance of the class.
An example is a simple complete profile wizard.
How its suppose to work
Case 1: If the user is lets say a Transporter, step_one is completed when the user has a company_name is present.
Case 2: On the otherhand if the user is a Client, step_one is completed when the user has a telephone present.
class User < ApplicationRecord
include ClientWizard
include TransporterWizard
end
module ClientWizard
def step_one_completed?
self.name.present?
end
end
module TransporterWizard
def step_one_completed?
self.company_name.present?
end
end

No, module methods all exist within the class's namespace. Consequently, this doesn't seem like a particularly good use case for modules.
You could give the methods module-specific names (client_wizard_step_one_completed?), but I'd recommend instead defining the wizards as separate classes, and passing the user instance as a parameter.
class User < ApplicationRecord
def client_wizard
ClientWizard.new(self)
end
end
class ClientWizard
def initialize(user)
#user = user
end
def step_one_completed?
#user.name.present?
end
end

Related

Accessing class property to define instance methods in rails concern

I'm trying my hands on metaprogramming after a long pause. I found a few questions but could not get an input to solve my problem so I hope someone can enlighten me.
In a rails 5 app, I am trying to write a concern that provides a class method to set configuration options. With those options, I want to define instance methods.
module Base64Attachable
extend ActiveSupport::Concern
class_methods do
attr_reader :base64_attachable_property
private
def base64_attachable(property)
#base64_attachable_property = property
end
end
included do
# ?
end
end
The concern above is used inside a User model:
class User < ApplicationRecord
include Base64Attachable
base64_attachable :image
end
In my understanding, the concern sets up the class method that is being called in the user model. However I do not seem to be able to get the base64_attachable_property inside the included block to define further methods based on the value of it. I thought I would find anything I need in self.class inside the included block, but that's not the case.
The aim in this case is to use define_method to define setters, getters and other methods for image in the user model.
What am I missing here?
The included block is run at the moment the concern is included on the class, the base64_attachable :image line is not run yet.
I'd suggest you follow what official gems does. Check ActiveStorage for example https://github.com/rails/rails/blob/530f7805ed5790af1d472a041bc74089dc183f47/activestorage/lib/active_storage/attached/model.rb#L35. It defines the methods that depends on that property right inside the class method (it uses class_eval, but I guess you can use define_method too):
def has_one_attached(name, dependent: :purge_later)
generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name}
#active_storage_attached_#{name} ||= ActiveStorage::Attached::One.new("#{name}", self)
end
def #{name}=(attachable)
attachment_changes["#{name}"] =
if attachable.nil?
ActiveStorage::Attached::Changes::DeleteOne.new("#{name}", self)
else
ActiveStorage::Attached::Changes::CreateOne.new("#{name}", self, attachable)
end
end
CODE
has_one :"#{name}_attachment", ......etc....

Ruby on Rails, calling a very big method from a model

I have a very big function in my model and I want to store it somewhere else in order to keep my model dry. I read that storing methods in ApplicationHelper and then calling them from a model is a bad idea. What is a good idea then?
I want to have a separate file with my big methods and call them from a model.
You can create a "plain old ruby object (PORO)" to do your work for you. let's say you had a method that calculates the amount overdue for a user.
So, you can create app/services/calculates_overages.rb
class CalculatesOverages
def initialize(user)
#user = user
end
def calculate
# your method goes here
end
end
Then, you can:
class User < ActiveRecord::Base
def overage_amount
CaluclatesOverage.new(self).calculate
end
end
Or, in a controller you could:
def show
#amount = CaluclatesOverage.new(current_user).calculate
end
The app/services directory could also be app/models, or the lib directory. There's no set convention for this (yet).
Use a Concern. https://gist.github.com/1014971
It's simple. In app/models/concerns create a file your_functionality.rb as follows:
module YourFunctionality
extend ActiveSupport::Concern
def your_fat_method
# insert...
end
end
And in your model simply:
include YourFunctionality

RoR, Calling column names from a module

I'm using Ruby on Rails. and I have a module called PatientFactory and it will be included in a Patient model.
I need to access a Patient's id, from this module.
module PatientFactory
def self.included(base)
# need to access instance variable here
...
end
end
But more importantly, I need it in the self.included(base)
I can easily access it outside of this method but how do I access it inside?
Given you want to do this:
class Patient < ActiveRecord::Base
include PatientFactory
end
then you would access the id like this:
module PatientFactory
def get_patient_id
self.id
end
end
a = Patient.new
a.id #=> nil
a.save
a.id #=> Integer
when your module gets included it a class, all of its methods become instance methods of that class. if you rather extend them, they get inserted in your class's singleton class, therefore they'll be accessible as if they were class methods.
class Patient < ActiveRecord::Base
include PatientFactory
end
Then you can access the instance as if they were part of Patient's methods.
If you still need to preserve your workflow as you mentioned, Yehuda might offer some help;
http://yehudakatz.com/2009/11/12/better-ruby-idioms/

How to dynamically generate association names?

I am using Ruby on Rails 3.2.2 and the Squeel gem. I have following statements and I am trying to refactoring the my_squeel_query method in a Mixin module (since it is used by many of my models):
# Note: 'article_comment_associations' and 'model_as_like_article_comment_associations'
# refer to database table names.
class Article < ActiveRecord::Base
def my_squeel_query
commenters.
.where{
article_comment_associations.article_id.eq(my{self.id}) & ...
}
end
end
class ModelAsLikeArticle < ActiveRecord::Base
def my_squeel_query
commenters.
.where{
model_as_like_article_comment_associations.article_id.eq(my{self.id}) & ...
}
end
end
My problem is that I can not refactoring article_comment_associations and model_as_like_article_comment_associations statements by generating a dynamic name in the Mixin module. That is, if that was a String I could dynamically generate the related name by using something like "#{self.class.to_s.singularize}_comment_associations" as the following:
class Article < ActiveRecord::Base
include MyModule
end
class ModelAsLikeArticle < ActiveRecord::Base
include MyModule
end
module MyModule
def my_squeel_query
commenters.
.where{
# Note: This code doesn't work. It is just an sample.
"#{self.class.to_s.singularize}_comment_associations".article_id.eq(my{self.id}) & ...
}
end
end
But, since it is not my case, I cannot "build" the name and make the my_squeel_query to be "shared" across models.
How can I dynamically generate association names related to the Squeel gem? Should I think to refactoring in another way? What do you advice about?
Since the DSL is instance_evaled, you can actually say something like:
def my_squeel_query
base = self
commenters.
.where{
# Note: This code does work. Because it's awesome.
__send__("#{base.class.to_s.singularize}_comment_associations").
article_id.eq(my{self.id})
}
end
You can do this if you generate the methods dynamically. The Module.included method is provided for this purpose:
module ModuleAsLikeArticle
def self.included(base)
base.send(:define_method, "#{base.to_s.singularize}_comment_associations") do
# ...
end
end
end
This gets triggered when the module is imported with include and allows you to create methods specifically tailored for that.
As a note you might want to use base.name.underscore.singularize for a more readable method name. By convention, method names should not have upper-case in them, especially not as the first character.
Conventional Rails type applications use a different approach, though, instead defining a class method that can be used to create these on-demand:
module ModuleAsLikeArticle
def has_comments
base.send(:define_method, "#{base.to_s.singularize}_comment_associations") do
# ...
end
end
end
This would be used like this:
class ModelAsLikeArticle < ActiveRecord::Base
extend MyModule
has_comments
end
Since the method is not created until has_comments is called, you can safely extend ActiveRecord::Base and then insert the appropriate call in all the classes which require that functionality.
I think you might find what you need in the Rails Reflection class (http://api.rubyonrails.org/classes/ActiveRecord/Reflection/ClassMethods.html), which, as the page says, allows you to interrogate ActiveRecord classes about their associations and aggregations.

How to get the current instance module in Ruby

That title doesn't really explain everything so here goes. I have two Rails engines that share some functionality (ie. user model and authentication). I have a base User class and then two other User classes that inherit from this base class for each app like so:
class User; end
class App1::User < ::User; end
class App2::User < ::User; end
My authentication has a method similar to the following
def user_from_session
User.find_by_id(session[:user_id])
end
which is included in my application_controller. My problem here is that when a user is fetched... it always uses the base User class. What I really want is to be able to fetch a User that is the same type as the app calling that method.
For instance, if a user is on SomeController:
class App1::SomeController < ApplicationController; end
I want the method in the application_controller to pull out the App1 so that it instantiates an App1::User rather than just a User
Is this possible?
I'm NOT looking for a solution that involves two user_from_session methods, one for each application. I am aware of how to implement that. I'm more interested in know if this type of thing is possible in Ruby.
Though I'd caution you to find a better, less hacky way to do this, here's how you might do it:
def user_from_session
# App1::Whatever::FooController -> App1::Whatever
module_name = self.class.name.split('::')[0..-2].join('::')
# App1::Whatever -> App1::Whatever::User
user_class = "#{module_name}::User".constantize
# App1::Whatever::User.find_by_id(...)
user_class.find_by_id(session[:user_id])
end

Resources