Create a rails 5 plugin that need migrations - ruby-on-rails

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

Related

How to access rails methods in a rails plugin

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

How Do I Create A Custom Rails Generate Model function?

I have a Rails 5 application that has separate migrations for three databases. I'm adding a new database. I normally do a rails g model field1:type1 field2:type2 field3:type.... to create my database tables with the desired fields. I want to create generate model statements where it will generate the model for the desired database and put it in the corresponding db/migrate folder.
I have custom database migration generators for the additional databases in lib/generators. Here is an example of the custom migration generator.
lib/generators/stats_migration_generator.rb
require 'rails/generators/active_record/migration/migration_generator'
class StatsMigrationGenerator < ActiveRecord::Generators::MigrationGenerator
source_root File.join(File.dirname(ActiveRecord::Generators::MigrationGenerator.instance_method(:create_migration_file).source_location.first), "templates")
def create_migration_file
set_local_assigns!
validate_file_name!
migration_template #migration_template, "db_stats/migrate/#{file_name}.rb"
end
end
I assume I need to create lib/generators/mydb_model_generator.rb or whatever the correct filename structure is for each additional database. I'm thinking I could do something like this after looking at the model_generator.rb file in GitHub.
lib/generators/stats_model_generator.rb
require 'rails/generators/active_record/model/model_generator'
class StatsModelGenerator < ActiveRecord::Generators::ModelGenerator
source_root File.join(File.dirname(ActiveRecord::Generators::ModelGenerator.instance_method(:create_migration_file).source_location.first), "templates")
def create_migration_file
set_local_assigns!
validate_file_name!
migration_template #migration_template, "db_stats/migrate/#{file_name}.rb"
end
end
Here are my questions after looking at the git repository for Rails, the Rails Guide regarding creating and customizing Generators and the Ruby on Rails API documentation:
How do I find what I need to require?
How do I find what method I need to override the db/migrate folder name?
How do I find out how to name the rb file for the model generator?
Are my assumptions correct in my attempt to create the model generator?
Well I did not think I would find the solution so quickly. I took another look at model_generator.rb in GitHub and had an aha moment. I realized that I just needed to replace the method in the default generator with my version which points to the db migrate folder I want. Here is the solution I came up with.
require 'rails/generators/active_record/model/model_generator'
class MydbModelGenerator < ActiveRecord::Generators::ModelGenerator
source_root File.join(File.dirname(ActiveRecord::Generators::ModelGenerator.instance_method(:create_migration_file).source_location.first), "templates")
def create_migration_file
return unless options[:migration] && options[:parent].nil?
attributes.each { |a| a.attr_options.delete(:index) if a.reference? && !a.has_index? } if options[:indexes] == false
migration_template "../../migration/templates/create_table_migration.rb", File.join("db_mydb/migrate", "create_#{table_name}.rb")
end
end
Now I can do rails g mydb_model field1:type1 field2:type2 field3:type and it creates the model migration file in the correct migration folder.

How to add optional Rails view helpers to my gem?

I'm working on a gem that does some general string manipulations I'd like to expose as helper methods to rails 4+ apps.
Not all consumers of my gem are rails apps so i'd like to safely expose helper methods to rails apps.
Two questions:
How do I add view helper methods to Rails from a gem and where should it live within the gem directory structure?
What can i do to prevent a blow up when the consumer is NOT a rails app? i.e. the gem can't find rails when it's included
Thanks
In your lib/my_gem.rb, you typically want to do something along these lines:
require 'my_gem/action_methods' if defined? ActionView
And lib/my_gem/action_view_methods.rb would contain all if your methods that require Rails/ActionView.
You can add these helpers to Rails with:
module ActionMethods
# ...
end
ActionView::Base.send :include, ActionMethods
Also see this question, and this one.
The rails way is by creating an engine and as it gets loaded with your gem it gets processed by rails
module MyGem
class Engine < ::Rails::Engine
isolate_namespace MyGem
initializer "my_gem.include_view_helpers" do |app|
ActiveSupport.on_load :action_view do
include MyViewHelper
end
end
end
end
Another way you can go is to not include the helper by default so that consumers don't get unexpected side-effects from your gem. Create the helper as a module and document that it should be added to ApplicationController or any needed controller.

How do I reference Rails in a gem I'm making?

I'm making a gem for Rails. I need access to the ApplicationController because I'll toy with it. Absolutely nothing online gives information about what to do with gemspec and then somehow manage to get Rails accessible in my gem.
I imagine the goal is eventually to be able to talk to Rails like:
module Rails
module ActionController
#code
end
end
If you are developing a gem exclusively for Rails I strongly recommend you generate the initial scaffold using rails plugin new gem_name. There's a ton of info on developing rails plugins.
The initial structure generated looks like this:
gem_name
gem_name.gemspec
lib/
gem_name.rb
gem_name/
version.rb
engine.rb # if generated using --mountable
The whole rails environment becomes available [edit: after your gem is loaded] so extending ApplicationController can be done like this:
# lib/gem_name.rb
require 'gem_name/controller_extensions'
module GemName
end
# lib/gem_name/controller_extensions.rb
module GemName::ControllerExtensions
# bleh
end
# dummy_application/app/application_controller.rb
class ApplicationController < ActionController::Base
include GemName::ControllerExtensions
end
Look at this question.

Rails 3.2 Gemified Engines: Reaching modules inside a mounted gemified engine

I'm working on an infrastructure for Rails application and I'm trying to take something out of someones existing project.
I am sort of new to rails but I read the guides on plugins and engines ect..
So I have a gemified Engine, containing some module. I have a model say SharedPost trying to extend said module and I'm getting the uninitialized constant error
uninitialized constant Stake::SharedPost
Here's some of what my engine looks like:
#file: lib/stake/shared_post.rb
module Stake
module SharedPost
...
end
end
#file: lib/stake/engine.rb
module Stake
class Engine < ::Rails::Engine
isolate_namespace Stake
end
end
And in the main app I've got
#file: Gemfile
...
gem 'stake'
...
#file: config/routes.rb
Pop::Application.routes.draw do
root :to => 'home#index'
mount Stake::Engine, :at => '/stake'
end
#file: app/models/posted.rb
class Posted < ActiveRecord::Base
extend Stake::SharedPost
...
end
end
The main application will load, though with no available data on it.
While I try to run
rake db:seed
for example when trying to load the Posted model I get the error uninitialized constant Stake::SharedPost
What am I missing to get access to my gem's namespaced modules?
EDIT:
I've read into the acts_as pattern and that doesn't seem to be the cleanest way of doing things, plus I'm not sure how to implement that onto my engine. Is there another solution?
In lib/stake.rb are you including the lib/stake/shared_post.rb file?
It should look something like this:
# file lib/stake.rb
require "stake/shared_post.rb"
module stake
....
end

Resources