Use external gem for Rails models - ruby-on-rails

I'm currently developing three Rails applications that are going to use the same database and and the same models. In order to keep them in sync, I want to move these models into an independent gem.
I've read the RubyGems guide for creating my own gem but I'm not sure of how some things should be handled (e.g. database credentials (database.yml), tests for the models, migrations...)
Any thoughts on best practices or pointers to existing gems providing this behavior?

The migrations go in your gem.
You can cause them to be included in your containing app by putting this in your gem's engine.rb file (when using Rails::Engine):
'lib/your_gem/engine.rb'
module YourGem
class Engine < ::Rails::Engine
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
You don't have to use Rails::Engine (in fact, it comes with some unneeded overhead). But, if you're doing this for the first time, it may be the easiest thing to do.
schema.rb will be auto-generated in your containing app's db directory as is normally the case when you run rake db:migrate.

Related

Want to develop a rubygem with ActiveRecord models

I want to develop a rubygem which is intended to be installed in a rails application.
The gem will contain few models and their database migrations.
Also I would like to add tests asserting the models and their relationships. I prefer RSpec for doing that.
While I was about to start I got stuck with a question that how to use ActivRecord in a gem so that using the tests I can insert the fixture data and test the relationships and behaviour.I believe SQLite should prove to be the best option for database here.
Note: I haven't developed any Rubygem before and this will be the first one I am attempting. Thus any help will be highly appreciated to guide me in the right direction.
Thanks.
Update on Jul 30, 2018
I found a similar question Ruby Gem Development - How to use ActiveRecord? which is exactly what I want to do. But the answers are not quite clear. Hope this helps in understanding my question.
You can get the functionality you are looking for using generators. Basically you write templates for the files that the user should have (models, migrations, tests) and save them with your gemfile. You then allow the user to copy those files in using commands.
This is a good link that goes into much more detail on generators, but here is a small example that I used in one of my gems:
gem_name/lib/generators/gem_name/install_generator.rb
module GemName
class InstallGenerator < Rails::Generators::Base
include Rails::Generators::Migration
# Allow user to specify a different model name
argument :user_class, type: :string, default: "User"
# Templates to copy
source_root File.expand_path('../../../templates', __FILE__)
# Copy initializer into user app
def copy_initializer
copy_file('create_initializer.rb', 'config/initializers/gem_name.rb')
end
# Copy user information (model & Migrations) into user app
def create_user_model
fname = "app/models/#{user_class.underscore}.rb"
unless File.exist?(File.join(destination_root, fname))
template("user_model.rb", fname)
else
say_status('skipped', "Model #{user_class.underscore} already exists")
end
end
# Copy migrations
def copy_migrations
if self.class.migration_exists?('db/migrate', "create_gem_name_#{user_class.underscore}")
say_status('skipped', "Migration create_gem_name_#{user_class.underscore} already exists")
else
migration_template('create_gem_name_users.rb.erb', "db/migrate/create_gem_name_#{user_class.pluralize.underscore}.rb")
end
end
private
# Use to assign migration time otherwise generator will error
def self.next_migration_number(dir)
Time.now.utc.strftime("%Y%m%d%H%M%S")
end
end
end
Finally, just some advice/personal opinion; your gem should either run under a variety of test suites and databases or else you should indicate that you only support that setup and assume they are available in the user's project. Though I think you could shoehorn in a second test suite, trying to force a second database wouldn't be possible, also you don't need to worry about the DB using migrations unless you want to use a data type that isn't available in all the supported DBs.
A better approach, in my opinion, would be to write separate generators for the specs you want and let the user run them optionally. This will copy the tests in and allow them to modify if they like.

Using a database within a Ruby gem

I'd like to use a database within a Ruby gem that I'm writing. The gem is meant to be used within Rails applications, and will contain an inverted index of documents passed in from the main Rails app.
I'm a bit confused as to how to go about this. Should I hook into the main Rails database somehow? Or should I have a standalone database? Ideally I'd just like to use ActiveRecord to create, update, delete and query entries but I am not sure how I'd set this up.
Data would go into the database at this point:
module ActiveRecordExtension
extend ActiveSupport::Concern
class_methods do
def foo
"bar"
end
end
included do
after_save :add_to_inverted_index
end
def add_to_inverted_index
# This is where I'd take the fields from the Rails app
# and include them to my inverted index. However, I'm struggling
# to find a way to hook into a database from my gem to do this.
end
end
# Include the extension
ActiveRecord::Base.send(:include, ActiveRecordExtension)
Suggestions are much appreciated! Thanks
Well after your clarification, you should use main Rails database. Just create a migration and insert the table(s) you need. You should do that because:
Everyone that uses your gem will know what is stored at the database.
With migrations, you can easily rollback the migration, making it simple to reverse something, if needed.
There's no need to create extra dependencies. Imagine a project that you did in RoR and think if the mess it would be if every gem that you used created its own database.
Maybe you should take a look at known gems and how they do that. I'm thinking about Devise.

Rails 3.2: Adding seed tasks from a mountable engine

I have a rails application using rake db:seed too fill in some of the basics in my DB to start working. This works through a little infrastructure and a fixtures.rb someone else wrote to load a YAML into the DB.
What I'm trying to do is take this infrastructure and move it into my own gem so that I can use it again elsewhere, this requires me both to have some of my own gem's models be inserted through the gem's seed task (which I want to have run from calling db:seed or something similar in the main app) and that the main app push some of the gem's models using it's own seed. The second part of this I already have working, it was a simple fix in the fixtures.rb file I was given.
The things I want to do now:
Move fixtures.rb into the gem: I still don't have any of the source running this in the gem. Now to do this I can probably require the file from the [MyGem::Engine.root, 'lib', ...].join then call a method there with a path to load YAML files from into the DB, which I don't see why it shouldn't work.
Get rake db:seed to run a task defined in my gem. I've added .rake files under lib/tasks of my gem (it's an engine) and I can't seem to call them from the main app rakefile though.
To make myself clear, what I want to do is through the gem (not the main app - or with 1 line of code in the main app) add a dependency onto the main apps seed task, so that when someone runs rake db:seed in the main app the gem will run additional seeding without the main app developer even having to know about them.
The dirty solution that I want to avoid is loading the .rake files from the gem inside the main app, or loading a seeds.rb in the gem from the one in the main app.
So what I'm asking is basically how to make the rake db:seed task do things defined within my gemified engine just by having the gem required in the gemfile?
So shortly after asking this I figured it out.
First step was taken from here: How to add a gems tasks to the main app
Then inside a task file
#lib/task/some_task.rake
Rake::Task['db:seed'].enhance ['my_seed_task']
#lib/tasks/my_seed_task.rake
task 'my_seed_task' do
...
end
And now when in the main app I run rake db:seed it runs whatever my_seed_task defines as a perquisite.
Try instead to extend Rails::Generators::Base.
This is the mechanism as given in the docs:
"Each public method in the generator is executed when a generator is invoked"
so
class DbGenerator < Rails::Generators::Base
source_root File.expand_path('../../../../db', __FILE__)
def copy_seeds_file
copy_file 'seeds.rb', 'db/seeds.rb'
end
def copy_seeds_dir
Dir.foreach seeds_dir do |file|
copy_file "seeds/#{file}", "db/seeds/#{file}" unless file.match /^\./
end
end
def seeds_dir
File.expand_path 'seeds', self.class.source_root
end
end
This approach will allow all gem-based seed data to copy into the app dir for the db:seed run

Ruby On Rails - Access model from a different file

I have a model in my rails application which is
class Person < ActiveRecord::Base
..
end
I would like to access this model from a different ruby file for certain manipulations. This is to populate my db. Can somebody please tell me how to go about it?
I am new to ruby on rails and hence a trivial question. I tried including the model by using require or require relative but I get a LoadError
If you use a rake task to populate/manipulate your database then you can solve this problem by making the task depend on the rails environment, this will set up everything you need to access the db:
task :my_task => :environment do
#do whatever with your models here
end

Rails RSpec with Multiple Databases

I run a Rails app, and we're in the process of splitting out our signup process to a separate app. The signup app has its own separate database (for CMS and collecting prospects), but it also needs to have access to the main database. This works really well using ActiveRecord::Base.establish_connection.
However, I'd like to be able to write some specs. The trouble is, how can I write specs/tests without clearing out my development database every time my tests run? If I go into the console in test mode, it's obvious the the test mode is hooked into the development database from my main app.
Here's what my database.yml file looks like:
development:
database: signup_dev
test:
database: signup_test
main_app_dev:
database: main_app_dev
main_app_test:
database: main_app_test
Based on this file, I'd like establish_connection to connect to connect to the database my_app_dev in development mode, and my_app_test in test mode. Any ideas?
We have a gem that is basically a collection of ActiveRecord models that connect to our legacy system. In our case we have all those models contained in a module from which all models related to the legacy database connects.
module Legacy
class Base < ActiveRecord::Base
establish_connection :legacy
end
class User < Base
end
end
With this setup it makes it really easy to switch out the database connection. If you really go for that automated detection you can put logic in your base class to determine which database to use:
module Legacy
class Base < ActiveRecord::Base
if Rails.env == 'test'
establish_connection :legacy_test
else
establish_connection :legacy
end
end
Or simply tell your module which connection to use in your spec helper:
# spec/spec_helper.rb
Legacy::Base.establish_connection(ActiveRecord::Base.configurations['legacy_test'])
Personally I would recommend the second option. Of course both solutions depend on namespaced models.
Peer
Ryan, we were also in the process of migrating from one datastore to another. We needed to develop against two databases and maintain separate migrations and fixtures for each.
I created a gem called Secondbase to help with this. Essentially, it allows you to manage two databases seamlessly in a single Rails app. Perhaps it will solve your issue as well: https://github.com/karledurante/secondbase
Here's what I came up with as a mixin:
# lib/establish_connection_to_master_database.rb
module EstablishConnectionToMasterDatabase
def establish_connection_to_master_database
case RAILS_ENV
when "development"
establish_connection :master_dev
when "test"
establish_connection :master_test
when "production"
establish_connection :master
end
end
end
ActiveRecord::Base.send(:extend, EstablishConnectionToMasterDatabase)
# models/subscription.rb
class Subscription < ActiveRecord::Base
establish_connection_to_master_database
end
# config/initializers/config.rb
require 'establish_connection_to_master_database'
In order for this to work with RSpec, this needs to be loaded in an initializer - apparently loading it in the environment file causes it to be loaded it too late, and it won't work.
We just used interpolation for this:
class ServiceModel < ActiveRecord::Base
establish_connection :"main_app_#{Rails.env}"
end
The funny :"main_app_" syntax makes a symbol from a string. This could be also written "main_app_#{Rails.env}".to_sym. In either case with Rails 4.1 this must be a symbol (under 3.2 we had just used a string).

Resources