How can I create a concern in a Rails engine? - ruby-on-rails-3

I'm trying to create a concern inside an engine in order to add/override this functions in the main application which is going to mount this engine. The problem is that I have had problems including the concern in the engine module. It seems that Rails can't find it.
This is my post.rb file in app/models/blorgh/post.rb:
module Blorgh
class Post < ActiveRecord::Base
include Blorgh::Concerns::Models::Post
end
end
And this is my post.rb concern in lib/concerns/models/post.rb:
require 'active_support/concern'
module Concerns::Models::Post
extend ActiveSupport::Concern
# 'included do' causes the included code to be evaluated in the
# conext where it is included (post.rb), rather than be
# executed in the module's context (blorgh/concerns/models/post).
included do
attr_accessible :author_name, :title, :text
attr_accessor :author_name
belongs_to :author, class_name: Blorgh.user_class
has_many :comments
before_save :set_author
private
def set_author
self.author = User.find_or_create_by_name(author_name)
end
end
def summary
"#{title}"
end
module ClassMethods
def some_class_method
'some class method string'
end
end
end
When I run the test/dummy, I got the this error: uninitialized constant Blorgh::Concerns
This is my blorgh.gemspec:
$:.push File.expand_path("../lib", __FILE__)
# Maintain your gem's version:
require "blorgh/version"
# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
s.name = "blorgh"
s.version = Blorgh::VERSION
s.authors = ["***"]
s.email = ["***"]
s.homepage = "***"
s.summary = "Engine test."
s.description = "Description of Blorgh."
s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.rdoc"]
s.test_files = Dir["test/**/*"]
s.add_dependency "rails", "~> 3.2.8"
s.add_dependency "jquery-rails"
s.add_development_dependency "sqlite3"
end
Can someone help me with this?

When using engines, you need to keep track of the load order, especially when changing behavior of the main app. Suppose your engine is called "engine_name", you should have this file: engine_name/lib/engine_name/engine.rb. This is one place to include your Concerns.
Bundler.require
module EngineName
class Engine < ::Rails::Engine
require 'engine_name/path/to/concerns/models/post'
initializer 'engine_name.include_concerns' do
ActionDispatch::Reloader.to_prepare do
Blorgh::Post.send(:include, Concerns::Models::Post)
end
end
# Autoload from lib directory
config.autoload_paths << File.expand_path('../../', __FILE__)
isolate_namespace EngineName
end
end
This way you make sure everything is loaded alright, but be very careful using Concerns and maybe reconsider using dependency injection by refactoring Blorgh::Post to deal with different configurations.

This is happening because in Rails 3 the lib directory is not being automatically looked at to find classes. Your can either update config.autoload_paths to add the lib directory to it in the engine or you can move concerns/models/post.rb out of the lib directory and into app/models where it will be automatically found.

Related

Create module that throws exception in rails if conditions not met on load

I have a module in a rails 3 app that's included in multiple models. I want to force the app to fail to load if any of the models do not have an appropriate attr_accessor defined. Due to the architecture of the app it's not feasible to include the attr_accessors as a part of the module to be added to the class during the self.included function.
Here's what I've tried at present:
module MyModule
def included(base)
raise "Accessor 'enabled' is not defined in #{base.name}" unless base.method_defined? :enabled
end
end
class MyModel < ActiveModel::Base
# attr_accessor :enabled
attr_accessor :attr_1, :attr_2
include MyModule
end
In this instance, I'd want the app to fail to load because MyModel does not have an enabled method.
Your code should work - on production. In development the classes in app/models are only loaded if used unless you set
config.cache_classes = true
in config/environments/development.rb. But that makes developing really no fun, since you have to restart your webserver every change to test your changes. On rails 4 you have another configuration option
config.eager_load = true
So you do not need to cache classes to get the behaviour to have loaded every class on starting your server / console.
If it's really only attribute accessor you check, it's better just to set it on including the module. And if you work with modules in rails: have a look into http://www.rubydoc.info/docs/rails/3.2.8/ActiveSupport/Concern
There you can just call attr_accessor :enabled in the included block.

rails3 monkey-patch generator

I want to monkey-patch the rspec-rails generator to generates a _form.html_spec.rb template. I know how do that, but not where I should put this patch!
What I've done:
# rspec_generator_patch.rb
module Rspec
module Generators
class ScaffoldGenerator < Base
def generate_views_specs_with_form *args, &block
generate_views_specs_without_form
copy_view :_form
end
alias_method_chain :generate_views_specs, :form
end
end
end
Where I put this file? Thank you.
EDIT:
Ok, I think problem is almost solved. Instead of monkey-patch, I've inherited the specific generators and edited the method. There's the solution:
# lib/generators/rspec_modded/scaffold/scaffold_generator.rb
require 'generators/rspec/scaffold/scaffold_generator.rb'
module RspecModded
module Generators
class ScaffoldGenerator < Rspec::Generators::ScaffoldGenerator
def generate_view_specs
super
copy_view :_form
end
end
end
end
If I do rails g rspec_modded:scaffold is in list and actually work if called manually (rails g rspec_modded:scaffold test).
# config/application.rb
# ...
config.generators do |g|
g.test_framework :rspec_modded, :fixture => false, fixture_replacement: nil
g.fallbacks[:rspec_modded] = :rspec
end
For what I know, every hook_for :test_framework should call rspec_modded generator and rspec should manage the rest (fallbacks). But it doesn't work: for some reason the unit_test generator kick in! What the matter? I really don't understand...
You can put your monkey patch anywhere, as long as it gets loaded. With rails, you'd usually put it in the /lib folder.

How to print the filename passed to `require`?

When starting the Rails 3.x server (rails s), is it possible to print the path and name of each file as it is being loaded?
Something like below, but for every file that is required by Rails (think loading of each railtie, for example):
Loading boot.rb...
Loading application.rb...
Loading environment.rb...
Loading development.rb...
Loading routes.rb...
Note that I am able to do the above simply by using puts calls in the files above, but that is not what I am aiming for. Instead I would like to print the file name being required by any code, not just in my application but whenever require is called. For example, printing file names for calls to require being made inside the Rails code.
UPDATE
Thanks #ScottJShea. I used the following code:
In application.rb:
require File.expand_path('../boot', __FILE__)
module Kernel
def require_and_print(string)
puts string
require_original(string)
end
alias_method :require_original, :require
alias_method :require, :require_and_print
end
....
Looks like you would need to edit kernel.rb. Here is an SO Post about it where the responder suggests this (although you may ant to limit it to the require method):
module Kernel
def load_and_print(string)
$:.each do |p|
if File.exists? File.join(p, string)
puts File.join(p, string)
break
end
end
load_original(string)
end
alias_method :load_original, :load
alias_method :load, :load_and_print
end

Dynamic define_method throwing error in RSpec

I am pretty sure I am missing a basic mistake here, so I am hoping another set of eyes might help. I am using Rails 3, Ruby 1.9.2 and Rspec 2.
I would like to define dynamic class methods on a model so that I can return base roles for an assignable object (such as account) as they are added to the system. For example:
BaseRole.creator_for_account
Everything works fine via the console:
ruby-1.9.2-p180 :003 > BaseRole.respond_to?(:creator_for_account)
=> true
but when I run my specs for any of class methods, I get a NoMethodError wherever I call the method in the spec. I am assuming that something about how I am dynamically declaring the methods is not jiving with RSpec but I cannot seem to figure out why.
The lib dir is autoloaded path and the methods return true for respond_to?.
# /lib/assignable_base_role.rb
module AssignableBaseRole
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
BaseRole.all.each do |base_role|
role_type = RoleType.find(base_role.role_type_id)
assignable_name = base_role.assignable_type.downcase
method = "#{role_type.name}_for_#{assignable_name}"
define_method(method) do
self.where(:role_type_id => role_type.id,
:assignable_type => assignable_name).first
end
end
end
end
Then include the Module in BaseRole
# /models/base_role.rb
class BaseRole < ActiveRecord::Base
include AssignableBaseRole
belongs_to :role
belongs_to :role_type
......
......
end
Then in my spec:
it "adds correct authority for creator role" do
create_assignment
base_role = BaseRole.creator_for_account # <== NoMethodError here
user1 = Factory.create(:user)
account.users << user1
user1.roles_for_assignable(account).should include(base_role.role)
end
Did you have another class in your project or specs with the same name, but doesn't have the dynamic methods added? I had the exact same problem as you, and renaming one of the classes fixed it.
My guess is the other class is getting loaded first
It appears you are defining these methods based on values in the database:
BaseRole.all.each do |base_role|
.....
Could it be that "creator" doesn't exist in the test database as a role type, or "account" doesn't exist as assignable_type?
Presumably you are testing this in the console for development, not test, so the data could be mismatched. Might need to set up the data in a before hook.

In Sinatra - does anyone use test fixtures? how is your test suite set up?

I'm coming from a Ruby/Rails world. I'm getting testing set up on a Sinatra project (with Rack::Test). I usually use Fixtures in testing. Is there an equivalent for Sinatra?
How do people set up their Sinatra test suites (outside of the basic helloworld example that is the only example I can find for Sinatra tests).
Thanks!
I use Machinist for this (and Rails, also. Hate YAML fixtures.)
ActiveRecord includes support for fixtures, you just have to wire them up in test_helper.rb.
# test/test_helper.rb
require_relative '../app'
require 'minitest/autorun'
require 'active_record'
ActiveRecord::Base.establish_connection(:test)
class ActiveSupport::TestCase
include ActiveRecord::TestFixtures
include ActiveRecord::TestFixtures::ClassMethods
class << self
def fixtures(*fixture_set_names)
self.fixture_path = 'test/fixtures'
super *fixture_set_names
end
end
self.use_transactional_fixtures = true
self.use_instantiated_fixtures = false
end
Then you can use fixtures on your test classes.
# test/unit/blog_test.rb
require_relative '../test_helper'
class BlogTest < ActiveSupport::TestCase
fixtures :blogs
def test_create
blog = Blog.create(:name => "Rob's Writing")
assert_equal "Rob's Writing", blog.name
end
def test_find
blog = Blog.find_by_name("Jimmy's Jottings")
assert_equal "Stuff Jimmy says", blog.tagline
end
end
Configure Rake to look for your tests in the right places.
# Rakefile
require_relative './app'
require 'rake'
require 'rake/testtask'
require 'sinatra/activerecord/rake'
Rake::TestTask.new do |t|
t.pattern = "test/**/*_test.rb"
end
task default: :test
I've posted a small example application to demonstrate using Sinatra, ActiveRecord, and test fixtures.