I have a simple class I want to include in my Rails app, but I want to still be able to access all the Rails niceness, e.g use the gems, helpers, routes etc etc as normal.
How can I do this? Is it the right thing to do?
You didn't quite specify what the class is for. If it's a model, put it in the models folder. Don't forget that a model doesn't have to be backed by a database. If you have a class that represents something and has business logic, it's a model.
# models/report.rb
class Report
def self.attendance_for(course)
Enrollment.find :all, :include => [:sections], :conditions => ["sections.course_id = ?", course.id])
end
....
end
The lib folder is also appropriate, but depending on how you name the file and the class definition, you may still need to require it from environment.rb or elsewhere.
Another approach is to use a plugin or a gem to distribute your code.
you put it in the lib directory
Related
As my app is getting a bit big now, I'm trying to use namespacing to help organise my models a little better.
I've created a app/models/theme.rb file which will act as a gateway to the rest of the theme related models which will go in a theme subdirectory.
# app/models directory
theme.rb
theme/compiler.rb
theme/instance.rb
theme/revision.rb
Where instance.rb will start something like....
class Theme::Instance < ActiveRecord::Base
end
and theme.rb is simply...
class Theme
def initialize(args)
# some stuff here
end
end
But anytime I create a new model using a generator, it tries to overwrite a new theme.rb as per below.
rails g model theme::revision
#new theme.rb
module Theme
def self.table_name_prefix
'theme_'
end
end
I could just get rid of the module and copy the def self.table_name_prefix method to each class but that doesn't seem very DRY. I would like to use the table prefix as it keeps things more obvious in the DB. Is there a 'correct' Rails way of going about this that I've missed?
I guess the easiest way is to define Theme as a module that all classes extends
The Theme::Instance imply that the Instance class is contained in a Theme module.
As an alternative, you can create a ThemeUtils module that contains all common method that is included in each class, like a plugin in /lib
In my rails application I have a teams model. My route.rb file for teams looks like this:
resources :teams
In my teams_controller.rb file the line team_path(Team.first.id) works however the team_path url helper is not recognized in my model team.rb. I get this error message:
undefined local variable or method `team_path' for # <Class:0x00000101705e98>
from /usr/local/rvm/gems/ruby-1.9.3-p392/gems/activerecord-4.1.1/lib/active_record/dynamic_matchers.rb:26:in `method_missing'
I need to find a way for the model to recognize the team_path path helper.
You should be able to call the url_helpers this way:
Rails.application.routes.url_helpers.team_path(Team.first.id)
Consider solving this as suggested in the Rails API docs for ActionDispatch::Routing::UrlFor:
# This generates, among other things, the method <tt>users_path</tt>. By default,
# this method is accessible from your controllers, views and mailers. If you need
# to access this auto-generated method from other places (such as a model), then
# you can do that by including Rails.application.routes.url_helpers in your class:
#
# class User < ActiveRecord::Base
# include Rails.application.routes.url_helpers
#
# def base_uri
# user_path(self)
# end
# end
#
# User.find(1).base_uri # => "/users/1"
In the case of the Team model from the question, try this:
# app/models/team.rb
class Team < ActiveRecord::Base
include Rails.application.routes.url_helpers
def base_uri
team_path(self)
end
end
Here is an alternative technique which I prefer as it adds fewer methods to the model.
Avoid the include and use url_helpers from the routes object instead:
class Team < ActiveRecord::Base
delegate :url_helpers, to: 'Rails.application.routes'
def base_uri
url_helpers.team_path(self)
end
end
Models are not supposed to be dealing with things like paths, redirects or any of that stuff. Those things are purely constructions of the view or the controller.
The model really should be just that; a model of the thing that you are creating. It should fully describe this thing, allow you to find instances of it, make changes to it, perform validations upon it... But that model wouldn't have any notion of what path should be used for anything, even itself.
A common saying in the Rails world is that if you're finding it difficult to do something (like call a path helper from a model) you are doing it wrongly. Take this to mean that even if something is possible, if it is hard to do in Rails it is likely not the best way to do it.
to add on the previous answer you can use Rails.application.routes.url_helpers just add in route :as like the following example:
get "sessions/destroy/:param_id", as: :logout
so you can use it as following:
Rails.application.routes.url_helpers.logout_path(:param_id => your_value)
Hopefully, this would help
I have a kind of social network website.
I have a logic to create the path for the user, and select an avatar for each user described in UsersHelper methods user_path(user) and user_avatar(user).
Instead I want to have methods like user.path and user.avatar, but I don't want to have that code inside the model file.
I tried extending the User class inside the helper:
module UsersHelper
class User
def avatar
...
end
end
end
That doesn't work - the method I added aren't recognized (I'm guessing Rails dynamically generates the ActiveRecord class on demand so my methods don't apply?)
I'd really appreciate ideas
First, there's a reason helpers are separated from models. According to the MVC pattern your model shouldn't know anything about your helpers, and not vice versa either (the latter is usually ignored in Rails, though).
But, if you want to do this, you need to do class ::User in model UsersHelper, or the following:
module UsersHelper
end
class User
end
The reason for this is that Ruby namespaces classes. So you defined UsersHelper::User, while your model is called User. Calling the class ::User or defining it outside the module forces it into the root namespace.
However, as I said, this is not the "correct" way to do it. A better way would be how you're already doing it, or maybe using a decorator pattern.
Draper is an awesome little gem that does an excellent job of achieving the functionality you're looking for (adding view / presentation specific code while still making it "feel" like the model you're working with). We've removed almost all of our model-specific helpers after starting to use draper.
Basically, it works by defining decorators, which work like a wrapper around your model. A decorator looks and feels like the model it's decorating, but can have additional functionality defined on top of it. In addition, you can restrict access to certain fields and lock stuff down if you like.
For your example, adding a decorator would be as simple as:
(in app/decorators/user_decorator.rb)
class UserDecorator < ApplicationDecorator
decorates :user
def avatar
# your implementation
end
(in your controller)
def index
respond_with UserDecorator.decorate(User.all)
end
(in your views)
<div class='avatar'><%= user.avatar %></div>
<div class='name'><%= user.name %></div>
Helpers are intended to use with the views, not with the models.
If you wish to have something like user.avatar, you have to add it to your model.
If you want to stick it in the helpers, then in the UsersHelper add a user_avatar method.
My project name is clog, so I named my models and controllers like this: Clog::User Clog::Userscontroller.
Is this naming convention mandatory?
No, in a conventional Rails project, that's not necessary. Just name your models and controllers the usual way, like eg User or UsersController.
The other thing is that, when your project grows in size, you may need to organize your models into submodules. One approach to do so is extending your models with app concerns, as show eg here or here.
As for organizing controllers, one approach is to create a module in the lib directory, which you then include in your ApplicationController, like so:
In lib/authentication.rb:
module Authentication
def self.included(base)
base.send :before_filter, :login_required
base.send :helper_method, :current_user, :logged_in?
end
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token]) if cookies[:remember_token].present?
end
#...
end
In app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
include Authentication
#...
end
For this to work, you need to add
config.autoload_paths << "#{config.root}/lib"
to your config/application.rb file
However, if you plan to build your Rails project as a Rails Engine, you may want to follow some naming convention. A good example of a Rails Engine is forem.
Yes, following the naming convention helps a great deal because not only does rails use it to generate other names, but other gems as well.
Specific to your question, you may be asking if you need to name the controller as UserController given that your model is called User. That is not necessary at all, and you may call it anything else if it better fits your purpose.
In this case, you will probably want to create a few controllers like so:
My::AccountController # for e.g.. /my/account
Admin::UsersController # for e.g. /admin/users/1
For a user, you refer to your own user record, as 'your account' so this makes more sense. However, the administrator's perspective would be to manage user records. You may also name a controller one thing and serve it under a different route. In your routes file, you may do this:
namespace :admin do
resources :users, :path => "user-accounts"
end
To reiterate, your model name need not match up to the controller name. They are only named similarly by association: UserController is understood to handle User records.
So, I have a database of people on an external system, and I want to set up the code to easily create people records internal to our sysem based on the external system. The field names, of course, are not the same, so I've written some code which maps from one table to the next.
class PeopleController < ApplicationController
...
def new
#person = Person.new
if params[:external_id] then
initialize_from_external_database params[:external_id]
end
end
private
def initialize_form_external_database(external_id)
external = External::Person.find(external_id)
if external.nil?
...
else
#person.name_last = exteral.last_name
#person.name_first = external.first_name
#...
#person.valid?
end
end
end
Okay, so the stuff in the "else" statement I can write as a loop, which would use a hash something like:
FieldMappings = {
:name_last => :last_name,
:name_first => :first_name,
:calculated_field => lambda {|external_person| ... },
...
}
But where would you put this hash? Is it natural to put it in the External::Person class because the only reason we access those records is to do this initialization? Or would it go in the controller? Or a helper?
Added: Using Rails 2.3.5.
I'd put this code in the External::Person to avoid Person even having to know it exists. Use a 'to_person' method (or maybe 'to_internal_person') on External::Person. Keep the Hash in External::Person and use it to perform the generation. Either way as JacobM says, you want this code in your model, not controller.
class PeopleController < ApplicationController
def new
if external = External::Person.find_by_id params[:external_id]
#person = external.to_person
else
#person = Person.new
end
end
end
If you're in Rails 3.x (maybe also in 2.x, I'm not sure), you can put miscellaneous classes and modules in your /extras folder which is included in the autoloader path. This is where I always put things of this nature, but I' not aware of any Rails convention for this sort of thing.
First of all, I would do that work in your (internal) Person model -- give it a class method like create_person_from_external_person that takes the external person and does the assignments.
Given that, I think it would be OK to include the hash within that Person model, or somewhere else, as Josh suggests. What would be particularly cool would be to write a generic create_person_from_external_person method that would ask the external person for a hash and then do the mapping based on that hash; that approach could support more than one type of external person. But that may be overkill if you know this is the only type you have to deal with.
I wouldn't put it in the controller, but, again, I wouldn't do that work in the controller either.
You can put it on a module on the lib directory so you don't mess any of your classes that will be full of awesome code that will probably last many years. Another good reason is you can then include/require your mapping module everywhere you need it (maybe in your tests).
module UserMapping
FIELDS = { :last_name => :name_last, .... }
end
If you drop the module on the lib and you use rails 3 you should put this on your config/application.rb file:
config.autoload_paths += %W(#{config.root}/lib)
On Rails::VERSION::MAJOR < 3 the lib directory is automatically added to the autoload_path