I don't think Rails calls it an interceptor, but I don't know what to search for.
In Java/Spring, you can create Interceptors that are called before and after any controller action in the MVC framework. This makes it easy to add data to the model for every request, so you don't have to keep adding it to each of your 50 controllers.
I have to take a wild guess and say Rails has something to do achieve the same effect... but I have no idea what it is called.
Care to share? :)
What you are looking for is called Filters.
ActionController::Filters::ClassMethods
Rails Guide
Related
this is kind of a newbie question, sorry but...if a model has no controllers, how could i perform a
flash[:notice]?
for example in
https://github.com/tute/merit
i noticed that it just has models and no controllers. i know that it would be wise to put the flash message inside a controller as opposed to the methods that get executed in the models, but.... how can i do this if there is no controller?
i could try to create one but then doesn't each action in the controller correspond to a page in the view?
certain methods are being executed in the models and i want to be able to add a flash[:notice] whenever they get executed but...how would i do this?
thank you
You would still set flash messages in a controller (or anywhere else the session can be accessed, technically). Merit is just an add-on for Rails, not a replacement. You would still use controllers in your application to handle requests from the user.
I'm working on a Rails app that uses the Shopify API via the Shopify_app Gem. To make a long story short in order to make calls to the Shopify API the following code must appear in the controller of the calling class.
around_filter :shopify_session, :except => 'welcome'
def welcome
current_host = "#{request.host}#{':' + request.port.to_s if request.port != 80}"
#callback_url = "http://#{current_host}/login/finalize"
end
Now, since I have multiple controllers in my app it hardly seems appropriate to place the above code in each. I tried to remove the code from each controller and place it solely in the application controller but that didn't work out. Any guidance as to why I might be experiencing these problems would be greatly appreciated. As a bonus if you could tell me how to get my controller tests in RSpec working without commenting out the above code in the controllers I would be especially grateful.
Thanks for your time
It makes sense for you to define the around filter in each controller as you're defining which methods of that controller to apply the filter to. The Application Controller knows nothing of the classes below it, so putting the definition there makes no sense.
I recommend setting up FakeWeb or something similar to fake out any external calls made by the API using testing. To avoid the shopify_session method from redirecting you to the login screen, make sure to set session[:shopify] to an instance of ShopifyAPI::Session in your test setup.
I've read about HMVC (Hierarchic Model View Controller) and it's flexible structure.
Have a look at this picture:
http://techportal.inviqa.com/wp-content/uploads/2010/02/MVC-HMVC.png
I wonder if the Rails 3 plugins are the answer to HMVC in Rails 3?
Based on the comments to Toby's answer it seems that you would like to be able to have MVC apps used as a component within a new app. Rails Engines (See http://rails-engines.org) provides this functionality. You simply install the engines gem and place apps in vendor/plugins and its modles/views/controller are all accessible.
This does not really conform to HMVC where the controllers in the new app delegate to other controllers. But like Toby I do not see the advantage of that.
What is nice about the Engines approach is that you can over ride any of models in the plugin by just adding a version of the model to the new apps app/model folder (same applies for views and controllers)
I have overidden app/views/layouts to give my Authentication app/plugin the same look and feel as the application it is included in.
For Rails 3 Railtie takes the place of engines and is officially supported (and actually used - Action Mailer is a Railtie plugin. I have not used it myself yet though.
Check it out at http://edgeapi.rubyonrails.org/classes/Rails/Railtie.html
A nice write up on it is also here http://www.igvita.com/2010/08/04/rails-3-internals-railtie-creating-plugins/
Rails has had plugins for a long time.
I doubt there is a technical reason why a controller couldn't dispatch to another controller, passing the request object along a chain. I just don't know what you gain by doing so - the diagram looks like spaghetti.
To me it's a misuse of MVC. I would suggest it is much simpler and more maintainable to push logic into lower-level models and classes and create a single controller that fronts the this logic, rather than creating a chain of controllers.
In the Rails 3 blog post, DHH mentioned the Cells project. I haven't used it but I am going to check it out.
The cart example shows well how that kind of functionality might clean up your application code. Code which retrieves data should be placed somewhere in controller. In every action or in a before filter. The Cell seems to be much better solution.
Please look at this rubyonrails-talk post: https://groups.google.com/forum/#!topic/rubyonrails-talk/0c4TT7UOGCw
I have a Rails application with several models-views-controllers which have some similar characteristics, for example 5 different models can be commented on, voted on or tagged, I am also heavily using external plugins.
At the moment I introduced comments, votes, tags, etc. only to a single model (and its view and controller). However, now that I am happy with the results, I want to cut out this common functionality from the particular MVC of one model and allow access to it from all other models.
Some questions before I start doing this (and maybe some general advice will also be great):
1 - How should I go about it? I was thinking creating a module in "lib" directory (is it the same as mixin class?) and then moving reusable view code to common partials. What about the controller code?
2 - As I was just learning Ruby on Rails during the coding of the first model, I went with a probably incorrect way of adding a bunch of methods to the controller. I have a method that adds a comment (addcomment), adds a vote (addvote), etc. All these methods require non-standard (non-RESTful) routing via :collection. From what I understand, the correct way would be to move comments controller functionality to its own controller and access via standard RESTful routes. Is this what I should be doing?
3 - Many plugins (eg. act_as_commentable) do not explicitly require loading a Module, just a line "act_as_commentable" somewhere in the Model. Can I use something like this for my common functionality? How does it work?
A simple way is to split the code into modules and use mixin.
A better way is to write your own plugins for your common code.. like act_as_commentable
you can learn about it here: http://guides.rubyonrails.org/plugins.html
The correct way is to do a comments controller, and have it nested to your models, giving a restful routes like this: /mymodelname/1/comments.
An easy way to make such controllers is by using inherited_resources plugin.
scroll down to the "Polymorphic belongs to" section- there is a comments controller example
For repeated model code, put it in a module in the lib directory.
For controller code, put your duplicate code in ApplicationController.
For your view code, use partials.
Sending an email is usually called after an action on a model, but the email itself is a view operation. I'm looking for how you think about what question(s) to ask yourself to determine where to put the action mailer method call.
I've seen/used them:
In a model method - bad coupling of related but seperate concerns?
In a callback in the model (such as after_save) - best separation as far as I can tell with my current level of knowledge.
In the controller action - just feels wrong, but are there situations were this would be the smartest way to structure the code?
If I want to know how to program I need to think like a programmer, so learning how you go about thinking through particular programming solutions is worth months of coding on my own in isolation. Thank you!
Late answer, but I want to rationalize on the subject:
Usually, in a web app, you want to send emails either as a direct reaction to a client. Or as a background task, in case we're talking about a newsletter/notification mail sort of thing.
The model is basically a data storage mapper. Its logic should encapsulate data-handling/communication with data storage handling. Therefore, inserting logic which does not relate to it is a bit tricky, and in most cases wrong. Let us take the example: User registers an account and should receive a confirmation email. In this case one could say, the confirmation email is a direct effect of the creation of a new account. Now, instead of doing it in the web app, try to create a user in the console. Sounds wrong to trigger a callback in that case, right? So, callback option scratched. Should we still write the method in the model? Well, if it's a direct effect of a user action/input, then it should stay in that workflow. I would write it in the controller after the user was successfully created. Directly. Replicating this logic in the model to be called in the controller anyways adds unnecessary modularity, and dependency of an Active Record model from Action Mailer. Try to consider sharing the model over many apps, in which some of them don't want Action Mailer for it. For the stated reasons, I'm of the opinion that the mailer calls should be where they make sense, and usually the model is not that place. Try to give me examples where it does make.
Well, depends.
I've used all of those options and your point about 'why should I put this where?' is good.
If it's something I want to happen every time a model is updated in a certain way, then I put it in the model. Maybe even in a callback in the model.
Sometimes you're just firing off a report; there's no updating of anything. In that case, I've normally got a resource with an index action that sends the report.
If the mailer isn't really related to the model that's being changed, I could see putting it in a callback. I don't do that very often. I'd be more likely to still encapsulate it in the model. I've done it, just not very often.
I'm aware it's been a while but best practices never die, right? :)
Email is by definition asynchronous communication (except for confirmation email, but even this one it should be a best practice to leave a delay before having to confirm).
Hence in my opinion, the most logical way to send it is :
in a background action (using Sidekiq or delayed_job)
in a callback method : "hey this action is successfully done, maybe we can tell the world now?"
Problem in Rails is that it is not too many callbacks (as in JS for instance): I personnaly find it dirty to have code like:
after_save :callback
def callback
if test_that_is_true_once_in_the_objects_life
Mailer.send_email()
end
end
So, if you really want to think like a programmer, the idea would be to set up some custom callback system in your app.
Eg.
def run_with_callback(action, callback_name)
if send(action)
delay.send(callback_name)
end
end
Or even creating an event system in your app would be a decent solution.
But in the end those solutions are pretty expensive in time so people end-up writing it inline after the action
def activate
[...]
user.save
Mailer.send_mail
respond_to
[...]
end
which is the closest fashion to callback in synchronous programming and results having Mailers call everywhere (in Model and in Controller).
There's several reasons why controllers are a good place for the mailers:
Emails that have nothing to do with a model.
If your emails depend on several models that dont know about each other.
Extracting models to an API should not mean reimplementing mailers.
Mailer content determined by request variables that you dont want to pass to the model.
If your business model requires a lot of diferent emails, model callbacks can stack.
If the email does not depend on the result of model computations.