I'm a bit confused with namespacing in engines. In a Rails engine, where isolate_namespace is used,
module Blog
class Engine < Rails::Engine
isolate_namespace Blorgh
end
end
when is it required that you refer to objects with the namespace Blog (e.g. Blog::Post vs just Post)?
As for example, within the controller of a Post resource of the engine, is it ok to just do Post.find? When it is absolutely required that you use Blog::Post?
Also in models associations, assume that Post has_many :comments. Somehow, I was expecting to define it as follows:
class Post < ActiveRecord::Base
:has_many "blog/comments"
end
since everything is namespaced (models, table names, ...), but it looks like has_many :comments just works. Why namespacing is not used in association keys, and in the case where a Comment resource exists in the host applications, how does rails know which Comment I'm referring to?
When you're inside some module, you can refer to other member of the module without giving the module name, eg:
module Foo
class Bar
def initialize
#baz = Baz.new # same as Foo::Baz.new
end
end
class Baz
end
end
If Baz doesn't exist in the current module, it will cascade down to find the definition, eventually calling const_missing (upon which is built the autoload of classes in Rails), then throw an error if nothing is found.
Rest of your questions is answered here.
Related
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.
I have a Boilerplate model which has two descending models:
BoilerplateOriginal
BoilerplateCopy
While BoilerplateOriginals is sort of a template that admins create, BoilerplateCopys are copies of the originals that are free to edit by everyone, and they also have some more associated objects (e.g. BoilerplateCopy belongs_to: BoilerplateOriginal, BoilerplateCopy belongs_to: Project or BoilerplateCopy has_many: Findings, all of which BoilerplateOriginal doesn't).
So in my opinion, it makes sense to maintain two different model classes that share the same basic functionalities.
Because they also look quite the same, I want to use the same views for them. But under the hood, they are treated a bit different, so I also have two different controllers inheriting from a base controller.
Everything seems to work fine, except that form_for boilerplate, as: resource_instance_name raises an exception undefined methodboilerplates_path', but only when called asnewaction, not when called asedit` action.
Here's what I have done so far to make it work (and everything else seems to work fine):
# config/routes.rb
resources :boilerplate_originals
# app/models/boilerplate.rb
class Boilerplate < ActiveRecord::Base
def to_partial_path
'boilerplates/boilerplate'
end
end
# app/models/boilerplate_original.rb
class BoilerplateOriginal < Boilerplate
end
# app/controllers/boilerplates_controller.rb
class BoilerplatesController < InheritedResources::Base
load_and_authorize_resource
private
def boilerplate_params
params.require(:boilerplate).permit(:title)
end
end
# app/controllers/boilerplate_originals_controller.rb
class BoilerplateOriginalsController < BoilerplatesController
defaults instance_name: 'boilerplate'
end
# app/views/boilerplates/_form.html.slim
= form_for boilerplate, as: resource_instance_name
# ...
As pointed out, new/create works flawlessly, but edit doesn't. And I'm using InheritedResources, by the way.
Rails is dong it correctly, you're slightly doing it wrong
the problem is:
resources :boilerplate_originals
that will just generate routes for especially boilerplate_originals.
when using form_helpers of rails rails will lookup for a route based on the models class which is in this case "boilerplate_copy". that means its looking for a edit_boilerplate_copy_path (which isnt generated by rails)
You said that BoilerplateCopy and BoilerplateOriginal are pretty much looking the same (i guess you just copy a model, there are gems out for doing that for you...)
If you go correct STI it "should" be
class Boilerplate; end
class BoilerplateCopy < Boilerplate; end
class BoilerplateOriginal < Boilerplate; end
for that you need only a route for Boilerplate
resources :boilerplate
and of course a boilerplate_controller
everything is handled as a boilerplate and the form_helper will look up for a new_boilerplate_path, which exists, no matter if its a copy or a original.
I found the problem.
# app/controllers/boilerplate_originals_controller.rb
class BoilerplateOriginalsController < BoilerplatesController
defaults instance_name: 'boilerplate' # This is wrong!
defaults instance_name: 'boilerplate', resource_class: BoilerplateOriginal # This is right!
end
Now a BoilerplateOriginal object is passed to the new action, and not a Boilerplate object.
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!
I have a Rails 3.2.8 model that is in a namespace. I'm sure it used to be that the namespace was automatically expanded out into what the table name would look like. e.g. Module::Model would become module_model in the database.
For some reason I have a problem now that this does not happen through the rails application. The migrations, models and controllers all live in a namespace but when the model looks for a table it ignores the table prefix and complains that it cannot find the database.
Here is the example
module Magazine
def self.table_name_prefix
'magazine_'
end
end
module Magazine
class Paper < ActiveRecord::Base
#some stuff here
end
end
When I do a query on Magazine::Paper it looks for the table "paper" and not "maganzine_paper" which it should. This is causing the whole application to exibit some weird behaviour. I have also tried setting the table name manually in the model with self.table_name but this only lets the model find the right table. Routes still play games with me as when I nest something below papers for example comments then it looks for a route that does not exist.
This is what is inside the routes. Comments should go to /magazine/papers/1/comments but it looks for a route called /magazine/comments
namespace :magazine do
resources :papers do
resources :comments
end
end
What is going on?
Update:
Ok so I figured it out but not sure if I should ignore it or see whats causing it. The engine name is magazine and in the engine I create a namespace for models called magazine but this causes the problems. So rails can't have a namespace name similar to the engine name?
I ran into the same problem, forgot my application's name (main module) had the same name as a namespace for my models. Changing either, the module name or the application name solves this issue.
If you just write
namespace :magazine do
resources :papers do
resources :comments
end
end
and run rake routes you can see it is looking for proper urls and when you say it is looking for /magazine/comments then you surely must have written another routes somewhere in the file.
When you write
module Magazine
def self.table_name_prefix
'magazine_'
end
end
And when you write your model with the same module name Magazine then you are replacing the same module with different contents within it. Instead of writing model Paper within Magazine module just include the module within the model Paper in following way.
class Paper < ActiveRecord::Base
include Magazine
#some stuff here
end
This will ensure your table_name_prefix method to be called when your model is loaded when your application starts.
Here's a Ruby OO head scratcher for ya, brought about by this Rails scenario:
class Product < ActiveRecord::Base
has_many(:prices)
# define private helper methods
end
module PrintProduct
attr_accessor(:isbn)
# override methods in ActiveRecord::Base
end
class Book < Product
include PrintProduct
end
Product is the base class of all products. Books are kept in the products table via STI. The PrintProduct module brings some common behavior and state to descendants of Product. Book is used inside fields_for blocks in views. This works for me, but I found some odd behavior:
After form submission, inside my controller, if I call a method on a book that is defined in PrintProduct, and that method calls a helper method defined in Product, which in turn calls the prices method defined by has_many, I'll get an error complaining that Book#prices is not found.
Why is that? Book is a direct descendant of Product!
More interesting is the following..
As I developed this hierarchy PrintProduct started to become more of an abstract ActiveRecord::Base, so I thought it prudent to redefine everything as such:
class Product < ActiveRecord::Base
end
class PrintProduct < Product
end
class Book < PrintProduct
end
All method definitions, etc. are the same. In this case, however, my web form won't load because the attributes defined by attr_accessor (which are "virtual attributes" referenced by the form but not persisted in the DB) aren't found. I'll get an error saying that there is no method Book#isbn. Why is that?? I can't see a reason why the attr_accessor attributes are not found inside my form's fields_for block when PrintProduct is a class, but they are found when PrintProduct is a Module.
Any insight would be appreciated. I'm dying to know why these errors are occurring!
You might have better luck delaying the attr_accessor call in PrintProduct until mixin-time:
module PrintProduct
def self.included(base)
base.attr_accessor :isbn
end
# other instance methods here
end
The problem is likely something to do with timing of the attr_accessor call and how that applies to modules mixed in. I'm not certain that the timing is defined by the Ruby spec, so it might vary betweeen implementations or versions.