How does ActionController::Base in Rails know what class its model is? - ruby-on-rails

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).

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!

In Rails, how can I tell, from within a model, whether the request came via a namespaced API controller or regular controller?

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]

Get model for current controller

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).

Helper methods for models in Rails

Is there a proper place for helper methods for models in Rails? There are helper methods for controllers and views, but I'm not sure where the best place to put model helper methods. Aside from adding a method to ActiveRecord::Base, which I'd prefer not to.
UPDATE: It seems Concerns make a lot of sense. Here's an example of what I want. Certain models can never be deleted, so I add a callback that always throws an exception:
before_destroy :nope
def nope
raise 'Deleting not allowed'
end
With concerns, I could do something like this?
class MyModel < ActiveRecord::Base
include Undeletable
end
module Undeletable
extend ActiveSupport::Concern
included do
before_destroy :nope
end
def nope
raise 'Deleting not allowed'
end
end
Is this the Rails way of doing this?
If you want to use a helper_method my_helper_method inside a model, you can write
ApplicationController.helpers.my_helper_method
If you need a bit more flexibility, for example if you also need to override some methods, you can do this:
class HelperProxy < ActionView::Base
include ApplicationController.master_helper_module
def current_user
#let helpers act like we're a guest
nil
end
def self.instance
#instance ||= new
end
end
and then use with
HelperProxy.instance.my_helper_method
If you have strong nerves, you can also try to include the ApplicationController.master_helper_module directly into your model.
via : makandracards's post.
For your reference: http://railscasts.com/episodes/132-helpers-outside-views
If what you are asking is where to put code that is shared across multiple models in rails 4.2, then the standard answer has to be to use Concerns: How to use concerns in Rails 4
However, there are some good arguments (e.g. this) to just using standard rails module includes, and extends as marek-lipka suggests.
I would strongly recommend NOT using ApplicationController helper methods in a model, as you'll be importing a lot unnecessary baggage along with it. Doing so is usually a bad smell in my opinion, as it means you are not separating the MVC elements, and there is too much interdependency in your app.
If you need to modify a model object by adding a method that is just used within a view, then have a look at decorators. For example https://github.com/drapergem/draper

Wrapping an object with methods from another class

Let's say I have a model called Article:
class Article < ActiveRecord::Base
end
And then I have a class that is intended to add behavior to an article object (a decorator):
class ArticleDecorator
def format_title
end
end
If I wanted to extend behavior of an article object, I could make ArticleDecorator a module and then call article.extend(ArticleDecorator), but I'd prefer something like this:
article = ArticleDecorator.decorate(Article.top_articles.first) # for single object
or
articles = ArticleDecorator.decorate(Article.all) # for collection of objects
How would I go about implementing this decorate method?
What exactly do you want from decorate method? Should it simply add some new methods to passed objects or it should automatically wrap methods of these objects with corresponding format methods? And why do you want ArticleDecorator to be a class and not just a module?
Updated:
Seems like solution from nathanvda is what you need, but I'd suggest a bit cleaner version:
module ArticleDecorator
def format_title
"#{title} [decorated]"
end
def self.decorate(object_or_objects_to_decorate)
object_or_objects_to_decorate.tap do |objects|
Array(objects).each { |obj| obj.extend ArticleDecorator }
end
end
end
It does the same thing, but:
Avoids checking type of the arguments relying on Kernel#Array method.
Calls Object#extend directly (it's a public method so there's no need in invoking it through send).
Object#extend includes only instance methods so we can put them right in ArticleDecorator without wrapping them with another module.
May I propose a solution which is not using Module mixins and thereby granting you more flexibility. For example, using a solution a bit more like the traditional GoF decorator, you can unwrap your Article (you can't remove a mixin if it is applied once) and it even allows you to exchange the wrapped Article for another one in runtime.
Here is my code:
class ArticleDecorator < BasicObject
def self.[](instance_or_array)
if instance_or_array.respond_to?(:to_a)
instance_or_array.map {|instance| new(instance) }
else
new(instance_or_array)
end
end
attr_accessor :wrapped_article
def initialize(wrapped_article)
#wrapped_article = wrapped_article
end
def format_title
#wrapped_article.title.upcase
end
protected
def method_missing(method, *arguments)
#wrapped_article.method(method).call(*arguments)
end
end
You can now extend a single Article by calling
extended_article = ArticleDecorator[article]
or multiple articles by calling
articles = [article_a, article_b]
extended_articles = ArticleDecorator[articles]
You can regain the original Article by calling
extended_article.wrapped_article
Or you can exchange the wrapped Article inside like this
extended_article = ArticleDecorator[article_a]
extended_article.format_title
# => "FIRST"
extended_article.wrapped_article = article_b
extended_article.format_title
# => "SECOND"
Because the ArticleDecorator extends the BasicObject class, which has almost no methods already defined, even things like #class and #object_id stay the same for the wrapped item:
article.object_id
# => 123
extended_article = ArticleDecorator[article]
extended_article.object_id
# => 123
Notice though that BasicObject exists only in Ruby 1.9 and above.
You'd extend the article class instance, call alias_method, and point it at whatever method you want (although it sounds like a module, not a class, at least right now). The new version gets the return value and processes it like normal.
In your case, sounds like you want to match up things like "format_.*" to their respective property getters.
Which part is tripping you up?
module ArticleDecorator
def format_title
"Title: #{title}"
end
end
article = Article.top_articles.first.extend(ArticleDecorator) # for single object
Should work fine.
articles = Article.all.extend(ArticleDecorator)
May also work depending on ActiveRecord support for extending a set of objects.
You may also consider using ActiveSupport::Concern.

Resources