I am building a controller concern, and inside it I need a reference to the current controller's related model. So, if I have something like:
class UsersController < ApplicationController
include Concern
end
module Concern
extend ActiveSupport::Concern
def bla
self.model # ??
end
end
I would like in bla to get a reference of the current model, so that when I include Concern in UserController, I get a User reference.
Is this possible in Rails?
You can do this, but only if you have followed Convention over Configuration for naming the controller and model
controller_name.classify.constantize
You can access use controller_name to get the name of controller and then use classify to get class name.
In short:
controller_name.classify
You can infer it from the name of the controller, but it's not 100% reliable, and it will only make sense for RESTful controllers.
This means that if you have an ArticlesController with the default RESTful routes (index, show, new, ecc), it is safe to assume that the related model will be Article. Likewise, you can assume that a RESTful UsersController will be about the User model.
This of course doesn't make sense for non RESTful controller. You could have a GraphPollingController, for example, that does not rely on a specific model.
Inside your controller action call like that
self.class.name.sub("Controller", "").singularize.constantize
Add following method to your concern:
def resource
resource_name = self
.class
.name
.underscore
.gsub(/_controller$/, '')
.singularize
self.instance_variable_get("##{resource_name}")
end
Now you can retrieve your resource through instance var (i.e. #user).
Related
I have a Rails app with a namespaced API and regular controllers that match.
Both sets of controllers of course use the same models. E.g., API::CouponController and CouponController both use the Coupon model.
We are building features into the web side of things first and need to have some conditional logic in the model to tell whether the request is coming from the API or from the regular controller, so we can bifurcate the model logic. How can I do this?
Or is there a way to use a different model for each pair of controllers, but point to the same table?
Try This, I have listed a example it may be helpful
app/controllers/api/coupon_controller.rb
class Api::CouponController < Api::ApiController
def index
Coupon.test_method(params)
end
end
app/controllers/coupon_controller.rb
class CouponController < ApplicationController
def index
Coupon.test_method(params)
end
end
app/models/coupon.rb
class Coupon
def self.test_method(params)
return "You can check your controller and action using params"
end
end
Check The controller and action in test_method
params[:controller]
params[:action]
I'm doing a bit of metaprogramming in Ruby. I'm writing a library to meta-define some methods for me, specifically in the controller (automate some find_by methods that I have to write for my applications).
Currently I generate these methods by having to pass the name of the model for a particular controller into my meta-programming method. Is there a method in a controller that is tied to an ActiveRecord model.
So, here is a poor example
module AwesomeGem
module ClassMethods
def write_some_methods_for(model)
raise "Class #{model.class} does not inherit ActiveRecord::Base" unless model < ActiveRecord::Base
define_method "money_remaining" do |id=nil|
moolah = id ? model.find(id).money : model.find(params[:id]).money
render text: moolah
end
define_method "money_remaining_poller" do |id=nil|
obj = id ? model.find(id) : model.find(params[:id])
# composes some ajax
render js: moneyjs
moneyjs
end
end
end
end
So, to use this method, I plan to
GamblerController < ApplicationController
write_some_methods_for Gambler
end
Again, how could I make it so I don't have to pass the Gambler class to my method? Is there some sort of method or attribute that I could just call the model directly? eg. self.send(:model)
A simple question with a complex explanation.
Controllers are not tied to a particular model by default. You can have a controller playing with several different models, or even a controller using no model at all.
If you still want your code to work automatically in "classic" cases, you could look at the controller's name, and look for a model with the same name (following rails naming conventions).
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 trying to access an instance variable which is set in the controller in the model. The controller is the products controller and the model is the products model. The instance variable is a instance of another model called account.
The instance variable is #current_account
When I run the code nothing happens, I do not get an error. Does anyone know where I can find something read about access instance variables set in the controller from the model?
Thanks
Eef
You shouldn't generally try to access the controller from the model for high-minded issues I won't go into.
I solved a similar problem like so:
class Account < ActiveRecord::Base
cattr_accessor :current
end
class ApplicationController < ActionController::Base
before_filter :set_current_account
def set_current_account
# set #current_account from session data here
Account.current = #current_account
end
end
Then just access the current account with Account.current
DISCLAIMER: The following code breaks MVC conventions, that said...
Using class attributes can probably lead to thread safety issues. I would use Thread.current + around_filter to store controller related data at thread level, and ensure it gets cleared
just before the request finishes:
class ApplicationController < ActionController::Base
around_filter :wrap_with_hack
def wrap_with_hack
# We could do this (greener solution):
# http://coderrr.wordpress.com/2008/04/10/lets-stop-polluting-the-threadcurrent-hash/
# ... but for simplicity sake:
Thread.current[:controller] = self
begin
yield
ensure
# Prevent cross request access if thread is reused later
Thread.current[:controller] = nil
end
end
end
Now the current controller instance will be avaliable globaly during the request processing through Thread.current[:controller]
If you need to access a controller variable from a model it generally means your design is wrong because a controller serves as bridge between view and model (at least in Rails), controller gets info from models, models shouldn't know anything about controllers, but if you want to do it anyway you can do it just as jeem said, but I'd rather do:
class << self
attr_accessor :current
end
instead of cattr_accessor :current
you can see why here => cattr_accessor doesn't work as it should
I can't comment directly so I'll post here: the accepted answer does not seem to be right. As #vise notes, class variables are shared across requests. So unless there's just one current account for the entire app, this won't behave as expected.
For more, see the accepted answer by #molf here: Is Rails shared-nothing or can separate requests access the same runtime variables?
I'm not sure if I understand the question exactly, but I'll take a stab.
I think if you need to access a controller instance variable from the model then you either need to make it an attribute in the model, or move your logic to the other class controller, not model.
In models and controllers, we often use Rails macros like before_validation, skip_before_filter on top of the class definition.
How is this implemented? How do I add custom ones?
Thanks!
They're just standard Ruby functions. Ruby's flexible approach to syntax makes it look better than it is. You can create your own simply by writing your method as a normal Ruby function and doing one of the following:
putting it somewhere that's accessible by your controllers such as application.rb
putting it in a file and requiring it in.
mixing the code into a class via the Ruby include keyword.
That last option is great for model classes and the first option is really only for controllers.
An Example
An example of the first approach is shown below. In this example we add code into the ApplicationController class (in application.rb) and use it in the other controllers.
class BusinessEntitiesController < ApplicationController
nested_within :Glossary
private
# Standard controller code here ....
The nested_within provides helper functions and variables to help identify the id of the "parent" resource. In effect it parses the URL on the fly and is accessible by every one of our controllers. For example when a request comes into the controller, it is automatically parsed and the class attribute #parent_resource is set to the result of a Rails find. A side effect is that a "Not Found" response is sent back if the parent resource doesn't exist. That saves us from typing boiler plate code in every nested resource.
That all sounds pretty clever but it is just a standard Ruby function at heart ...
def self.nested_within(resource)
#
# Add a filter to the about-to-be-created method find_parent_id
#
before_filter :find_parent_id
#
# Work out what the names of things
#
resource_name = "#{resource.to_s.tableize.singularize}"
resource_id = "#{resource_name}_id"
resource_path = "#{resource.to_s.tableize}_path"
#
# Get a reference to the find method in the model layer
#
finder = instance_eval("#{resource}.method :find_#{resource_name}")
#
# Create a new method which gets executed by the before_filter above
#
define_method(:find_parent_id) do
#parent_resource = finder.call(params[resource_id])
head :status => :not_found, :location => resource_path
unless #parent_resource
end
end
The nested_within function is defined in ApplicationController (controllers/application.rb) and therefore gets pulled in automatically.
Note that nested_within gets executed inside the body of the controller class. This adds the method find_parent_id to the controller.
Summary
A combination of Ruby's flexible syntax and Rail's convention-over-configuration makes this all look more powerful (or weirder) than it actually is.
Next time you find a cool method, just stick a breakpoint in front of it and trace through it. Ahh Open Source!
Let me know if I can help further or if you want some pointers on how that nested_within code works.
Chris
Chris's answer is right. But here's where you want to throw your code to write your own:
The easiest way to add Controller methods like that is to define it in ApplicationController:
class ApplicationController < ActionController::Base
...
def self.acts_as_awesome
do_awesome_things
end
end
Then you can access it from individual controllers like so:
class AwesomeController < ApplicationController
acts_as_awesome
end
For models, you want to reopen ActiveRecord::Base:
module ActiveRecord
class Base
def self.acts_as_super_awesome
do_more_awesome_stuff
end
end
end
I personally would put that in a file in config/initializers so that it gets loaded once, and so that I know where to look for it always.
Then you can access it in models like so:
class MySuperAwesomeModel < ActiveRecord::Base
acts_as_super_awesome
end