respond_with and namespaces - ruby-on-rails

Tricky issue...
Assume the following models:
class Foo::Bar < ActiveRecord::Base
class Foo::Nut < ActiveRecord::Base
The following route:
namespace :admin do
resources :bars do
resources :nuts do
In the create action for nuts at /admin/bars/100/nuts, I create the model based on post data and would like to respond with:
#respond_with(:admin, #bar, #nut) (where bar and nut had been set up in the action)
I'm presented with this lovely error:
NoMethodError (undefined method `admin_foo_bar_foo_nut_url')
I'd like rails to look for admin_bar_nut_url and not admin_foo_bar_foo_nut_url.
Any ideas if I can get around this? Clearly something up with having my models define in modules...
Would prefer to have to abandon the model namespacing but can if I must.
Thanks so much!

I do not see why you need to go away with name space rather than using respond_to instead of respond_with
respond_to do |format|
format.html { redirect_to(admin_bar_nut_url(#bar, #nut)) }
end

To remove foo from your routes, override model_name in your models like:
class Foo::Bar < ApplicationRecord
def self.model_name
ActiveModel::Name.new(self, Foo)
end
end
class Foo::Nut < ApplicationRecord
def self.model_name
ActiveModel::Name.new(self, Foo)
end
end
This will result in Foo::Nut.model_name.route_key being "nuts" instead of "foo_nuts"
And now respond_with should work as desired.

Related

Route not find - Rails

Rails 3.2
In my controllers/admin/accounts_receivables_contoller.rb, I have:
class Admin::AccountsReceivables < Admin::ApplicationController
def index
...
end
and in one of the views, I have:
= link_to admin_accounts_receivables_path
In my config/routes.rb, I have:
namespace :admin do
resources :accounts_receivables do
collection do
get 'admin_report'
get 'customer_report'
post 'process_invoices'
end
end
end
rake routes, produces:
admin_accounts_receivables GET admin/accounts_receivables(.:format) admin/accounts_receivables#index
However, when I click on the link, I get (in the browser, but no entry in the log file):
uninitialized constant Admin::AccountsReceivablesController
I do not have a corresponding AccountsReceivable model, as I don't need it.
Any ideas?
The class should be named AccountsReceivablesController and you should nest the class explicitly instead of using the scope resolution operator so that it has the correct module nesting:
module Admin
class AccountsReceivablesController < ApplicationController
def index
# ...
end
end
end
When you use the scope resolution operator class Admin::AccountsReceivablesController - the module nesting is resolved to the point of definition which is Main (the global scope) and not Admin. For example:
module Admin
FOO = "this is what we expected"
end
FOO = "but this is what we will actually get"
class Admin::AccountsReceivablesController < Admin::ApplicationController
def index
render plain: FOO
end
end
See The Ruby Style Guide - namespaces.
class Admin::AccountsReceivables < Admin::ApplicationController
should be...
class Admin::AccountsReceivablesController < Admin::ApplicationController

Where should I create an after create hook

I have the following models:
class Page < ApplicationRecord
has_one :architecture
end
class Architecture < ApplicationRecord
belongs_to :page
end
And after a new page is saved I need to capture it's architecture (number of paragraphs por example). I would like to know what is the proper way to do that. I'm not sure if I should leave that responsible for the Page model:
class Page < ApplicationRecord
has_one :architecture
after_create :scrape_architecture
private
def scrape_architecture
data = call_something_to_capture_architecture(url)
create_architecture(data)
end
end
class Architecture < ApplicationRecord
belongs_to :page
end
or if it should be the responsibility of the Architecture model:
class Page < ApplicationRecord
has_one :architecture
after_create :create_architecture
end
class Architecture < ApplicationRecord
belongs_to :page
before_create :scrape_page
private
def scrape_page
data = call_something_to_capture_architecture(page.url)
create(data)
end
end
Which is actually incorrectly because before_create runs after the validation – causing MySQL errors duo to non null constraints
Thank you.
I would just create a job or service object that handles scraping.
class PageScrapingJob < ApplicationJob
queue_as :default
def perform(page)
data = call_something_to_capture_architecture(page.url)
architecture = page.create_actitecture(data)
# ...
end
end
You would then call the service/job in your controller after saving the page:
class PagesController < ApplicationController
def create
#page = Page.new(page_params)
if #page.save
PageScrapingJob.perform_now(#page)
redirect_to #page
else
render :new
end
end
end
This gives you a perfect control of exactly when this is fired and avoids placing even more responsibilities onto your models. Even though your models may contain little code they have a huge amount of responsibilities such as validations, I18n, form binding, dirty tracking etc that are provided by ActiveModel and ActiveRecord. The list really goes on and on.
This instead creates a discrete object that does only one job (and hopefully does it well) and that can be tested in isolation from the controller.
For such things you could use a service pattern
class PageScrapper
Result = Struct.new(:success?, :data)
def initialize(url)
#url = url
end
def call
result = process(#url)
...
if result.success? # pseudo methods
Result.new(true, result)
else
Result.new(false, nil)
end
end
end
class Fetcher
...
def call
scrapper = PageScrapper.new(url)
result = scrapper.call
if scrapper.success?
page = Page.build(parsed_result_if_needed(result)
page.architecture.build(what_you_need)
page.save # here you need to add error handling if save fails
else
# error handling
end
end
There are a lot of resources about why callbacks are bad.
Here is one from Marcin Grzywaczewski but you can also google it "why callbacks are bad ruby on rails".
By using service you are liberating models from having too much business logic and they do not need to know about other parts of your system.

Rails: sharing a controller across two models

I have two models:
Student
Classroom
Both of them have an action that does the same exact thing: it shows a report of daily activity. That is:
/students/1
/classrooms/1
Grabs activity for the model in question and displays it on the page.
In an attempt to dry this up, I created a ReportsController which extracts all the common logic of building a report.
If I leave the routes like this:
/students/1/report
/classrooms/1/report
Then I can have the ReportsController#show action look for params for :student_id or :classroom_id to determine which model type it is dealing with (for purposes of querying the database and rendering the correct view).
But I would prefer the URLs to be cleaner, so I also changed my routes.rb file to pass the show action for these models to the reports#show controller action:
resources :students, :classrooms do
member do
get :show, to: 'reports#show'
end
end
This works, but I can no longer depend on params to identify which model to work with and which view to render.
Question: should I parse request.fullpath for the model? Or is there a better way to make a shared controller understand which model it is working with?
Routing both show methods to the same controller method for code reuse is somewhat like banging a nail in with a dumptruck.
Even if you can find the resource by looking at the request url you would start splitting the ResortsController into a bunch of ifs and switches even before you got off the ground.
One solution is to add the common action in a module:
module Reporting
extend ActiveSupport::Concern
def show
# the Student or Classroom should be available as #resource
render 'reports/show'
end
included do
before_action :find_resource, only: [:show]
end
private
def find_resource
model = self.try(:resource_class) || guess_resource_class
#resource = model.find(params[:id])
end
# This guesses the name of the resource based on the controller name.
def guess_resource_class
self.class.name[0..-11].singularize.constantize
end
end
class StudentController < ApplicationController
include Reporting
end
# Example where resource name cannot be deduced from controller
class PupilController < ApplicationController
include Reporting
private
def resource_class
Student
end
end
self.class.name[0..-11].singularize.constantize is basically how Rails uses convention over configuration to load a User automatically in your UsersController even without any code.
But the most important key to DRY controllers is to keep your controllers skinny. Most functionality can either be moved into the model layer or delegated out to service objects.
I would put the common logic in the Event Model:
#Event Model
class Event < ...
def self.your_event_method
#self here will be either student.events or classroom.events
#depending on which controller called it
end
end
class StudentsController < ...
...
def show
student = Student.find(params[:id])
student.events.your_event_method
end
end
class ClassroomsController < ...
...
def show
classroom = Classroom(params[:id])
classroom.events.your_event_method
end
end

Rails Controller in nested module cannot resolve model in module with the same

I have a rails model located at app/models/scheduling/availability.rb which looks like:
class Scheduling::Availability < ActiveRecord::Base
end
I have a Rails controller located at *app/controllers/admin/scheduling/availabilities_controller.rb* which looks like:
class Admin::Scheduling::AvailabilitiesController < ApplicationController
def index
#availabilities = Scheduling::Availability.all
end
end
My routes look like:
namespace :admin do
namespace :scheduling do
resources :availabilities
end
end
When trying to load the url:
/admin/scheduling/availabilities
I get the error:
uninitialized constant
Admin::Scheduling::AvailabilitiesController::Scheduling
I have a feeling this is because Rails is confusing the Scheduling module/namespaces.
What am I doing wrong?
Found my answer in another answer.
Need to preface my module with ::
class Admin::Scheduling::AvailabilitiesController < ApplicationController
def index
#availabilities = ::Scheduling::Availability.all
end
end

Create new object from nested resources for the new action method

Suppose I have this association:
class User < ActiveRecord :: Base
has_one :car
end
class Car < ActiveRecord :: Base
belongs_to :user
end
routes:
resources :users do
resources :cars
end
Then what would be the code, in the 'new' action method in CarsController
class CarsController < ApplicationController
def new
#??? what's necessary to be put here,
# if I have request 'localhost:3000/users/1/cars/new'
end
...
end
Will Rails figure out everything automatically so I don't have to write any code in the 'new' method? Also, since the 'new' action will generate a 'form_for(#car)' form helper, how can I create this car object
Is this right?
class CarsController < ApplicationController
def new
#user = User.find(params[:user_id])
#car = #user.build_car({})
end
end
That looks just fine. Rails will not do any of this automatically. There are gems out there that can automate some of that if you like, but the jury is out on whether they're actually worth your time.
If you have no params to pass to the car, you can just run #user.build_car with no arguments, by the way. You'll also need to specifically say in the form_for helper that you're nesting the car under the user: form_for [#user, #car] Otherwise the form's destination will be /cars instead of /user/1/cars.
You're pretty close, Baboon. I would actually do:
class UsersController < ApplicationController
def new
#user = User.new
#car = #user.cars.build
end
end
But if you don't want to create the Car at the same time as the user, try:
class CarsController < ApplicationController
def new
#user = User.find(params[:user_id])
#car = #user.cars.build
end
end
I found the nested_form railscast extremely helpful when doing this sort of thing. In fact, I think you'll probably get a lot out of it (and using the nested_form gem).
railscasts_196-nested-model-form-part-1

Resources