Where should I store session code in a Rails app? - ruby-on-rails

In a Rails app I'm creating, I have some code in my controller that I'm wondering whether it's in the proper place. The code is fairly insignificant, it stores ids in an array to show 'recently viewed' pages. It's about 3 lines of code, but I'm thinking to the future, what if this feature expands? I don't want my controller to be bloated.
I could make a module, but in that case where should I store the file?
Is the controller the right place to be doing the session management?
Any suggestions on my code organization?
Thanks

If it is specific to the controller, keep it in the controller.
If it applies to all controllers, it goes in ApplicationController.
If it is shared by some controllers and not others, then inherit from a controller that inherits from ApplicationController, or use include/extend or make a module that extends ActiveSupport::Concern (which is what Rails uses internally fairly commonly).
And it's best to keep everything in app/controllers or some subdirectory, sub-subdirectory, etc. Rails autoloading depends on the path to match up with the module namespace, so A::B::C belongs in app/controllers/a/b/c.rb. Don't make it deep like Java, etc. Just have the number of directories/modules you need to keep it organized.
Note: though controllers aren't as problematic to have in their own modules, in my experience your models should stay in the root, like app/models, or you'll have problems.
I'd also avoid storing too much in session if you can help it. Store in the DB (or long-life cookies, if it is browser-environment specific) instead. For example- if someone logs out and they were looking at one record, they might want to log back in later and have a list containing the link to that record.
BTW- you weren't asking and probably already have the code for storing recently visited pages in session, but here are similar questions/answers:
https://stackoverflow.com/a/10813602/178651
http://www.rorexperts.com/how-to-store-visited-pages-in-session-object-in-rails-t941.html

Related

Rails 3.1 Custom Controllers and Views w/ Default

Background:
I'm building an app that lets users create their own website (e.g., mywebsite.alexlod.com)
Each website owner is affiliated with me, so I'll trust them to write their own rails code
Each website should have default controllers and views, except when one of these owners creates their own
Here's how I envision controllers working:
I'm thinking that what's in app/controllers/ will be the default, but when a file is specified in a <subdomain> top-level directory, that file will take precedence over the default.
Let me give an example. Let's say one of my website owners (foo.alexlod.com) wants to tweak app/controllers/photos_controller.rb. They should be able to create foo/controllers/photos_controller.rb whereby their controller is used instead of the default. I'm thinking the correct approach here has something to do with routes and the load path, but I'm new to Rails and Ruby and could use some guidance.
As for views, I would like them to work much the same. When a view or partial is defined in <subdomain>/views/, that view is used instead of the default located in app/views/.
I realize my plan here violates the default rails directory structure. But this approach has gotta be more simple than the alternative - case statements in each controller action. Unless there's an even better alternative?
I believe engines may be the way to go for what you want. That link is a little old but still should be applicable with Rails 3.1. Each subdomain's controllers and views can be placed in their respective engine folder.
Now, you just need to figure out which engine to load per request. That's the part I think may be the deterrent to your idea -- you don't want to be loading and unloading code all the time in production.
You may want to opt for a templating language like Liquid, in that case. That, however, gives a lot less control to the users.
I may have a partial answer for you as it sounds like you want to do something very similar to what I am doing for the mobile version of my site. After I identify that a user is mobile I add a mobile directory to the path to override any views that I have optimized for mobile. If the view doesn't exist in the mobile directory it defaults to the default view.
Here is what I did for views:
in app/controllers/application_controller.rb
before_filter :prepend_view_path_if_subdomain
def prepend_view_path_if_subdomain
unless pSubdomains.blank?
subdomain = request.subdomain.first
#This will add the subdomain view directory to the view path before the default
#Rails view directory and any views here will be picked up and rendered.
prepend_view_path 'app/' + subdomain + '/views'
end
end
Doing the same thing for controllers, however, is a little trickier due to routing. There is no prepend_controller_path method that is equivalent to prepend_view_path. Honestly I'm not sure how to approach this, you could use your case statement approach or possibly dynamically forward on the request to the subdomain controller (it if exists). I think it might be possible to add a before_filter to your controllers that evaluates each request much like I show above with views and then determines which controller should be used.
I also stumbled across this question: Rails 3.1 load controller from different path based on subdomain, not sure if it helps you or not.

Ruby on Rails - Best practice / method for loading side column content/blocks

I am developing a community / news article website where there is a side column with different "blocks" on nearly all pages. In these blocks is "Recent Articles (showing five most recent articles)," "Recent Blogs," "Recent Comments," you get the drift.
When I started out building the application, I wasn't real sure where to put the controller code (say, to call #recent_articles = Article.where...etc). I didn't think it could go into the Articles controller, because it's not always the Articles controller being called. So I thought it would work best in the application controller, as most content on the site would be calling this. I put "#recent_content" into the application controller, did a :before_filter to load it.
You might see the flaw in this. As I'm getting better with Rails, I went back to refactor as the site was loading horribly and sure enough, all my logic in the application controller defined by before_filter was being loaded on every action, no matter if it was needed or not. (The site sped up dramatically when I cleaned house on the application controller).
My mistake is realized, but I still need to define the instance variables for #recent_articles, #recent_blogs, etc somewhere, so they load up only when needed. Granted I'll be eventually caching the site content when it goes into production, but I want to be a good Rails programmer here.
So here is my question...exactly how would you handle this situation and where would you put the logic? I can think of two ways, not sure which one is better...
The first way...I took a look at a project from another Rails developer and I noticed he was doing odd things like this by creating files in the /lib folder. For example, defining a method for page meta tags or active menu states. I honestly haven't messed with the /lib folder before, figuring most of my stuff should stay in the /app folder.
The second way...seems to me like helpers might seem the way to go. Maybe I could define a "recent_articles" helper, call my #recent_articles instance variable in there, then render and pass the results to a view file in my shared folder.
Overall, which one of these ways is the better way to go, either from a performance or best-practices viewpoint? Or is there a better way of doing this that I'm unaware of?
Whenever there are many models that can call a particular method, i would probably use a module. I think that is what you are talking about in your first idea, since /lib is where modules are placed.
You can use helpers as well, but it's a good idea to keep logic out of helpers, only in models if possible. Helpers should be just used as a way to present data, they are part of views. If logic is added, then something is wrong :)
Make sure that you do not have logic in your controllers as well. I would be doing the same things in the beginning, but it's really a bad idea. Instead, put everything in your models, or maybe a module if they seem to be used by many other models.
Hope that helps you a bit :)

Helpers in rails

I'm just starting out in Rails and there's a lot I still need to learn so I'm likely to be on Stackoverflow more often than normal asking beginner Rails / Ruby questions.
I'm just trying to figure out how Helpers work in Rails. From what I've seen so far, Helpers are intended to be used with Views and not so much with your Controllers.
However I would like to make a simple function that will validate the user input given in params (check if certain params are defined and optionally check if their value is valid).
Can anyone explain to me what would be the best way of implementing this? (Keeping in mind that I will want to use this in many different controllers so it should be globally available.)
I also noticed that by default Rails does not generate a lib folder in the main application folder. Are developers to place their libs outside the app folder in the main folder, or does Rails use libraries differently?
With regards to your validation issue, it depends on what you are validating.
If the data makes up objects from your problem domain, also known as models, then you should use the built in validators from ActiveModel. This is probably what you should do, but its hard to say without knowing the exact problem. See the Rails Guides on Validations. You can tell if this is the case by asking yourself if the data that needs validation will be stored after you get it. If so, its most definitely a model. An example of this kind of data would be the title and text fields of a blog post being sent to Rails from a browser form.
If the data is something tertiary to your models, or specific to presentation, then you should be fine using helpers. You noticed that helpers are used mostly in the views, and although this is true, theres nothing stopping you from using them in the controllers, you just have to declare that you will use them using the ActiveController#helper method. Inside the ApplicationController class, a lot of devs will put helper :all to just include all the helpers in all the controllers. Once the code has been required once, it doesn't really incur that big a performance hit.
Do note that almost all incoming data can be modeled using a model. A big school of thought in the Rails world subscribes to the Fat Model idea. People say that putting as much code as possible in the model and as little in the controller as possible separates concerns properly and leads to more maintainable code. This suggests that even if you don't think the incoming data is modelable (in the sense that you can create a model to represent it), you should try to make it a model and encapsulate the logic around validating it. However, you may find that making a helper function is faster, and either will work.
Your notion of validating user input is a good one. I get the feeling that as you are new to Rails you are used to doing these things yourself, but that doesn't quite apply here. In the Rails world, a lot of the common stuff like validations is handled by the framework. You don't have to check for presence in the params array, instead you call validates_presence_of on the model and let Rails spit the error out to the user. It makes things easier in the long run if you let the framework do what it is designed to.
With regards to your question about the lib folder, it doesn't really matter. You can put miscellaneous support files and libraries in the lib folder in the root directory and they will be available for use in your application (the files in the app folder). You can also choose to abstract your code into a plugin or a gem and include it that way, which a lot of people opt to do. My suggestion for this would be to read up on the notion of gems and plugins before diving in.
Want you want is probably a custom validator (in Rails3):
http://railscasts.com/episodes/211-validations-in-rails-3
You can either add libs in a lib folder you create, or add them to config/initializers in a file you add. Files in the initializers directory are automatically loaded by Rails.

Where to put reusable methods for access by controllers in rails

I have several methods I call from my controllers that feel like they should be pulled out and put into a reusable class, outside of the controller. Where do people usually put this stuff? I know I can put them into my ApplicationController, but that doesn't seem to be a great solution if I think I can use these methods later in other applications.
Also, I have a bunch of utility methods in my controllers that likely won't be used in other controllers, or in the future at all, but I feel like they just bloat my controller a bit. Do people usually move these out someplace for cleanliness or just end up with a huge controller?
I'm coming from Java and Actionscript where I'd just create new util classes for this stuff.
The lib directory is a place you can put modules/classes which can be mixed in or used by controllers (and anything else, really). This can be a place to put code that doesn't fall into other areas (but be careful about making sure lib doesn't turn into a mess itself). Side comments just to keep in mind:
If you know you have a large amount of related functionality that could, or will, be used in other applications, that might be a plugin.
Its also worth keeping in mind that there's nothing wrong with creating a model that is not an Active Record object. So again, depending on what you have, this might make sense as well.
Create a module file in lib directory:
module ControllerUtil
def foo
end
def bar
end
end
Include the module in the controller:
class UsersController < ApplicationController
include ControllerUtil
end
You can create app/modules directory, and create XYZUtils module in it e.g
module XYZUtils
def abc
end
def efg
end
end
and include the module as and when required in Controllers or models etc.
include XYZUtils
You can create different modules for utility functions related to different Models or entities
I won't prefer /lib directory because that should contain the project related code, not app related, e.g tasks etc.
I would keep all the App related code in /app directory itself
Related to Sahil kalra's above answer from 2014:
Rails now has an app/controllers/concerns directory where you can put modules full of helper methods and easily include or extend them in your controllers. I just copied and pasted all of my logic-intensive methods out of my application_controller and they worked right out of the box.
(You should, of course, still make sure everything works correctly before putting anything into production.)
Controllers should be very minimal - basically ingesting arguments and making some very high level decisions. If you have some helper functions that are doing just those kinds of things but aren't going to be reused, then keeping them in the controller is the right thing to do. Just make sure to mark them as private.
For more commonly shared things, you can either back them into your ApplicationController (if they're used in all controllers) or into a class in your app/models directory. I recommend the models directory over lib because Rails in development mode is much better about spotting changes to those files and reloading them. Changes to files in /lib tend to require restarting the webserver, which slows down your development effort. This is unfortunate, because controller helpers really should not be mixed with models.
In general, though, if you have more than a handful of these helpers, you're probably doing too much in your controllers. Take a hard look at them and see if maybe some of them shouldn't be part of your models instead.

Correct way to optimize repeated Rails code

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.

Resources