Problems with mountable engine's application controller - ruby

I have a rails app called X and a rails mountable engine called Y.
App X has Devise and engine Y has CanCan which works with the hosts apps devise installation. In order to seperate the concerns of host app and mountable engine, the engine has a CanCan ability model as well as the host app.
My problem is the following, as per CanCan documentation you can change the default ability model like so https://github.com/ryanb/cancan/wiki/Changing-Defaults.
My problem is that if I setup the of default ability class in the mountable engines application controller like so:
module Y
class ApplicationController < ActionController::Base
def current_ability
#current_ability = Ability.new(current_user)
end
end
end
I get the following error:
uninitialized constant Ability
(On a side note, when I edit the file and reload the page without restarting the server it works fine)
If however I put the same method into the host app's application controller:
class ApplicationController < ActionController::Base
def current_ability
#current_ability = Y::Ability.new(current_user)
end
end
Everything works as expected (which also means that Devise and CanCan are setup correctly).
Anyone got any ideas of why this is happening? I might not understand mountable engines correctly however I always thought that they are isolated from the host app, thus when accessing a page defined by the mountable engine the host app's application controller shouldn't even be executed.

it is most likely that your engines application controller is not loaded at all. Rails engines behave bad if you have same class name in the main app. here is the issue if you want to look at. I would recommend using the explicit declaration in your engine. Use Y::Ability instead of Ability.

Related

Rails mount engine while allowing to subclass any engine class

I'm building a platform in Rails that will act as a basis for a few other Rails applications. Ideally I would like this to be a shared engine, on which the other concrete applications can build.
It would be nice if the applications could extend the base classes that the engine provides. AFAIK this can be done using monkey patching, but that feels 'hacky' to me.
I stumbled onto what looked like the solution, which was to just create 'mirror' classes in the main Rails app, which extend those of the engine:
# SharedEngine/models/shared_engine/post.rb
module SharedEngine
class Post < ActiveRecord::Base
def hello
"Hello"
end
end
end
# App/models/shared_engine/post.rb
require SharedEngine::Engine.root.join('app', 'models', 'shared_engine', 'post')
class Post < SharedEngine::Post
def hello
super + " world"
end
end
However, it looks like there are some autoloading problems. After server startup, it prints "Hello". Then after I save the app model, it says "Hello world".
The Rails engine guide suggests putting shared code into concerns. Is there any other way to get this working cleanly without having to create concerns for every class?
Since you need to create a basis for a few Rails apps to reduce boilerplate i suggest having a look a Rails Application Templates. Using templates you may provide all the necessary scaffolding without monkey-patching any class.

Models on the Rails Console

I have created a simple class under the models folder like so:
class CaffeineShops
def initialize
end
end
When I run rails console and try CaffeineShops.new(), I get a message: "NoMethodError: undefined method 'new' for CaffeineShops::Module"
I am using Rails 4.1.
Any ideas why I am getting that error?
You cannot name a model the same name as the application itself. When you run rails g caffeine_shops this creates a module named CaffeineShops which will power your application.
The module is utilized in many files including
config\application.rb
config\environment.rb
config\environments\development.rb
config\environments\production.rb
config\environments\test.rb
config\initializers\secret_token.rb
config\initializers\session_store.rb
config\routes.rb
config.ru *actually starts the application on Rack-based servers
Rakefile *loads rake tasks for the application
When you then try to name a class the same name it creates ambiguity in the rails application so your code is assuming you mean the module CaffeineShops not the class CaffeineShops.
If you were to actually use a generator to define this model rails would make it very clear there was a problem with this e.g.
rails g caffeine_shops
cd caffeine_shops
rails g model caffeine_shops
#=>The name 'CaffeineShops' is either already used in your application or reserved
by Ruby on Rails. Please choose an alternative and run this generator again.

Sinatra settings returning strange result

I have a very simple Sinatra application:
require 'sinatra'
get '/' do
settings.inspect
end
And when I go to the root path I get: Sinatra::Application which is not at all what I expected. The application also doesn't respond well when I call the development? method (NoMethodError). I get the feeling this is because of my environment. I'm running Sinatra 1.4.4 with Ruby 1.9.3 on Windows 8. Any ideas on how to figure this out?
This is right, it’s just the way Sinatra works.
The settings instance method calls the class method (with self.class.settings) and the class method simply returns self, which is Sinatra::Application in the case of classic apps, and whatever your app class is in the case of modular apps.
When you add a setting in Sinatra with set, a new method is created on the app class for it, rather than there being a separate settings object. There is no way, as far as I know, to iterate over just the settings of your app.

Authorisation for Sinatra via CanCan

I am trying to get some auth mechanism working for my webapp (written in Sinatra). Currently I am playing around with sinatra-can which looks great. The issue I now face is that I need access to the specific model from the can method. Lets say I have a route that looks like that:
class ProjMgmt < Sinatra::Base
get '/edit/:project' do
project = Project.where(name: param[:project]).first
authorize! :edit, project
project.to_html
end
end
There are two models defined, Project and Manager. They are stored in a MongoDB (via mongoid, NO datamapper, ActiveRecord or so) and have a has_and_belongs_to_many relation, eg. the relations can be accessed via project.managers or manager.projects.
Now, only managers that have a relation to the particular project should be able to edit the project. What I want to have is something like that on authorize!:
class Ability
include CanCan::Ability
def initialize(user)
can :edit, project if project.managers.include? user
end
end
Obviously, that does not work since Ability does not know about any project.
Is there any nice approach to this? Must not necessarily be CanCan...
Try like this
def initialize(user)
can :edit, Project do |project|
project.managers.include? user
end
end

Using routes from one sinatra app from another when racked up

In the real world, thanks to Rack::Builder, I've started up two sinatra apps and mapped one to "/api/v1" and the other to "/ui".
I'd like the app mapped to /ui to be able to take advantage of the routes in /api but,
becuase they're separate, the ui app can't perform calls on the api side of things.
Is there a way to call one app's route from another via rack, or should I just use Net::HTTP ?
Here's a simplified example of what I'm trying to do:
#/usr/bin/ruby
require 'sinatra'
class API < Sinatra::Base
get '/accounts/' do
'{"json":"account_data"}'
end
end
class UI < Sinatra::Base
get '/accounts/' do
# How do I get /api/accounts?
# call "/api/accounts" obviously does not work
# would use erb here to render accounts list in human readable form
end
end
rack = Rack::Builder.new
rack.map "/ui" do
run UI.new
end
rack.map "/api" do
run API.new
end
Rack::Server.start :app => rack
exit
Thanks very much!
If these apps are racked up in Rack together, they have no real connectivity. What Rack does call .call(env) on whatever app you have racked up and that responds with [status,env,body] You can have layers in the middle that act on and modify env. Your Rack is triggering #call on whatever app you mapped to respond to that path. The called app has no concept of what is running in Rack. Nor can it call another app unless there is a handle for it in env that was populated upstream.
But you have to consider why you decided to make these 2 different apps in the first place. I would imagine it is due to the fact that there could be multiple apps using the API. Or you enjoy the decoupled nature. So in that case you have to think if you would want some coupling through Rack at all. What if the API was on another server? Any internal link between the 2 through Rack would break the UI application. If you always know your API will be self contained on the same machine you could just as well make it a library and not do HTTP calls.
So even if it is possible to call routes from a co racked up app I would never do it. The whole reason for having API servers is to have a decoupled layer. And you should access that layer via HTTP so it would work if it were on same box or across the world.

Resources