How does including a module work inside a controller? - ruby-on-rails

If I do:
rails generate scaffold account/user username
I get a controller that looks like this:
class Account::UsersController < ApplicationController
def index
#account_users = Account::User.all
end
...
end
If I include the Account Module, then it looks like all the database calls don't need to be prefixed with "Account::". I.e.
class Account::UsersController < ApplicationController
include Account
def index
#account_users = User.all #this works because I included the Account Module above
end
...
end
Now if I were to move my
controllers/account/users_controller.rb
file to:
controllers/admin/account/users_controller.rb
The file looks like this (note: I also corrected my routes file after this move):
class Admin::Account::UsersController < ApplicationController
include Account
def index
#account_users = User.all #this call does not work now
end
...
end
But I get an error saying "uninitialized constant Admin::Account::UsersController::User"
It looks like rails is trying to make a database call on the "User" model without the "Account::" module in front of it.
So how does including modules in controllers work? Why does this not work when I move my controller into a different file (and leave the model in the same location from the generated scaffold) but it works with the scaffold generated files? How can I fix this issue?

Resolving the name of a module is done relative to the current module. Try and change it to:
include ::Account
or
include ::Admin::Account
(depending on the module in which your User model is defined)
This will tell ruby to look in the global namespace for the module Account

I guess I didn't realize you can just explicitly require the path to the module you would like to include. I learned this after reading up on modules some more...
So adding an explicit call to "require 'account/user'" just outside the controller class makes it so including the module in the controller works.

Related

How does a controller class access a model class?

How does this random controller class
class RandomController < ApplicationController
def index
#user = User.all
end
end
access the User class? I've been searching for the connection in the source files, but I can't seem to find a logical explanation.
Rails has 'constant autoloading', so you don't need to add require 'user' to the top of your file.
http://guides.rubyonrails.org/autoloading_and_reloading_constants.html
When Rails encounters a missing constant, it tries to load a file with a filename based on the constant's name. This nearly always works smoothly... C-;

Rails concerns, how to include a concern inside an api controller

I am building a Rails api and currently have this folder structure:
The error_serializer.rb file is a module:
module ErrorSerializer
extend ActiveSupport::Concern
...methods here...
end
Which I can include in any of the api controllers, for example:
class Api::TemplatesController < ApiController
include ErrorSerializer
...
end
But since this errors_serializer module is only relevant to api controllers, I want to move the file to 'api/concerns/error_serializer.rb'.
But that generates the error:
ActionController::RoutingError (uninitialized constant Api::TemplatesController::ErrorSerializer)
I tried changing the name inside the file to:
module Api::ErrorSerialzer
but got the same error.
So what must I change to be able to move that file?
Since rails expects your module naming to follow your file structure, your concern should be named:
module Api::Concerns::ErrorSerializer
Since you're including it in Api::TemplatesController, I would do:
class Api::TemplatesController < ApiController
include Api::Concerns::ErrorSerializer
...
end
To help rails out with the constant lookup.
Thanks to the answer from #jvillian and this blog post, I was able to figure out the 'Rails' way to do this (since actually I will need the concern in all Api controllers, and also my api controller was outside the api namespace). So I'm posting this solution as (I think) it's the preferred way:
I moved the error_serialzier.rb file into api/concerns and change the code to include the Api namespace:
module Api::Concerns::ErrorSerializer
extend ActiveSupport::Concern
...
end
I also moved api_controller.rb file and put it inside the /api folder, and thus into the API module namespace, so now it looks like this:
class Api::ApiController < ActionController::API
before_action :authenticate_api_user!
include DeviseTokenAuth::Concerns::SetUserByToken
include Concerns::ErrorSerializer
respond_to :json
end
This got rid of the uninitialized constant errors.

Rails 4.2 naming conventions for a multi-step wizard

Rails 4.2
I've created a multi-step wizard for a User model i.e. a visitor to the app registers as a User over a 4 step form. Its working in it's current setup.
However, I've had to use require statements to include several of the wizard related ruby files. As a consequence these files are not auto loaded by Rails.
I'd like to refactor the relevant files so that they follow Rails conventions and are able to be auto loaded.
Current Structure - is working
app/wizards/user.rb User wizard model - runs validations on each step etc
module Wizard
module User
STEPS = %w[step1 step2 step3 step4].freeze
# omitted class implementations, not relevant
class Base
end
class Step1 < Base
end
class Step2 < Step1
end
class Step3 < Step2
end
class Step4 < Step3
end
end
end
app/controllers/user_wizards_controller.rb
# I have to require the file above, would like to avoid
require Rails.root.join('app', 'wizards', 'user.rb')
class UserWizardsController < ApplicationController
# I have to specify the template, would like to avoid
def step1
render 'wizards/users/step1'
end
# Notice how I have to refer to module/classes above.
def wizard_user_for_step(step)
raise InvalidStep unless step.in?(::Wizard::User::STEPS)
"Wizard::User::#{step.camelize}".constantize.new(session[:user_attributes])
end
end
app/views/wizards/users/step1.html.erb
app/views/wizards/users/step2.html.erb
Attempted Solution
Based on this statement by xfn
The directories in autoload_paths are considered to be root directories, they do not reflect namespacing. For example, the classes below app/models are not under a Models namespace, any namespace has to go within that directory.
The file app/services/doctor_finder.rb for example does not follow autoloading conventions because of that, since it defines Services::DoctorFinder rather than DoctorFinder. Therefore, that file cannot be autoloaded.
I'm going for...
Models
app/wizards/user/user.rb
app/wizards/user/base.rb
app/wizards/user/step1.rb
app/wizards/user/step2.rb
app/wizards/user/step3.rb
app/wizards/user/step4.rb
However, I'm not getting very far. Any ideas?
If you want to autoload these files move it into e.g services or steps directories like:
app/services/wizards/user/step1
and rails should autoload module:
module Wizards::User
class Step1
end
end
Depend on rails version you will need to add 'services' to autoload path.
Regards views:
render 'wizards/users/step1'
isn't bad and In my opinion could be consider as good practice.(using render method allow you to pass non global variables to view)
If you want to remove this line you should put views for UserWizardsController to user_wizards/step1.html.xxx
or if you want to have view in wizards/users/step1.html.xxx
you should scope your controller in that way:
module Wizards
class UsersController < ApplicationController
end
edn

How to use and where to put code not part of MVC

I wrote ruby code which pulls content from Google API. It works as a standalone example.rb file. I need to add this to my RoR app. What is the standard way to do it? How should I call this code from the controller? Should I add this code in some model file, keep the code in /lib folder, or put the code in /vendor/plugins folder?
Either extract it out into a gem, or you could put it in lib if you wanted.
If you take the second approach, here's an example. Say you have it in a module (Google)
#lib/google.rb
module Google
class Uploader
def initialize
...
end
def foo
...
end
end
...
end
in your controller
require 'google'
class MyController < ApplicationController
def new
uploader = Google::Uploader.new # do whatever here
uploader.foo
end
end
There are many ways to modify / use this module approach, the given code is only one possibility.

Rails: macro style functions

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

Resources