Observe primary application models from within Rails engine observer - ruby-on-rails-4

I have a Rails engine that has no models of its own; just controllers, views, and observers that add functionality to the primary application.
I'm attempting to create an observer but can not get Rails to notice it's existence -- the after_create actions and debug statements in the observer are ignored and inserting syntax errors into the file does not raise an error on startup or when insert a row in the observed table.
I've tried all of the techniques mentioned here with no effect.
#/engines/loansengine/lib/loansengine/engine.rb
module Loansengine
class Engine < ::Rails::Engine
isolate_namespace Loansengine
# OBSERVERS
config.active_record.observers = ['Loansengine::TourObserver']
end
end
Observer:
#/engines/loansengine/observers/loansengine/tour_observer.rb
class Loansengine::TourObserver < ActiveRecord::Observer
observe :tours
def after_create(tour)
test_observer(tour)
end
private
def test_observer(tour)
tour.agent_comments = 'pink'
tour.save
end
end

Think I've figured this out:
module Loansengine
class Engine < ::Rails::Engine
isolate_namespace Loansengine
config.before_initialize do
config.active_record.observers << 'Loansengine::TourObserver'
end
end
end

Related

How to structure namespaced modules

I am having trouble with Ruby class (constant) lookup within the context of a Rails engine gem.
I have a gem MyGem that is a Rails engine. It defines non-namespaced models that are expected to be overridden by the MainApp that would include the gem and namespaced modules, which are included in gem's and main_app's models to have a DRY way of defining reusable code.
Here is a sample code structure:
Two models
# in /app/models/user.rb
class User < ActiveRecord::Base
include MyGem::User::CommonExt
end
# in /app/models/comment.rb
class Comment < ActiveRecord::Base
include MyGem::Comment::CommonExt
end
Their two modules
# in /app/models/concerns/my_gem/user/common_ext.rb
module MyGem::User::CommonExt
def load_comment(id)
return Comment.find(id)
end
end
# in /app/models/concerns/my_gem/comment/common_ext.rb
module MyGem::Comment::CommonExt
def load_user(id)
return User.find(id)
end
end
Now, if I call
User.new.load_comment(1)
I get undefined method #find for MyGem::Comment::Module
I think I understand why this is happening - in the context of #load_comment definition, which is namespaced under MyGem, Comment constant lookup returns MyGem::Comment, rather than the more distant ::Comment
I would prefer not to have to prepend every model instance with ::.
Is there a file structure, model/class definition or configuration change I could use to make a call to Comment return the model Comment, not the MyGem::Comment module?
I would use inheritance instead of mixin in this case.
So in your gem/engine you could define your common class similar to this:
module MyGem
module Common
class Base < ActiveRecord::Base
# common functionality goes here
def load(record_type, id)
record_type.find(id)
end
end
end
end
Then in your main_app code:
class User < MyGem::Common::Base
...
end
Now you could do this:
User.new.load(Comment, 1)
This violates the Law of Demeter but hopefully it illustrates the point.
Doing it like this is DRY and has the added benefit that it prevents your gem from having to know about classes which are outside it's own scope.

Rails joining multiple methods and scopes

My rails app provides a list of services which are defined like this:
service.rb
has_and_belongs_to_many :clients
has_many :translations, class_name: ServiceTranslation, dependent: :destroy
def with_translation(lang)
includes(:translations)
.where("service_translations.language_id=?", lang.id)
.references(:service_translations)
end
scope :with_clients, -> { select("services.*, count(clients_services.service_id) as clients_count")
.joins(:clients).group('services.id').order('clients_count desc') }
So the with_translation method is eager loading the translations for the service (name, content etc) and the scope with_clients is making sure that the services are ordered by the number of linked clients (also, if no client is linked to a service, the service is not showing)
Both methods are working when called separately in my services_controller:
services_controller.rb
class ServicesController < ApplicationController
def index
##services = Service.with_clients.page(params[:page])
#or
#services = Service.with_translation(#lang).page(params[:page])
end
end
All is good and dandy, but I need both of those methods working together, so when I do this:
#services = Service.with_clients.with_translation(#lang).page(params[:page])
I get this error message:
PG::Error: ERROR: column "clients_count" does not exist
LINE 1: SELECT DISTINCT "services"."id", clients_count AS alias_0 F...
I tried merging the two methods, creating the whole query in the controller, both in vain...any help would be appreciated!
The error suggest the output query have clients_count as a column but it is just an alias for COUNT.
Try running scopes in rails console and analysing the queries in the log one by one:
you say this works
Service.with_clients
first make sure two scopes combined without pagination work correctly
Service.with_clients.with_translation(#lang)
The resulting query should be like this:
SELECT DISTINCT "services"."id", count(clients_services.service_id) AS clients_count FROM "services"
INNER JOIN "clients_services" ON "clients_services"."service_id" = "services"."id"
INNER JOIN "clients" ON "clients"."id" = "clients_services"."client_id"
LEFT OUTER JOIN "service_translations" ON "service_translations"."service_id" = "services"."id"
WHERE (service_translations.language_id=2)
GROUP BY services.id
ORDER BY clients_count desc

pundit policies with namespaces

I have Question model in my application.
app/models/question.rb
class Question < ActiveRecord::Base
...
end
I'm using 'pundit' gem for authorization. There are two controllers to do some changes in questions: one for registered user, one for admin.
I'm trying to create separate policies for controllers.
app/controllers/questions_controller.rb
class QuestionsController < ApplicationController
...
end
app/policies/question_policy.rb
class QuestionPolicy < ApplicationPolicy
...
end
app/controllers/admin/questions_controller.rb
class Admin::QuestionsController < Admin::ApplicationController
...
end
app/policies/admin/question_policy.rb
class Admin::QuestionPolicy < Admin::ApplicationPolicy
...
end
When I'm trying to use 'authorize' method in Admin::QuestionsController it uses app/policies/question_policy.rb class not from admin folder.
Gem's documentation says that is should work like I described above (https://github.com/elabs/pundit#namespaced-policies).
Can somebody help me with that?
I was trying to get separated policies for the main app and the ActiveAdmin and ended up with a working solution by creating a customized PunditAdapter to be used in config/initializers/active_admin.rb
class NamespacedPunditAdapter < ActiveAdmin::PunditAdapter
def get_policy(subject, user, resource)
"ActiveAdmin::#{subject}Policy".constantize.new(user, resource)
end
def retrieve_policy(subject)
case subject
when nil then get_policy(subject, user, resource)
when Class then get_policy(subject, user, subject.new)
else
if subject.class.to_s.split('::')[0] == 'ActiveAdmin'
Pundit.policy!(user, subject)
else
get_policy(subject.class, user, subject)
end
end
end
def scope_collection(collection, _action = Auth::READ)
return collection if collection.class != Class
scope = "ActiveAdmin::#{collection}Policy::Scope".constantize
scope.new(user, collection).resolve
rescue Pundit::NotDefinedError => e
if default_policy_class && default_policy_class.const_defined?(:Scope)
default_policy_class::Scope.new(user, collection).resolve
else
raise e
end
end
end
Another option would be to use an ActiveSupport::Concern as pointed out here
I've created issue in github source code and it was closed with such explanation:
The docs refer to the currently unreleased master branch. You can use it by referring to the github source in your Gemfile.
# Gemfile
gem 'pundit', github: 'elabs/pundit'
A bundle install later your code should work.
You can switch back to a released version on Rubygems as soon as 0.3.0 is out. We're still discussing a few namespacing issues, but it will come soon.
If anyone is still looking for this functionality, I needed it as well for splitting up authorizations between ActiveAdmin and my end-user facing site. I built a Pundit compatible gem for controller-based namespaced authorizations (your policies will work), and I plan to follow any features released for pundit. It also includes an ActiveAdmin adapter.

Minitest - test class in Rails 4

I am trying to use Minitest in a fresh Rails 4 install. My understanding is that if I have a class that doesn't inherit from ActiveRecord then I should be able to use Minitest itself, without Rails integration:
#test/models/blog.rb
require "minitest/autorun"
class Blog < Minitest::Unit::TestCase
def setup
#b = Blog.new
end
def test_entries
assert_empty "message", #b.entries
end
#app/models/blog.rb
class Blog
attr_reader :entries
def initialize
#entries = []
end
I run the test with ruby test/models/blog.rb.
My problem comes with the setup method. If I don't include an entry for my blog, the tests fails with the message that there are the wrong number of arguments in setup. If I include an entry in my setup message #b = Blog.new entries: "Twilight", my test fails in the test_entries method because entries is an undefined method.
You have a couple problems. First, you are not requiring "test_helper", which means that rails isn't getting loaded when you run this test, which means that the mechanism rails uses to resolve missing constants isn't loaded. You will either need to require the helper or require the blog file directly. Second, you are overwriting the constant you want to test with the test, which is why you are getting confusing messages. Name the test class BlogTest instead to avoid this.
This is what I think you are trying to do:
require "minitest/autorun"
require "models/blog" # Assuming "app" is in your load path when running the test
#require "test_helper" # Or require this instead if you need to use DB
class BlogTest < Minitest::Unit::TestCase
def setup
#b = Blog.new
end
def test_entries
assert_empty #b.entries, "Blog entries should be empty"
end
end

Rails: Invalid single-table inheritance type error

So, I am working on migrating this php site with an existing database which I cannot change over to Rails. There is a table: Quotes with a column named type. Whenever I try and create a model of this and set the type, it tells me the following error:
ActiveRecord::SubclassNotFound (Invalid single-table inheritance type: HOME is not a subclass of Quotes)
I don't understand why it thinks its inheriting because it's not supposed to. My create method looks like this:
quote = Quotes.create(
agent_id: agent.id,
client_id: client.id,
type: 'HOME',
status: 0,
date_created: DateTime.now
)
If I comment out the type, everything works fine. But with the Type it errors.
I resolved this by setting the models inheritance_column to nil. Active Record Models can inherit from a table through the attribute :type, setting the inheritance_column to nil removes that attribute allowing you to have a database column named type
class Quote < ActiveRecord::Base
self.inheritance_column = nil
end
I hate having potential gotchas deep in the code especially in the intial processes like generating a model. Better to just change the reserved word to something else and free yourself up to take advantage of inheritance column later if the need comes up. A cleaner solution is listed here -> rename a database column name using migration
It reads;
Execute $> rails generate migration ChangeColumnName
where, ChangeColumnName is the name of our migration. This can be any name.
Now, edit the generated migration file at db/migrate/_change_column_name.rb
class ChangeColumnName < ActiveRecord::Migration
def change
rename_column :table_name, :old_column, :new_column
end
end
$> rake db:migrate
You will have to edit controller and view files e.g. if the model name is Product then you will likely edit these files
/app/views/products/_form.html.erb
/app/views/products/show.html.erb
/app/controllers/products_controller.erb
/app/views/products/index.html.erb