I've created a gem (TranslationsGem) which I use in multiple projects (an engine and a Rails app). This gem sets up several hashes which are loaded into the I18n backend.
A method #store_dynamic_translations sets up several hashes which are loaded into the I18n backend. It basically works like this:
I18n.backend.store_translations(:en, { test: { property: 'value' } })
My tests confirm the method and translation loading works correctly. I can't however get it to work in the host engine and Rails app.
In my test environment I have to execute the method in my test_helper to ensure the translations are loaded correctly. Outside the test environment I cannot seem to get it working correctly. I can verify that the method is executed, but the translations aren't loaded.
I have tried numerous things for hours, like executing the method in the Engine initializer and using ActiveSupport hooks. In the host Rails app I tried executing the #store_dynamic_translations in an initializer but to no avail.
Oddly enough, if I execute the #store_dynamic_translations in my Rails app controller or view, it works. Is there any way to set this up at app boot time?
EDIT: I've setup an example repository which contains the current setup.
A Gem which dynamically stores translations into the I18n backend.
A Rails Engine which loads the gem and should have its translations available
In the test in question uncommenting the MyI18n::Translations.store_dynamic_translations directive makes the test pass. But it should be possible to do from within an engine initializer I think?
As per Emill Kampp's suggestion, the correct hook was after_initialize. I specified this in engine.rb:
module Blorgh
class Engine < ::Rails::Engine
isolate_namespace Blorgh
config.after_initialize do
MyI18n::Translations.store_dynamic_translations
end
end
end
Related
I am creating two engines. Engine Two builds on Engine One. I am using both of these Engines in an application I'm building. I used the class_eval Engine overriding technique mentioned in the rails guide.
# app/models/active_stix/tool_override.rb
ActiveStix::Tool.class_eval do
# Model override code here
end
The override is required from lib/engine_name/engine.rb as follows
initializer "active_attack.update_stix" do |app|
engine_root = File.expand_path("../../..", __FILE__)
Dir.glob(engine_root + "/app/overrides/**/*_override*.rb").each do |c|
require_dependency(c)
end
end
Everything works as expected in development, but when I run rails zeitwerk:check or try to deploy the application to Heroku, I get the following error
expected file ~/.rvm/gems/ruby-2.7.0/gems/active_attack-0.1.14/app/overrides/models/active_stix/tool_override.rb to define constant Models::ActiveStix::ToolOverride
This seems to be related to Rails issue 36100, but that issue seems to describe how the main Rails app can override Engine models. It doesn't seem to work for one Engine overriding another Engine's models.
Answered in Github Issue
Rails.autoloaders.main.ignore(absolute/path/to/app/overrides) ignores the overrides directory.
config.autoloader = :classic uses previous Rails autoloader instead of zeitwerk.
I created an engine which provides an ui component as a cell. The corresponding gem (criteria_operator-ui_component) contains nearly no code inside the lib folder, because for cells to function properly I had to work inside the assets path. The base file of the gem looks like this:
require 'criteria_operator/ui_component/engine'
require 'cells/rails'
module CriteriaOperator
module UiComponent
# Your code goes here...
end
end
The engine doesn't contain much, either:
module CriteriaOperator
module UiComponent
class Engine < ::Rails::Engine
require 'jquery-rails'
require 'criteria_operator'
isolate_namespace CriteriaOperator::UiComponent
end
end
end
To me, it looks like the gem couldn't even know about the cell, but as far as I know I'm not allowed to include anything from outside the lib folder. Also, testing the cell in the dummy application within the project is working fine.
Now I'm using this engine inside a real Rails application. In the gemfile, I included the following:
gem 'criteria_operator'
gem 'cells' # i added these three, because `bundler list` didn't show me
gem 'cells-rails' # `cells-rails` and `cells-erb` even though they are listed
gem 'cells-erb' # as dependencies for the engine
gem 'criteria_operator-ui_component'
I mounted the routes
mount CriteriaOperator::UiComponent::Engine => '/criteria_operator-ui_component'
and tried using the cell CriteriaOperator::UiComponent::CriteriaEditor like I did in the dummy application. Inside erb:
cell('criteria_operator/ui_component/criteria_editor', #op)
or from code:
include Cell::RailsExtensions::ActionController
def whatever
cell(CriteriaOperator::UiComponent::CriteriaEditor, #op).call()
end
The error is ActionView::Template::Error (uninitialized constant CriteriaOperator::UiComponent::CriteriaEditor).
What am I doing wrong? Am I just missing something when using the engine, or is the engine itself implemented the wrong way? And if that's the case, why does the dummy application work? I'm totally stuck, this is my first time creating a Rails Engine as well as my fist time working with cells...
The full code of the engine (including the dummy application) can be found on GitHub (this isn't supposed to be any advertisement, it's just in case anyone needs additional information).
You're calling CriteriaOperator::UiComponent::CriteriaEditor but that class/module does not seem to exist.
CriteriaOperator::UiComponent::Engine works OK because it's defined in the engine itself.
I'm guessing that your sample application works because it's using the view-based invocation like cell('criteria_operator/ui_component/criteria_editor') which presumably works with the javascript? You can't use the "code" version without defining the cell as a class like this:
https://github.com/trailblazer/cells#cell-class
I want to share a model between 2 (maybe more in the future) of my rails apps. I couldn't find any clear suggestions, but I picked up some of the questions and answers I've read and came to a conclusion that it has to be done with a "gemmed" plugin engine.
I decide to go with an plugin, because I read that engine is simply a kind of a "full" plugin.
So I created a plugin using: rails plugin new my_models --skip-active-record --skip-test-unit --dummy-path=spec/dummy (the options are for skipping activerecord as an ORM and using rspec for testing).
After I created the plugin, I got the following files:
my_models.gemspec Gemfile Gemfile.lock lib MIT-LICENSE Rakefile README.rdoc spec
I tried to include the model using the following methods:
Just creating an app/models directory and put my model inside
As suggested in this tutorial (and I could see in devise's github), I created a generator in an attempt to generate the model.
Both of them failed, and then I decided to go with the engine suggestion (by just adding --mountable to the options list of the "rails new" command), I got the full rails app structure (with app, bin, db and the rest of the directories), put my model in the app/models dir and it worked like a magic!
As I believe I'm a programmer and not I magician, I don't to do such magics, so can you tell me what's wrong with both of my thin plugin solutions (using generator/creating a model)?? Moreover, what are the advantages of using those generators?
I'm attaching my generator's code, maybe I miss something:
require 'rails/generators/named_base'
require 'mongoid'
module Mongoid
module AttackGenerator
def generate_model
invoke "mongoid:model", [name] unless model_exists? && behavior == :invoke
end
def inject_field_types
inject_into_file model_path, migration_data, after: "include Mongoid::Document\n" if model_exists?
end
def migration_data
field :link_url, type: String
field :token, type: String
end
def model_exists?
File.exists?(File.join(destination_root, model_path))
end
def model_path
#model_path ||= File.join("app", "models", "#{file_path}.rb")
end
end
end
An engine (very good guide) is basically a small Rails app: has controllers, can inject into your rails code in various ways (sharing classes/controllers and such) and most important, can use your main Rails application code transparently.
After thinking a bit about it, I believe you need an engine, the reason is, a simple model still require migrations. Notice that an engine is basically a gem plus some few additions provided by rails.
Although miguiding (plugins in rails are not used anymore), the command rails plugin new blorgh --mountable creates a gem which is a rails engine.
How do you understand if a gem is a rails engine? By the file engine.rb (can be named differently, the contents are the important stuff).
module Blorgh
class Engine < ::Rails::Engine
isolate_namespace Blorgh
end
end
The other important thing you should be aware of, is that rails (actually Bundler.require) auto requires one file for you when you add your custom gem to your gemfile: the file named lib/yourgemname.rb, in this case, lib/blorgh.rb. That's your entry point.
Aside from that, all the other things (gemspec and all the other files) are things created for rubygems. The important part is that you use .gemspec file as your gemfile, just add gems using add_dependency instead of the standard Gemfile syntax. If you want (and you should) learn more about ruby gems, this article is really good
The app directory is autoloaded like rails does, so adding app/models/yourmodel.rb is a good way to have your model autoloaded and shared with all your apps.
The last important thing, are migrations. You have to remember to run your_engine_name:install:migrations in your Rails app to copy your migrations from your engine to your rails app and run them (otherwise you can see the suggestions on this article)
And you are ready. One note! To install your gem you have two options: use your remote git repository (best option) or for local development you can use :path, here two examples that can be added to your Rails apps gemfiles:
# Use this only for development purposes
gem 'yourgem', '1.0.0', path: 'your/local/path/to/gem'
# Use this for deploy and such, you'll need access to that repository
gem 'yourgem', '1.0.0', git: 'yourgiturl'
This should be enough
I have written a generator which creates the following ruby file and folder:
app/tests/test.rb
in the test.rb file I have a Test class which looks like this:
class Test < MyCustomModule::MyCustomClass::Base
...
end
Now, I want to use its functionality in one of the show.html.erb files creating new instance like this:
Test.new(...).render(...).html_safe
but I am getting the following error:
uninitialized constant MyCustomModule::MyCustomClass::Base
I have use the following answer to link my gem and my rails application. It seems to work as I am able to use the generator, but the gem module and class are not seen in the rails application.
Could anyone tell how to fix this issue?
I have try to follow the tips posted here but still nothing changed:
Adding config.autoload_paths += Dir["#{config.root}/lib/**/"] in application.rb file
I have created my gem structure looking at CarrierWave gem, so the naming should be correct
I try to disable config.threadsafe! but it is already disabled since config.cache_classes and config.eager_load are set to false in development
DEPRECATION WARNING: config.threadsafe! is deprecated. Rails
applications behave by default as thread safe in production as long as
config.cache_classes and config.eager_load are set to true.
Also, looking at adding-asset-to-your-gems rails documentation, it is said that:
A good example of this is the jquery-rails gem which comes with Rails
as the standard JavaScript library gem. This gem contains an engine
class which inherits from Rails::Engine. By doing this, Rails is
informed that the directory for this gem may contain assets and the
app/assets, lib/assets and vendor/assets directories of this engine
are added to the search path of Sprockets.
So, I have done this, and put my model class file in assets folder, but the result is the same.
The following screenshots demonstrate my real case:
The screenshot below displays my gem file structure
Here you can see how I am loading the gem in my Rails application Gemfile:
gem 'thumbnail_hover_effect', '0.0.3', github: 'thumbnail_hover_effec/thumbnail_hover_effec', branch: 'master'
Then I am using the gem generator a ruby file with a cutstom name in app/thumbnails/test.rb folder with the following code:
class Test < ThumbnailHoverEffect::Image::Base
...
end
and trying to use the Test class gives me uninitialized constant ThumbnailHoverEffect::Image::Base error.
Back in the gem files, these are how the thumbnail_hover_effect file looks like
require 'thumbnail_hover_effect/version'
require 'thumbnail_hover_effect/engine'
require 'thumbnail_hover_effect/image'
module ThumbnailHoverEffect
# Your code goes here...
end
and hoe the image file looks like:
module ThumbnailHoverEffect
#
class Image
...
end
end
From what you've posted here there is no ThumbnailHoverEffect::Image::Base defined. Rails autoloading conventions (which you should not be depending on a gem btw, more on that later) would be looking for this file in thumbnail_hover_effect/image/base.rb, but the directory structure you printed does not have that. Of course you could define the class in thumbnail_hover_effect/image.rb and it would work, but the abridged snippet you posted does not show that. So where is ThumbnailHoverEffect::Image::Base defined?
If it's in thumbnail_hover_effect/image/base.rb then that would indicate the file is not being loaded. You can sanity check this by putting a puts 'loading this stupid file' at the top of thumbnail_hover_effect/image/base.rb. That will allow you to bisect the problem by seeing whether there is a problem with your definition of the class, or whether the problem is with loading the proper files. Debugging is all about bisecting the problem.
So there's this great plugin I've gotten used to using in my Rails 2 projects called Bootstrapper. It essentially duplicates the functionality of the seeds.rb file, but I like it because it lets you break up your bootstrap process into concise chunks.
Anyway, I've gone so far as to fork the project and attempt to turn it into a Rails 3 gem. I've been able to get the gem to initialize and register the rake tasks and generators OK. However, I'm running into a problem with the Bootstrapper class itself. It won't load in the Rails project unless it's in a module.
That is, if I place the Bootstrapper class in a file by itself and require that file in my Railtie, then in my Rails app, it can't find the Bootstrapper class. If I put the class in a module and call Bootstrapper::Bootstrapper everything is peachy.
The code that actually requires the Bootstrapper class is this:
ActiveSupport.on_load :active_record do
require 'bootstrapper/bootstrapper'
end
The source is available here:
http://github.com/jrmehle/bootstrapper/tree/make_gem
Autoload paths actually has an annoying feature of following filesystem paths. For example in your lib or extras (depending on what you autoload) you might have the following file structure:
lib/bootstrapper/bootstrapper.rb
# in this case, Bootstrapper::Bootstrapper.class = Class in rails c
# ie: you don't get a NameError exception
More specifically,
lib/bootstrappers/bootstrapper.rb
# Bootstrapper::Bootstrapper => NameError
# Bootstrappers::Bootstrapper => works
If you really want the other way, you can move everything into your lib/bootstrapper.rb source file but meh, I don't like doing that, that's not how gems are organized. In rails3, you'll find the autoloading pretty nice once you use modules everywhere (which can be painful).
Rails3 uses /extras instead of /lib but it's not required, it's just the default (commented out) from rails new. To switch, you just autoload extras instead of lib.