Extending application's model in rails engine - ruby-on-rails

I have an application which defines some models. I want to extend the functionality of some models(eg. adding methods,adding associations) from application to my engine.
I tried adding a model in the engine with the same name as my application's model and Rails will automatically merge them, however it doesn't work.
eg:
(Application's model)
class Article < ActiveRecord:Base
def from_application
puts "application"
end
end
(Inside my Engine)
module MyEngine
class Article < ::Article
has_many :metrics, :class_name => 'Metric'
end
end
has_many association is not getting applied to my Articles model when I try to access #articles.metrics. Any ideas ?

You have the right idea and are close. But your implementation is a little off.
Generally, your engine should have no knowledge of your host app. That way, your engine and the host app(s) stay loosely coupled. So, classes in your engine should not inherit from classes in your host app. (BTW, your approach doesn't work, I believe, because of the way ruby does constant lookups, but that's a different discussion.)
Instead, use the included hook. In the host app, you do something like:
class Article < ActiveRecord:Base
include FooEngine::BarModule
def from_application
puts "application"
end
end
And inside the engine:
module FooEngine
module BarModule
def self.included(base)
base.class_eval do
has_many :metrics, :class_name => 'Metric'
end
end
end
end
When the ruby interpreter goes to include FooEngine::BarModule in Article, it will look for and run (if found) the self.included method in FooEngine::BarModule, passing in the Articleclass as base.
You then call class_eval on the base (Article) which re-opens the Article class so that you can add methods or whatever monkey business you're up to (define new methods in situ, include or extend other modules, etc.).
In your example, you call the has_many method, which will create the various association methods provided by has_many.
If (a) you're going to add a lot of metrics-related functionality through your engine, (b) you want to have lots of classes make use of the metrics-related functionality, and (c) you want some of the functionality to vary from class-to-class (where included), you might consider creating an acts_as_having_metrics (or similar). Once you head down this path, it's a whole new world of wondrous metaprogramming.
Best of luck.

Do you have your metrics model have a belongs_to association with Articles.
You might want to give the other side of the association, Metrics a belongs_to Articles to have this work properly. Also, make sure to have a migration to hold articles_id on the metrics table. Everything should work fine.

Related

Is this a known Rails Manager pattern

I am going through a co-workers code and am not able to find a single tutorial where this has been used. Can someone point me to some resources where this has been used. This has made code very clean but I haven't found any reference to it. This is only part of this class. It includes other some more methods.
class Manager
include ActiveModel::Model
include ActiveModel::Associations
attr_accessor :application_id, :user_id, :user_application_id,.........
belongs_to :application
belongs_to :user_application
belongs_to :user .. more belongs .......
# This method is necessary to enable this ActiveModel Class to be used in views along with Form helpers
def self._reflect_on_association(association) #:nodoc:
_reflections[association.to_sym]
end
def []=(attr, value)
self.send("#{attr}=", value)
end
def [](attr)
multi_attribute_ids = [:some_ids.to_s, :someid2.to_s]
return if multi_attribute_ids.include?(attr)
self.send(attr)
end
def applicant_name
end
-- some more methods
end
What would be the use of such a "manager". What are the two methods that are using self.send doing here. Is this a common pattern in rails.
Yes, with the introduction of ActiveModel in Rails 3, it has become an increasingly common pattern to use domain objects (called a manager in this case) that are not backed by an actual database table but which look and feel like models.
Even though ActiveModel makes it particularly convenient to pick and choose Rails model features to be incorporated into arbitrary classes, this pattern is something Rails pioneers have been encouraging since a long time.
As has been illustrated clearly in the example you posted, this pattern allows us to define virtual models and virtual associations which can easily take advantage of form helpers and other rails niceties written assuming model objects.

How to set has_many association between parent app's model and mounted engine's model?

Following to RailsGuides instruction, I have created an engine for blogging system in my app. This blog engine is mounted as /blog.
RailsGuides shows how to add belongs_to association to the mounted engine's Article model. However, the parent app's User model still requires has_many association to the engine's Article model which is in different namespace.
How to set has_many association between parent app's model and mounted engine's model?
Ruby 2.2.0
Rails 4.2.0
Thanks in advance.
In the rails application, you know what module you include, so you can simply specify the relation with the class name ;)
has_many :articles, class_name: 'Blog::Article'
check if this is the right syntax for your database adapter, e.g. I'm using this for Mongoid, but it should be the same with ActiveRecord AFAIK
The accepted answer requires manual modification of main_app's parent model in order to set the has_many relationships to the engine's child model. So each time you add the engine to one your main_apps you would have to go into the main_apps models and set up all required relationships by hand.
A more robust, although more complicated, solution would be to use the decorator pattern in the engine so that the engine will auto-configure main_app's parent model with the relationships it needs.
By using this method you just need to add a setting to the engine initializer in your main_app and the engine will handle the rest.
In engine:
blog.gemspec.rb
s.add_dependency 'decorators' #this will install the decorators gem for use in engine
lib/blog/blog.rb
module Blog
class Engine < ::Rails::Engine
isolate_namespace Blog
engine_name 'blog'
#to set up main_app objects via decorators in engine
config.to_prepare do
Decorators.register! Engine.root, Rails.root
end
end
end
lib/blog.rb
require 'decorators'
module Blog
mattr_accessor :user_class #Can now reference this setting as Blog.user_class
class << self
#the following lets us add functionality to main_app user model
def decorate_user_class!
Blog.user_class.class_eval do
has_many :articles, :class_name => "Blog::Article", :foreign_key => "user_id"
end
end
end
end
app/decorators/lib/blog/user_class_decorator.rb
if Blog.user_class
Blog.decorate_user_class!
else
raise "Blog.user_class must be set in main_app blog.rb initializer"
end
In main app:
app/initializers/blog.rb
Blog.user_class = User
If you run rails console from main app, you will see relationships will have been set properly. The decorator pattern in the engine can also be used to extend the main_app's models and controllers in different ways, not just Activerecord relationships. Almost complete decoupling achieved!

Calling class methods from mixed in module in ruby

The scenario: I have a couple of ActiveRecord models in my rails system that all need to be controlled via an access control list. I have a nice little ACL implementation that does what I want, but right now the check-access calls are all duplicated in each controlled object type (document, user, etc).
My intuition is to pull that shared code into a module and use it with a mixin. I'm not sure this is possible (or what the right syntax is), because the mixed-in module has calls to ActiveRecord::Base methods - there's scope and has_many definitions.
The example of what I'd like to accomplish is here:
class Document < ActiveRecord::Base
include Controlled
end
module Controlled
has_many :acls, as: :controlled
scope :accessible, ->(uid, level){where("BUNCH OF SQL HERE")}
def access_convenience_methods
#stuff to provide easy access to authorization checks
end
end
And then I'd have a few other models that derive from ActiveRecord::Base that include Controlled.
It's the has_many and scope calls in the module that are causing heartache - I can't call them from within the mixed-in module, apparently this context doesn't have access to the outer class methods.
Any advice is welcome.
You are correct in that you can't just call class methods from the module like that.
Nowadays the boilerplate code required to do this has been wrapped into ActiveSupport::Concern; it does exactly what you want.
[EDIT]: I also suggest you should study the boilerplate code itself, as it's pretty short and readable and a good example of Ruby metaprogramming.
Aha, this is clearly a ruby newbie failure here - I need to put the has_many and other one-off calls inside an included block. It seems like ActiveSupport::Concern is precisely the right thing to use here:
module Controlled
extend ActiveSupport::Concern
included do
has_many :acls, as: :controlled
scope :accessible, ->(uid, level){where("BUNCH OF SQL HERE")}
end
def access_convenience_methods
#stuff to provide easy access to authorization checks
end
end

ActiveRecord model inheritence and polymorphism

I have a class heirarchy but not single-table inheritence.
ThrowAway < ActiveRecord::Base
and
Junk < ThrowAway
the problem is that all ThrowAway objects have references to a Location. A Location
belongs_to :throw_away, :polymprohic => true
The problem is that if I define
ThrowAway < ActiveRecord::Base
has_many :locations, :as => :throw_away
end
then even if Junk inherits from it and defines a different table name, the throw_away_type column will always be set to ThrowAway where I actually want it set to Junk.
Now there will be many of these subclasses so there will be Stuff < ThrowAway Rags < ThrowAway etc. I want them all to have a Location without defining a location relationship in each individual class.
Is this possible with rails? Problem is that there is not just location, there are other of these sort of relationships and I'd rather follow some DRY here. I'm assuming I need to create a generator method which will execute on the current object and generate these relationships generators on runtime.
Thanks.
ActiveRecord doesn't seem to be able to cooperate with model inheritance that's not STI. Fortunately, that's probably not what you really want anyway. You probably want a mixin instead.
Create a module that contains all of the functionality that you want your models to share in common, and have all of your models include that module. I would probably put the module in your models directory, but give it an adjective as a name. Some folks might put the module in the lib directory or create a lib directory within the models directory for it.
In your case, you'll want the association to be defined at the time the module is mixed in, so you'll need to use a callback. Something like..
module Trashable
def self.included(base)
base.send :has_many, :locations, :as => :throw_away
end
end

How many classes is too many? Rails STI

I am working on a very large Rails application. We initially did not use much inheritance, but we have had some eye opening experiences from a consultant and are looking to refactor some of our models.
We have the following pattern a lot in our application:
class Project < ActiveRecord::Base
has_many :graph_settings
end
class GraphType < ActiveRecord::Base
has_many :graph_settings
#graph type specific settings (units, labels, etc) stored in DB and very infrequently updated.
end
class GraphSetting < ActiveRecord::Base
belongs_to :graph_type
belongs_to :project
# Project implementation of graph type specific settings (y_min, y_max) also stored in db.
end
This also results in a ton of conditionals in views, helpers and in the GraphSetting model itself. None of this is good.
A simple refactor where we get rid of GraphType in favor of using a structure more like this:
class Graph < ActiveRecord::Base
belongs_to :project
# Generic methods and settings
end
class SpecificGraph < Graph
# Default methods and settings hard coded
# Project implementation specific details stored in db.
end
Now this makes perfect sense to me, eases testing, removes conditionals, and makes later internationalization easier. However we only have 15 to 30 graphs.
We have a very similar model (to complicated to use as an example) with close to probably 100 different 'types', and could potentially double that. They would all have relationships and methods they inheritated, some would need to override more methods then others. It seems like the perfect use, but that many just seems like a lot.
Is 200 STI classes to many? Is there another pattern we should look at?
Thanks for any wisdom and I will answer any questions.
If the differences are just in the behavior of the class, then I assume it shouldn't be a problem, and this is a good candidate for STI. (Mind you, I've never tried this with so many subclasses.)
But, if your 200 STI classes each have some unique attributes, you would need a lot of extra database columns in the master table which would be NULL, 99.5% of the time. This could be very inefficient.
To create something like "multiple table inheritance", what I've done before with success was to use a little metaprogramming to associate other tables for the details unique to each class:
class SpecificGraph < Graph
include SpecificGraphDetail::MTI
end
class SpecificGraphDetail < ActiveRecord::Base
module MTI
def self.included(base)
base.class_eval do
has_one :specific_graph_detail, :foreign_key => 'graph_id', :dependent => :destroy
delegate :extra_column, :extra_column=, :to => :specific_graph_detail
end
end
end
end
The delegation means you can access the associated detail fields as if they were directly on the model instead of going through the specific_graph_detail association, and for all intents and purposes it "looks" like these are just extra columns.
You have to trade off the situations where you need to join these extra detail tables against just having the extra columns in the master table. That will decide whether to use STI or a solution using associated tables, such as my solution above.

Resources