How to access rails methods in a rails plugin - ruby-on-rails

I followed this tutorial to create a rails plugin.
Section 3 explains how to add a method to string that is available anywhere in a Rails application. Now for my case, I want to be able to access rails methods in the plugin itself.
For example in lib/plugin/my_plugin/my_plugin.rb I want to use:
Rails.application.eager_load!
But every time running the gem with an executable it throws following error:
undefined method `eager_load!' for nil:NilClass (NoMethodError)
Now I know this error is thrown because there's no Rails application, but I added the plugin to a Rails application and it also doesn't seem to work (same error) and can't find another way to get this working. Am I approaching this problem the wrong way or is there even a better way?
lib/plugin/my_plugin/my_plugin.rb:
require 'rails'
class MyPlugin
def fetch_models
arr_models = []
Rails.application.eager_load!
::ApplicationRecord.descendants.each do |model|
arr_models[] << model
end
end
end
Steps reproduce:
Create a new plugin rails plugin new custom
Add lib/custom/generator.rb
lib/custom/generator.rb:
require 'faker'
require 'rails'
class Generator
def fetch_models
arr_models = []
Rails.application.eager_load!
ApplicationRecord.descendants.each do |model|
arr_models[] << model
end
end
end
Edit lib/custom.rb
lib/custom.rb:
require "custom/generator"
require "custom/version"
require "custom/railtie"
module Custom
def self.generate
generator = Generator.new
models = generator.fetch_models
end
end
Add executable in bin/
bin/custom:
#!/usr/bin/env ruby
require 'custom'
Custom.generate
Edit custom.gemspec so that bin/test passes
Execute following commands:
$ bundle exec rake build
$ bundle exec rake install

Plugins usually are built to extend core classes.
Using the generator and following the linked guide at my_plugin/lib/my_plugin/rails_core_ext.rb you have to use class you want to extend.
Your code become
class RailsPlayground::Application
def fetch_models
arr_models = []
Rails.application.eager_load!
ApplicationRecord.descendants.each do |model|
arr_models[] << model
end
end
end
or
class RailsPlayground::Application
def fetch_models
Rails.application.eager_load!
ApplicationRecord.descendants
end
end

Related

Writing macro for activerecord

I'm trying to write a new macro for my ActiveRecord model (User). Macro will be defined in my own gem which called Kart.
Below is my implementation for my gem Kart and User
kart.rb
require "kart/version"
module Kart
extend ActiveSupport::Concern
included do
def goodbye
p "Gooooood"
end
end
end
ActiveSupport.on_load :active_record do
ActiveRecord::Base.send :include, Kart
end
user.rb
class User < ApplicationRecord
goodbye # this is my macro, simply print "Goodbye"
end
For my understanding, when I run "rails c", "Gooooood" will be printed in the console but error message "method_missing': undefined local variable or methodgoodbye' for User" always shows up.
There are 2 main problems that I finally figure out:
I need Railtie to integrate this gem with my rails app. You can find guidance in here: https://api.rubyonrails.org/classes/Rails/Railtie.html
I forgot to rebuild and run bundle update after fixing code in my gem (beginner error but stuck for almost one day)

Create a rails 5 plugin that need migrations

I want to create a rails 5+ gem that must create a migration as it would have to use a model and save/access data in DB.
My gem has some simple classes I stored in lib/ but it must use some that should access DB and this is what I don't know how to do.
Language should update itself in a DB
Languages should list all existing Languages and create a new Language
Basically in a rails app, I whould create the migrations and models, but I couldn't find tutorials about how to do that, and the rails doc did not help me.
Any help is appreciated.
Take a look at Rails Engines.
With an engine you can define generators, models, routes, controllers, etc and hook into a host rails app.
# routes
MyCustomGem::Engine.routes.draw do
resources :languages
end
# controllers
module MyCustomGem
class LanguagesController < ApplicationController
...
end
end
When installing this in a rails app, you'll use
$ bin/rails my_custom_gem:install:migrations
And that will create the migration in the host app.
If you don’t want to copy the migrate file, you can execute migrate directly in the application as follows
module MyEngine
class Engine < ::Rails::Engine
isolate_namespace MyEngine
initializer :append_migrations do |app|
unless app.root.to_s.match root.to_s
config.paths["db/migrate"].expanded.each do |expanded_path|
app.config.paths["db/migrate"] << expanded_path
end
end
end
end
end
When installing this in a rails app
bin/rails db:migrate

Add new migrations from Rails engine gem to app via generator

I'm building a Rails engine in a ruby gem. It includes some migrations right now that are called when you run:
rails g myengine:install
The code in the generator is as follows:
module MyEngine
module Generators
class InstallGenerator < ::Rails::Generators::Base
include Rails::Generators::Migration
source_root File.expand_path('../templates', __FILE__)
# ...
def copy_migrations
migration_template "migrations/migration1.rb", "db/migrate/migration1.rb"
migration_template "migrations/migration2.rb", "db/migrate/migration2.rb"
end
# ...
end
end
end
However, if I run rails g myengine:install again, it fails with this error:
Another migration is already named migration1: /Users/jh/Code/Web/demoapp/db/migrate/20130327222221_migration1.rb
I want it to just silently ignore the fact that there's already a migration and continue on to the next migration. What would be the best way to do this?
EDIT:
Per Dmitry's answer, this was my solution:
def copy_migrations
copy_migration "migration1"
copy_migration "migration2"
end
protected
def copy_migration(filename)
if self.class.migration_exists?("db/migrate", "#{filename}")
say_status("skipped", "Migration #{filename}.rb already exists")
else
migration_template "migrations/#{filename}.rb", "db/migrate/#{filename}.rb"
end
end
Using migration_template in Rails as example, you could perhaps check for destination = self.class.migration_exists?(migration_dir, #migration_file_name) and if migration already exists, skip over making migration_template call.

Can I run a rake task inside a generator?

I have a simple rails install generator for an engine I'm making:
module Bouncer
module Generators
class InstallGenerator < Rails::Generators::Base
source_root File.expand_path("../../templates", __FILE__)
desc "Copies locale file and migrations to your application."
def copy_locale
copy_file "../../../config/locales/en.yml", "config/locales/bouncer.en.yml"
end
def copy_migrations
# I would like to run "rake bouncer_engine:install:migrations" right here
# rather than copy_file "../../../db/migrate/blah.rb", "db/migrate/blah.rb"
end
end
end
end
When a user runs rails g bouncer:install, a locale file is copied into their app. I also want to copy in my migrations, but rather than use copy_file method, I was hoping I could just run rake bouncer_engine:install:migrations inside the generator, like I would do from the command line. How can I do this?
The correct way to do this:
#!/usr/bin/env rake
module Bouncer
module Generators
class InstallGenerator < Rails::Generators::Base
desc "Copies migrations to your application."
def copy_migrations
rake("bouncer_engine:install:migrations")
end
end
end
end
This saves a lot of hassle and even takes care of the making sure each migration's name is timestamped properly.
Well, I think it should be possible by just executing the shell command. Here are 6 different ways to execute a shell command in ruby.
But my other suggestion would be instead of implementing it as a rake task, to direcly implement it as part of your generator... I don't know what your exact demands are, but given your description it seems to me that the migrations-task only runs once, when you execute the install task? Or is there a special need to offer it as a rake task as well?

Customizing Rspec generators in Rails 3

I'm writing a Rails 3.1 engine and testing with RSpec 2. When I use rails generate, I get spec files generated for me automatically, which is so convenient:
$ rails g model Foo
invoke active_record
create db/migrate/20111102042931_create_myengine_foos.rb
create app/models/myengine/foo.rb
invoke rspec
create spec/models/myengine/foo_spec.rb
However, to make the generated specs play nicely with my isolated namespace, I have to wrap the spec each time manually in a module:
require 'spec_helper'
module MyEngine
describe Foo do
it "should be round"
...
end
end
I would love to know if there's a clean and easy way to modify the automatically generated spec 'templates' so that I don't have to wrap the spec in Module MyEngine each time I generate a new model or controller.
You can copy RSpec's templates using a Rake task like:
namespace :spec do
namespace :templates do
# desc "Copy all the templates from rspec to the application directory for customization. Already existing local copies will be overwritten"
task :copy do
generators_lib = File.join(Gem.loaded_specs["rspec-rails"].full_gem_path, "lib/generators")
project_templates = "#{Rails.root}/lib/templates"
default_templates = { "rspec" => %w{controller helper integration mailer model observer scaffold view} }
default_templates.each do |type, names|
local_template_type_dir = File.join(project_templates, type)
FileUtils.mkdir_p local_template_type_dir
names.each do |name|
dst_name = File.join(local_template_type_dir, name)
src_name = File.join(generators_lib, type, name, "templates")
FileUtils.cp_r src_name, dst_name
end
end
end
end
end
Then you can modify the code in #{Rails.root}/lib/templates/rspec/model/model_spec.rb to include the module name.
Copy the '/lib/generators/rspec/scaffold/templates/controller_spec.rb' file from the rspec-rails gem to your 'app/lib/templates/rspec/scaffold' folder, then customize it. Obviously, if you move to a new version of rspec-rails you will want to make sure that your customized template hasn't gotten stale.

Resources