Rails custom routing via Model String Key (instead of id) - ruby

I am trying to set up an application where I can route via a String key instead of an id attribute. To illustrate this, please consider the following:
I have a class Foo which inherits from the ActiveRecord::Base and is implemented as follows:
class Foo < ActiveRecord::Base
attr_accessible :subject
Subject is a string type which exists in my database.
I have a controller which I implement as follows:
class SubjectController < ApplicationController
def index
#snip
end
As you can see, the SubjectController inherits from the ApplicationController.
Running rake routes gives me the standard (GET, POST, PUT, DELETE) routes for my subject. This is the expected behavior and I understand this functionality.
I want to know how I can extend the routes.rb file so that I can use a string url in order to access a subject. For example:
Instead of typing in localhost:3000/subject/1, I would like this /:id to resolve when I type in the url: localhost:3000/subject/grumpy-cat-says-hello
What does this implementation look like?
How should I setup my routes.rb file to accommodate this?
How should I configure my application to allow for this type of implementation?
Thank you in advance.

I've always used https://github.com/FriendlyId/friendly_id for this stuff.
If you prefer something simpler, this will do
class Foo < ActiveRecord::Base
def to_param
[id, subject.parameterize].join("-")
end
end
Then you can access your resource with: localhost:3000/foos/1-grumpy-cat-says-hello
Basically, since the name of the resource still starts with a number, it will be converted to a number by Rails and everything will work seamlessly.
This Gist goes in much greater detail about this topic :)

Related

Rails: monkey-patching ActiveRecord::Base vs creating a Module

I am reading through The Rails 4 way (by Obie Fernandez), a well-known book about Rails, and from what I've read so far, I can highly recommend it.
However, there is an example section 9.2.7.1: Multiple Callback Methods in One Class that confuses me:
Bear with me, to make the problem clear for everyone, I have replicated the steps the book describes in this question.
The section talks about Active Record callbacks (before_create, before_update and so on), and that it is possible to create a class that handles multiple callbacks for you. The listed code is as follows:
class Auditor
def initialize(audit_log)
#audit_log = audit_log
end
def after_create(model)
#audit_log.created(model.inspect)
end
def after_update(model)
#audit_log.updated(model.inspect)
end
def after_destroy(model)
#audit_log.destroyed(model.inspect)
end
end
The book says then that to add this audit logging to an Active Record class, you would do the following:
class Account < ActiveRecord::Base
after_create Auditor.new(DEFAULT_AUDIT_LOG)
after_update Auditor.new(DEFAULT_AUDIT_LOG)
after_destroy Auditor.new(DEFAULT_AUDIT_LOG)
...
end
The book then notes that this code is very ugly, having to add three Auditors on three lines, and that it not DRY. It then goes ahead and tells us that to solve this problem, we should monkey-patch an acts_as_audited method into the Active Record::Base object, as follows:
(the book suggests putting this file in /lib/core_ext/active_record_base.rb)
class ActiveRecord::Base
def self.acts_as_audited(audit_log=DEFAULT_AUDIT_LOG)
auditor = Auditor.new(audit_log)
after_create auditor
after_update auditor
after_destroy auditor
end
end
which enables you to write the Account Model class as follows:
class Account < ActiveRecord::Base
acts_as_audited
...
end
Before reading the book, I have already made something similar that adds functionality to multiple Active Record models. The technique I used was to create a Module. To stay with the example, what I have done was similar to:
(I would put this file inside /app/models/auditable.rb)
module Auditable
def self.included(base)
#audit_log = base.audit_log || DEFAULT_AUDIT_LOG #The base class can override it if wanted, by specifying a self.audit_log before including this module
base.after_create audit_after_create
base.after_update audit_after_update
base.after_destroy audit_after_destroy
end
def audit_after_create
#audit_log.created(self.inspect)
end
def audit_after_update
#audit_log.updated(self.inspect)
end
def audit_after_destroy
#audit_log.destroyed(self.inspect)
end
end
Note that this file both replaces the Auditor and the monkey-patched ActiveRecord::Base method. The Account class would then look like:
class Account < ActiveRecord::Base
include Auditable
...
end
Now you've read both the way the book does it, and the way I would have done it in the past. My question: Which version is more sustainable in the long-term? I realize that this is a slightly opinionated question, just like everything about Rails, but to keep it answerable, I basically want to know:
Why would you want to monkey-patch ActiveRecord::Base directly, over creating and including a Module?
I would go for the module for a few reasons.
Its obvious; that is to say, I can quickly find the code that defines this behavior. In acts_as_* I don't know if its from some gem, library code, or defined within this class. There could be implications about it being overridden or piggy-backed in the call-stack.
Its portable. It uses method calls that are commonly defined in libraries that define callbacks. You could conceivably distribute and use this library in non-active-record objects.
It avoids the addition of unnecessary code on the static level. I'm a fan of having less code to manage (less code to break). I like using Ruby's niceties without doing to much to force it to be "nicer" than it already it is.
In a monkey-patch setting you are tying the code to a class or module that could go away and there are scenarios where it would fail silently until your class can't call acts_as_*.
One downfall of the portability argument is the testing argument. In which case I would say you can write your code to protect against portability, or fail early with smart warnings about what will and won't work when used portably.

Rails Metaprograming/Refactoring/DRYing the controller

I have a controller with several actions. Many follow this pattern:
def favorites
#favorites = Favorite.where(organization_id: #resource.id).page(params[:page]).per(50)
end
It's not just favorites, but there's also downloads, searches, lists etc and they're all so similar that I wanted to create a method that I could call in a before_filter. Something like this:
def set_instance_variable
subject = __method__
class = __method__.singularize.constantize
instance_variable = self.class.instance_variable_set("##{subject}", "#{class}.where(organization_id: #resource.id).page(params[:page]).per(50)")
end
The syntax might be a little off here, but I know this won't work because __method__ will always be set_instance_variable and not the parent method where it is called.
Is there a way to dynamically set instance variables based on the method that defines them? Is this example above even on the right track?
I like the way the CanCan library handles this problem. With CanCan, you call a class method at the top of your controller:
load_resource
CanCan then looks at:
the action you're in to determine whether you want a collection or singular resource,
the name of the controller to determine the class to load
authorization rules to add scopes like your organization_id restriction (cancan is also a library for defining these)
I think pagination and resource loading are separate things, and you shouldn't put them in the same method. I'd shoot for an interface like this:
class FavoritesController
load_resource
paginate_resource only: [:index]
def show
# #favorite loaded here
end
def index
# #favorites loaded and paginated here
end
end
https://github.com/CanCanCommunity/cancancan/blob/develop/lib/cancan/controller_resource.rb#L29
If it makes more sense in your application to have non-restful resources, then you can't re-use the convention-based thing cancan is and instead have to define your own function. Consider something like this:
def favorites
#favorites = load_resource Favorite
end
private
def load_resource(klass)
klass.where(organization_id: #resource.id).page(params[:page]).per(50)
end

Is there a better way to create a Rails subclass?

My single table inheritance system creates it's subclasses based on a input string from my service. I am wondering if there is a better way to write this code as it seems clunky.
The client should not know, nor should it care, what my sub-classing structure is. When a request comes in the parameters I get are:
{calculator: {course: 'science'}}
The class:
class Calculator < ActiveRecord::Base
before_save :subclass, on: :create
def subclass
case course.downcase
when "science"
self.type = "Calculator::ScientificCalc"
when "standard"
self.type = "Calculator::StandardCalc"
end
end
end
It seems smelly to have a callback on the object and then set the 'self.type'. Any body have a better solution to this?
Im not sure if I get what you want to do there, but you should create a type column on your calculator table, and your classes will inherit it from Calculator.

Rails sharing controller functionality and menus

Sorry if this seems daft (not much sleep lately) but I'm trying to figure out the best way to share controller (and method) functionality between controllers without writing the same code for each one.
Let's say I have various controllers with the same method called search. Inside this method I have situations for the same term for different controllers, like:
case term
when "beginning_parens"
#books = Book.where("title REGEXP ? AND localeLanguage LIKE ?", '^\\([0-9]*\\)', params[:language]).not_locked.reorder(order).paginate(:page => params[:page])
when 'search_all_caps'
#books = Book.detect_uppercase.not_locked.reorder(order).where("localeLanguage LIKE ?", params[:language]).paginate(:page => params[:page])
Now I don't want to have to write all of that for each controller. And I'd like not to have to pass the params[:language], paginate, etc. to each of the cases since I want each one to search by language and paginate as well.
Or course, the instance variable would change among controllers, instead of #books we would have #dvds, #games, etc.
I also find myself having to pass all the params in the menu links:
= link_to 'with all CAPS', books_search_path(:term => 'search_all_caps', :language => params[:language])
And I'm sure there has to be a better way to do this.
I'm already sharing common methods for the models with a file I call at the top of each model with extend ModelFunctions. This file contains methods such as detect_uppercase which is used above and is common for many controllers. So at least I have that part working :)
Any help would be appreciated.
If you're going to be writing a bunch of controllers that do just about the same thing, it seems like inheritance might be the way to go here.
Make one controller (let's call it the BaseController) and give it all the methods you want the other controllers to have. Then each of your other controllers can inherit from BaseController and just override the methods you need them to and set class variables for things like which model to search, what instance variable to set, and so on. For example:
class BaseController < ActionController::Base
def search
##model.where("title REGEXP ? AND localelanguage ...")
end
end
class BooksController < BaseController
##model = Book
end

Avoid creating duplicate objects from subclasses of a given model in Rails

I have a model group.rb with subclasses organization.rb, company.rb, etc. I'm wondering if there is a way to create an organization with the name "Rails Beginners Society" without also creating a company with the name "Rails Beginners Society"? As it stands right now it looks like if I do something like Organization.find_or_create_by_name(:name => #profile.organization) I end up not only creating an organization, but also a company and all the other subclasses of group.rb with the name I supply in my Controller.
Any ideas/guidance would be much appreciated!
My Models look like this:
class Group < ActiveRecord::Base
has_and_belongs_to_many :users
end
class Organization < Group
end
Etc...
It sounds like what you're trying to do is STI (Single Table Inheritance). Rails does support this.
class Group < ActiveRecord::Base
end
class Organization < Group
end
etc...
If you have a column type in your groups table, Rails should handle everything for you.

Resources