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
Related
I have a method current_org that's defined simply as:
def current_org
Organization.find_by(subdomain: Apartment::Tenant.current)
end
It's always the same, whether it's in a view, controller, a model, or even a service. Since the current tenant is derived from the database connection, I shouldn't have to worry about it being properly scoped. And I find myself using it everywhere.
What's the best way to define a global method in Rails so I can just call current_org from anywhere? Currently my best solution is defining a module in /lib and calling it with CustomHelperMethods.current_org. But I'm looking for something a little cleaner.
I'd put it as a class method in an Organiation model or create a special service/class that fetches it.
class Organization < ApplicationRecord
def self.current_org
find_by(subdomain: Apartment::Tenant.current)
end
end
or
# e.g. in app/services/
class CurrentOrganization
def self.current_org
Organization.find_by(subdomain: Apartment::Tenant.current)
end
end
I know this is 1001st question about global objects, but I think my situation is slightly different.
I'm working on ecommerce solution, which provides few different shops within a single rails application.
There is a class Shop which provides shop-specific logic and options. For example:
#shop.tax should be accessible in models. Tax can differ depend on shop. eg 9%, 18%.
#shop.name and #shop.layout should be accessible in controllers and views.
#shop.email.general for mailers.
I need to be able to create an instance of Shop in application controller and somehow pass it to the all application parts.
# controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_filter :set_shop
protected
def set_shop
requested_shop = if request.domain.match(/^.*shop1\.com$/)
:shop_1
elsif request.domain.match(/^.*shop2\.com$/)
:shop_2
end
#shop = Shop.new(requested_shop)
end
end
I know that request-based logic should not be used in models, but I really need shop options there. In tests I could mock this "global" object like that Shop.new(:test_shop) in spec_helper.
Is global variable my only choice? I've never used them.
I tried to use Settingslogic gem, but it defines attr_accessors for shop-specific options, and they persist between requests, which is not what I need.
One way of doing this would be something like
class Shop
def self.current=(shop)
Thread.current[:current_shop] = shop
end
def self.current
Thread.current[:current_shop]
end
end
Which allows you to maintain a separate current shop for each request.
The alternative is to pass the current shop around. It may seem tedious at first but can ultimately be simpler to reason about than global or pseudo global behaviour
This is going to sound strange, but hear me out...I need to be able to make the equivalent of a POST request to one of my other controllers. The SimpleController is basically a simplified version of a more verbose controller. How can I do this appropriately?
class VerboseController < ApplicationController
def create
# lots of required params
end
end
class SimpleController < ApplicationController
def create
# prepare the params required for VerboseController.create
# now call the VerboseController.create with the new params
end
end
Maybe I am over-thinking this, but I don't know how to do this.
Inter-controller communication in a Rails app (or any web app following the same model-adapter-view pattern for that matter) is something you should actively avoid. When you are tempted to do so consider it a sign that you are fighting the patterns and framework your app is built on and that you are relying on logic has been implemented at the wrong layer of your application.
As #ismaelga suggested in a comment; both controllers should invoke some common component to handle this shared behavior and keep your controllers "skinny". In Rails that's often a method on a model object, especially for the sort of creation behavior you seem to be worried about in this case.
You shouldn't be doing this. Are you creating a model? Then having two class methods on the model would be much better. It also separates the code much better. Then you can use the methods not only in controllers but also background jobs (etc.) in the future.
For example if you're creating a Person:
class VerboseController < ApplicationController
def create
Person.verbose_create(params)
end
end
class SimpleController < ApplicationController
def create
Person.simple_create(params)
end
end
Then in the Person-model you could go like this:
class Person
def self.verbose_create(options)
# ... do the creating stuff here
end
def self.simple_create(options)
# Prepare the options as you were trying to do in the controller...
prepared_options = options.merge(some: "option")
# ... and pass them to the verbose_create method
verbose_create(prepared_options)
end
end
I hope this can help a little. :-)
I have an app that has users whose profiles are accessible via site.com/username. When choosing a username, I make an AJAX call to a method in my UsersController to make sure the username is available (and check on the back end as well when submitted). I now want to add groups that will also be accessible through site.com/groupname. Since group and user names cannot collide, whatever controller method that responds to the AJAX call will need to check both so the check_username_available and check_groupname_available methods will do the exact same thing. What's the best practice / Rails way to handle this since I don't want to replicate code in both UsersController and GroupsController?
Having a method for each controller seems a bit redundant, even if the functionality is pulled out to a helper, since there will still be two routes that do the same thing. Having a separate controller solves the problem too but not sure this is good Rails practice.
code that is reused can be shared via a module
class UsersController < ActionController::Base
include NameUniqueness
end
class GroupsController < ActionController::Base
include NameUniqueness
end
module NameUniqueness
protected
def check_name
# implementation here
end
end
both controllers will now have access the check_name instance method.
DanPickett's answer is great.
Another choice is to make a class method in the user model and just call it from each controller. Since this name checking seems like a job for the model, that's what I would do.
class User
def self.check(stuff) ...
I am in the middle of migrating my application from using subdirectories for userspace to subdomains (ie. domain.com/~user to user.domain.com). I've got a method in my user class currently to get the "home" URL for each user:
class User
def home_url
"~#{self.username}"
# How I'd like to do it for subdomains:
#"http://#{self.username}.#{SubdomainFu.host_without_subdomain(request.host)}"
end
end
I'd like to update this for subdomains, but without hardcoding the domain into the method. As you can see, I am using the subdomain-fu plugin, which provides some methods that I could use to do this, except that they need access to request, which is not available to the model.
I know it's considered bad form to make request available in a model, so I'd like to avoid doing that, but I'm not sure if there's a good way to do this. I could pass the domain along every time the model is initialized, I guess, but I don't think this is a good solution, because I'd have to remember to do so every time a class is initialized, which happens often.
The model shouldn't know about the request, you're right. I would do something like this:
# app/models/user.rb
class User
def home_url(domain)
"http://#{username}.#{domain}"
end
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# ...
def domain
SubdomainFu.host_without_subdomain(request.host)
end
# Make domain available to all views too
helper_method :domain
end
# where you need it (controller or view)
user.home_url(domain)
If there is such a thing as a canonical user home URL, I would make a configurable default domain (e.g. YourApp.domain) that you can use if you call User#home_url without arguments. This allows you to construct a home URL in places where, conceptually, the "current domain" does not exist.
While molf's answer is good, it did not solve my specific problem as there were some instances where other models needed to call User#home_url, and so there would be a lot of methods I'd have to update in order to pass along the domain.
Instead, I took inspiration from his last paragraph and added a base_domain variable to my app's config class, which is the set in a before_filter in ApplicationController:
module App
class << self
attr_accessor :base_domain
end
end
class ApplicationController < ActionController::Base
before_filter :set_base_domain
def set_base_domain
App.base_domain = SubdomainFu.host_without_subdomain(request.host)
end
end
And thus, when I need to get the domain in a model, I can just use App.base_domain.