Ruby (rails) conflicting class and modules names when nested - ruby-on-rails

I have a basic User model in app/models/user.rb. I also have a few services in lib. For example, I have lib/services/user/creation_service.rb. The following code generates an error:
# lib/services/user/creation_service.rb
module Services
module User
class CreationService
...
def create_new_user
# User.new below causes an error because it defaults to Services::User which is a module instead of User which is an ActiveRecord class
User.new
...
end
...
end
end
end
Is there any way to get User.new to refer to app/models/user.rb instead of the Services::User module in the code above?
Any help would be greatly appreciated!

Use ::User.new instead of User.new. The double colon basically tells ruby to look for the constant that does not have a parent.
So in this case ::User will point to User which is a class instead of Services::User which is a module.

Related

Retrieve Model type in Rails Helper

Does a helper class in Rails 5 (for example UserHelper) have access to the model type it is related to (User)?
I have some shared logic AddFilter that requires the model type to work propery. I now enforce a method filter_for to specify which model is used:
module AddFilter
def filter_for
raise "filter_for not implemented"
end
#...other code
end
Currently I include that logic in many of my helpers:
module UserHelper
include AddFilter
def filter_for
User
end
end
It is possible to retrieve the model type in the AddFilter directly?
Apparently since helpers are included in controllers, a feature from controllers can be used:
# add_filter.rb
def filter_for
controller_path.classify.constantize
end
This retrieves the controller path, classifies the name, then tries to find the constant specified.
Still feels rather hacky, better solutions appreciated!

Two modules with same method names included in same class

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

Rails - proper way to create admin section (`module` or `Admin::`)?

I'm new in Ruby and RoR and I'd like to create an admin section for a demo app.
From a little research I've done I've found two different options for creating an admin section. Example:
# config/routes.rb
namespace :admin do
resources :users
end
# app/controllers/admin_controller.rb
class AdminController < ApplicationController
before_filter :authorized?
...
end
Which of the two options is the most proper way to define controllers for the admin section, or they are both equally same?
# app/controllers/admin/users_controller.rb
# I think this is what rails generates if I run the "rails g controller admin::users" command
class Admin::UsersController < AdminController
...
end
# or instead
module Admin
class UsersController < AdminController
....
end
end
Both approaches yield to the same result, which is an UsersController which inherits from AdminController and is found in the Admin module (namespace).
Admin::MyClass is just a shortcut for module Admin ... class MyClass, but...
I would however prefer the explicit nested code (with module Admin on its own line), because it does make a different if the Admin-module has never been defined before. This probably won't happen to you when hacking with standard rails, but can happen when you write ruby code outside of rails.
See these examples:
class I::Am::AClass
end
i = I::Am::AClass.new
puts i.inspect
will lead to
i.rb:1:in `<main>': uninitialized constant I (NameError)
if you never declared the I and nested Am modules before in your code.
Whereas
module I
module Am
class AClass
end
end
end
i = I::Am::AClass.new
puts i.inspect
will work:
#<I::Am::AClass:0x00000001d79898>
because the modules are created along the path to AClass (at least this is how I think about it).
If you ever run in that problem and want to save whitespaces (because you will usually indent stuff in a module definition), there are some idioms to use. The one that solves the problem in the most obvious way (again, to me) is the following:
# Predefine modules
module I ; module Am ; end ; end
# Just a shortcut for:
# module I
# module Am
# end
# end
class I::Am::AClass
end
i = I::Am::AClass.new
puts i.inspect
#<I::Am::AClass:0x000000024194a0>
Just found that the nature of your question (it is not about an Admin-Interface, more about Module-Syntax) is also nicely discussed here Ruby (and Rails) nested module syntax . And I would love to see a ruby-bug report/feature-request on this :)
You can also use the administration framework for Ruby on Rails applications like
ActiveAdmin https://github.com/activeadmin/activeadmin
OR
Railsadmin
https://github.com/sferik/rails_admin

How does including a module work inside a controller?

If I do:
rails generate scaffold account/user username
I get a controller that looks like this:
class Account::UsersController < ApplicationController
def index
#account_users = Account::User.all
end
...
end
If I include the Account Module, then it looks like all the database calls don't need to be prefixed with "Account::". I.e.
class Account::UsersController < ApplicationController
include Account
def index
#account_users = User.all #this works because I included the Account Module above
end
...
end
Now if I were to move my
controllers/account/users_controller.rb
file to:
controllers/admin/account/users_controller.rb
The file looks like this (note: I also corrected my routes file after this move):
class Admin::Account::UsersController < ApplicationController
include Account
def index
#account_users = User.all #this call does not work now
end
...
end
But I get an error saying "uninitialized constant Admin::Account::UsersController::User"
It looks like rails is trying to make a database call on the "User" model without the "Account::" module in front of it.
So how does including modules in controllers work? Why does this not work when I move my controller into a different file (and leave the model in the same location from the generated scaffold) but it works with the scaffold generated files? How can I fix this issue?
Resolving the name of a module is done relative to the current module. Try and change it to:
include ::Account
or
include ::Admin::Account
(depending on the module in which your User model is defined)
This will tell ruby to look in the global namespace for the module Account
I guess I didn't realize you can just explicitly require the path to the module you would like to include. I learned this after reading up on modules some more...
So adding an explicit call to "require 'account/user'" just outside the controller class makes it so including the module in the controller works.

Ruby - How to access module's methods?

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.

Resources