Single Table Inheritance and routing in Ruby on Rails 3.0 - ruby

I am having some trouble getting routing to play nicely with Single Table Inheritance in my Ruby on Rails application. I am using Ruby 1.9.2 and Rails 3.0.6. This is in development so the back-end is SQLite3, in case that makes a difference.
Let's say I have two products, widgets and sprockets. My application keeps track of bug numbers and support case tickets for both the products but the bugs and support tickets themselves are stored in other systems. There are two separate teams that work on these two products.
I have implemented Single Table Inheritance for the two types of bug records because the validation rules for widget bug numbers and sprockets bug numbers are different (the two teams use different bug tracking systems) and there is a possibility that I will have to add further products to the application that behave wildly differently. Using STI gives me the flexibility to implement additional methods and properties as required.
The widgets team only cares about widgets information and the sprockets team only cares about sprockets information. There is a third team that needs to be able to view the information on both the widgets and the sprockets. The widgets team will access the application using the path /widgets and the sprockets team will access the application using the path /sprockets. I set this up in routes.rb using namespaces:
resources :bugs
namespace "widgets" do
resources :bugs
end
namespace "sprockets" do
resources :bugs
end
I have set up the following models which work as expected when I fire up irb and use WidgetBug.create() or SprocketBug.create():
bug.rb
class Bug < ActiveRecord::Base
end
widget_bug.rb
class WidgetBug < Bug
# Some validation rules
end
sprocket_bug.rb
class SprocketBug < Bug
# Some different validation rules
end
I used scaffolding to create the controller and the view for the bug object, then modified the controller to try to generalize it so it could be used with both widgets bugs and sprockets bugs. For example, the index method looks like this:
def index
# The scaffold code lists all bugs, which is not what we want
# #bugs = Bug.all
# Only return bugs of the subclass we're looking for
#bugs = eval("#{params[:controller].classify}.all")
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #bugs }
end
end
I then used create() to populate the database with several bugs of each type. Unfortunately when I browse to /widgets/bugs, the bugs for both products appear. After some debugging, I determined that the classify call is returning Widgets::Bugs or Sprockets::Bugs, so when I call all on it, it appears to be running against the superclass instead of running against the subclass.
I've reviewed the routing documentation and done quite a bit of searching on Google but I'm still at a loss as to how I can change the routing or the controller to make this work correctly. Any help would be greatly appreciated.

Checkout this post: STI, one controller
Routes
resources :widgets, :controller => "bugs", :type => "Widget"
resources :sprockets, :controller => "bugs", :type => "Sprocket"
# And I don't know if you need this
resources :bugs, :type => "Bug"
Controller
def index
#bugs = params[:type].constantize.all
end
UPD
namespace "widgets" do
resources :bugs, :type => "Widget"
end
namespace "sprockets" do
resources :bugs, :type => "Sprocket"
end

I wrote a blog post on STI in Rails 3 that discusses some common pitfalls and proper work arounds, including the problem you mention. http://www.christopherbloom.com/2012/02/01/notes-on-sti-in-rails-3-0/

Related

Recommended use of couchrest model in a multi-tenant app

I'm looking for recommendations on how to implement multi-tenancy with couchrest model in a rails app. For my multi-tenant app, I'm thinking of two options:
{ edit - removed my ugly options because they'll only confuse future readers }
I would like this to work well with 10K users.
SOLUTION:
Based on Sam's advice, here's what I did and it's working well -
In my case, I needed to override the proxy_database method because the standard naming for proxy databases didn't match my naming.
created the master
class Site < CouchRest::Model::Base
property :name
property :slug
proxy_for :users
proxy_for ...(all the other multi-tenant models)
# Databases are on same server in this example
def proxy_database
#db ||= self.server.database!(slug)
end
end
Then in each multi-tenant model
class User < CouchRest::Model::Base
...
proxied_by :site
In ApplicationHelper create a 'site' method that you can reuse in all your controllers.
module ApplicationHelper
def site
db_name = current_user.db_name
#site ||= Site.create(slug: "#{db_name}_#{Rails.env}" )
end
Then controller might do something like:
def show
user = site.users.find(params[:id])
render :json => user
end
You might want to checkout the Proxying feature of CouchRest Model for this. More details can be found here:
http://www.couchrest.info/model/proxying.html
Although I have no personal experience, I understand that CouchDB handles >10k databases. Here is a good thread of ways of scaling the number of users:
http://comments.gmane.org/gmane.comp.db.couchdb.user/13862
A few considerations to take into account when dealing with lots of databases:
File system sub-directory count, not a problem with Ext4.
Namespace databases to split between sub-directories and/or servers.
System open file limit. Usually around 10k. Probably fine if not all databases are accessed at the same time.
Hope that helps.

Loading attributes only once in Grape

I'm little new to Ruby, but I have the following coded using Grape API in Ruby. I have #data = YAML.load() called every time it hits GET /api/v1/foo, is there a way in Grape to only load it once and use it? This way is more optimized and not calling YAML.load() at every time. Should I override the initialize method and put a super() for this operation?
Thanks,
require 'grape'
require 'json'
require "yaml"
module MyProject
CONFIG_FILE = "./config.yml"
class Api < Grape::API
rescue_from :all
prefix 'api'
version 'v1'
format :json
resources :foo do
get do
#data = YAML.load(File.open(MyProject::CONFIG_FILE))
end
end
end
end
The short answer is that Grape doesn't work quite how you think, and attribute variables of the MyProject::Api are not the way forward for your new web service. However, it is an interesting question, and worth exploring why this is so.
If you add a puts self.inspect inside the resources :foo block, and run using rackup, when you call the route you should see that self is in fact a Grape::Endpoint object. Also, no matter what you try to do with instance variables, they will always start in the same state for each request. That is because Grape turns your route definitions into prepared Grape::Endpoint objects, with a lot of the definition data and setup put into a quickly-accessible form (so that it is not figured out on each request). Eventually, on each request, the matching Grape::Endpoint object including your block (and other details you defined for the route) is duplicated before being called, meaning that state is not maintained between requests.
This may seem complicated, but most frameworks covering web service requests will do something similar. Generally you don't want request-handling state to persist between requests. Frameworks with larger scope - e.g. Rails - have places to put more persistent data planned out for you. Grape does not have this defined, which has its pros and cons. One obvious plus point is that you are more free to use whatever other data persistence approach that you wish to.
23tux's answer will sort you out immediately for loading config. Although I'm not entirely sure how ##data becomes accessible to the endpoint block (it may even be creating a closure around the variable).
Longer term, you should look to moving config management out of your MyProject::Api class, and including it as a module via Grape's helpers method (I'm happy to provide an example if you are interested).
Edit: Example based on your current code, but moving config management to a separate module:
require 'grape'
require 'json'
require "yaml"
module MyProject
module Config
CONFIG_FILE = "./config.yml"
##data = nil
def config
##data ||= YAML.load( File.open( CONFIG_FILE ) )
end
end
class Api < Grape::API
rescue_from :all
prefix 'api'
version 'v1'
format :json
helpers MyProject::Config
resources :foo do
get do
config
end
end
end
end
This is one step further, structurally, than 23tux's answer, but is still not completely separating concerns of storage (and caching etc) versus api access. As you progress towards a more sophisticated web service, you will want to keep the Grape route definitions simple, with only a small amount of logic that manages or manipulates the data - well, at least as seen directly in the blocks.
One way to link between your Grape definitions, and other gems that might manage config, logging, authentication and other services, is via Grape's helpers method. Grape also has some built-in helper methods for common tasks.
The main exception to using helpers MyModule to add shared functions into Grape is when you want to manage displaying data objects (aka "models") from your core application. For that you have a few choices, but the grape-entity gem and the present method is not a bad place to start.
If the #data is the same for the whole api, and doesn't change at any time, just use a class variable
require 'grape'
require 'json'
require "yaml"
module MyProject
CONFIG_FILE = "./config.yml"
class Api < Grape::API
##data = YAML.load(File.open(MyProject::CONFIG_FILE))
rescue_from :all
prefix 'api'
version 'v1'
format :json
resources :foo do
get do
puts ##data
end
end
end
end
Not tested, but with this way, you ensure that the data is only loaded once, when your Api class is loaded

Injecting a scope through an association extension in Rails 2.3.17

I'm upgrading from Rails 2.3.5 to 2.3.17 and have come across a pretty obscure problem. I use the following extensions to inject a scope within an association accessor, and provider a custom builder:
module XYZFilterExtension
def in_context(context)
if proxy_owner.context == :abc && context != :admin
scoped(:conditions => {:startup => false})
else
scoped({})
end
end
end
module OtherExtension
def build_with_component_instance(attributes = nil)
attributes ||= {}
attributes.reverse_merge!(:parent_id => proxy_owner.component_instance.id)
instance = build
instance.build_component_instance
instance.attributes = attributes
instance
end
end
has_many :pages, :extend => [ABCExtension, OtherExtension]
In Rails 2.3.5 I could call:
Something.pages.in_context(:abc).build_with_component_instance
and I'd get a Page object (And it's associated component_instance, the build is complicated because it's a polymorphic association being built from the other direction).
Now I get:
undefined method `build_with_component_instance' for #<Class:0x1151dcae8>
Inspecting the scope, the only difference that I could find is that calling proxy_scope on the scope created by in_context() returns the Page model in 2.3.17 and the scope in 2.3.5.
I'm not sure where to go from here. I can't extract the scope out into a module to include in each model because I need to make a decision based on the proxy_owner in the association.
Update: It appears the problem is around extension methods not being available within the context of a scope. Pretty strange but I guess it kind of makes sense. Unfortunately both my scope definition and the build extension require knowledge of their association context. Any ideas welcome :)
I ended up not finding a way to work around this. In the end I had to avoid the bug in the specific situation in which it occurred. Fortunately it was only in a couple of places in the app.

Kaminari and Mongoid 'undefined method page' when model defined in separate gem

I have a simple logging application consisting of ruby scripts and a "read-only" Rails application that reads from the MongoDB documents. To keep the models consistent they both use a common gem where the Mongoid documents have been defined.
The problem I'm having is that Mongoid documents that are defined in the gem are not getting the Kaminari decoration. In other words (simplified as much as possible).
# User is a Mongoid document defined in the Rails app (i.e. standard behavior)
require 'kaminari'
class UsersController < ApplicationController
def index
User.page(params[:pg]) # works great
end
end
but
# SharedLogging::LogEntry is a Mongoid document defined in a separate gem
require 'kaminari'
class LogEntriesController < ApplicationController
def index
SharedLogging::LogEntry.page(params[:pg]) # undefined method 'page' for ...
end
end
I think the solution is to do something in /config/initializers/kaminari_config.rb to force the pagination to be applied to the shared models but I haven't been able to stumble across the correct solution.
Alternately, I've also tried adding Kaminari as a dependency in the shared gem, but no luck there.
It seems to be resolved with the same solution described here: https://github.com/collectiveidea/delayed_job_mongoid/issues/10
In my kaminari_config.rb I've added the following lines:
SharedLogging::LogEntry.send(:include, Kaminari::MongoidExtensions::Document)
SharedLogging::LogEntry.send(:include, Kaminari::MongoidExtensions::Criteria)
The first line is required if I do SharedLogging::LogEntry.page(params[:pg]) the second if I apply a scope first (e.g. SharedLogging::LogEntry.by_date(params[:dt]).page(params[:pg]) ).
The biggest problem is that I need both lines for each model in my gem; and there are a lot of models.
Rather than manually extending, use Kaminari's hooks initializer. There are details in another answer I posted:
undefined method page for #<Array:0xc347540> kaminari "page" error. rails_admin

In Rails 3.x, how to design a view for a nested resource that refers to the parent resource?

Let's say I have a nested resource like in the Rails guide example:
http://guides.rubyonrails.org/routing.html#nested-resources
resources :magazines do
resources :ads
end
This routes a call to /magazines/newsweek/ads to the AdsController#index. It requires a magazine_id in the URL...how should I go about creating a view that is based off of a template used for Ads#index yet also includes the context of the parent resource?
For example, all parent resources that have Ads will have a table list of those ads. But I'd like the top of the view to include boilerplate information for each Magazine. If /magazines/newsweek/ads goes directly to the generic AdsController#index, how do I make that #index view aware of the need to include boilerplate generated from the Magazine model?
And if other models have relationships to ads (TelevisionShow has_many :ads), I'd like AdsController#index to react differently to those as well.
EDIT:
This is how I've done such things in the past, going through MagazinesController. Let's say I want a separate Magazine#show action...the routes would be:
resources :magazines
resources :ads
get "magazines/:id/ads", :controller=>"companies", :action=>"ads"
And the controller would be:
class MagazinesController < ApplicationController
def show
...
end
def ads
...
end
end
And then Magazine#ads would have a partial for ads listings that would be shared across all other types of resources.
Makes sense to me, but that route seems like it could somehow be DRYer?
It sounds like you should be working in the magazine controller.
#Magazine_controller
def show
#magazine = Magazine.find(params[:id])
#ads = #magazine.ads
end
Then in your view just render a partial for your collection of ads. Your ads partial will be saved in the ads area. So in your veiw:
<%= render :partial => "ads/ad", :collection => #ads %>
http://guides.rubyonrails.org/layouts_and_rendering.html#using-partials

Resources