Sharing a mongoid model between 2 applications - using engine vs. plugin - ruby-on-rails

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

Related

Use Orchestrate.io with a basic Rails Application

I have a basic application running with ruby on rails. I use sqlite3 for development and postgres for production. I recently got access to Orchestrate.io and their student tier.
I would like to know how I could possibly use this with my application. I'm not even sure this is possible. My app is really simple (add-edit-delete). But it would be interesting to have a go at using the tools.
Mostly because I cannot find an efficient and free rails db text search.
You can definitely use Orchestrate as the backend for a Rails application.
A few different options:
The Orchestrate-Ruby client has both a method client & object client. Although the object client is still considered a work-in-progress, the method client provides a solid interface for connecting to the Orchestrate API.
Use an http library (such as rest-client or faraday) to make calls to the Orchestrate API.
There's also the Orchestrate-Rails gem. Your mileage may vary with this (last updated in April).
To use Orchestrate as the only backend (no postgres/mysql), I've found the best approach is to disable ActiveRecord:
When creating a new application, you can use -O to skip ActiveRecord:
rails new my_app -O
For existing applications:
1. Remove database adapter gems from your Gemfile (pg, mysql2, sqlite3, etc.)
2. Change your config/application.rb
Remove require 'rails/all line and require frameworks you want to use, for example:
require "action_controller/railtie"
require "action_mailer/railtie"
require "sprockets/railtie"
require "rails/test_unit/railtie"
3. Delete your database.yml file, db/schema.rb and migrations (if any)
4. Delete migration check in test/test_helper.rb
5. Delete any ActiveRecord configuration from your config/environments files (this is what is causing your error)
If you run into problems/errors, stack trace should give you sufficient information on what you need to change. You might for example have some ActiveRecord configuration in your initializers.
With the Orchestrate-Ruby client, you can define a model like so:
class Foo < Orchestrate::Collection
...
end
I'm still undecided on what the best approach for defining the Application object is, but one way I've done it is by making a global object with an initializer:
# /config/initializers/orchestrate.rb
$App = Orchestrate::Application.new(ENV['ORCHESTRATE_KEY']) # using dotenv gem
$App = Orchestrate::Application.new('your_key_here') # without dotenv
From here, you can then access the $App object in your controllers (to initialize the collection object):
class FoosController < ApplicationController
def index
#foos = Foo.new($App, "foos").each
end
end
Leveraging model concerns might be a better way to share the Application object/API key between models. Or you could roll your own classes on top of the method client.
Hope this helps!
I'll be looking into how to accomplish this in a more 'Railsy' way.
EDIT: also, textacular is a ruby gem which provides full-text search and indexing for postgres

rails: how to supply a model with gem, and how to test rails gem without dummy app

I have already read The Basics Of Creating Rails Plugins and several other articles, but I can't find how to supply a model with a gem.
Say, I want to make a gem for tagging (yes, I know about acts_as_taggable_on gem, I need different functionality). So, I want the model Tag to be bundled in the gem. I found no tutorial explaining that.
Of course I tried to reverse-engineer acts_as_taggable_on gem to understand how does it work, but it brought even more confusion: the tutorial I mentioned above says that I should have a dummy app in my gem, in order to test the gem. BUT, acts_as_taggable_on has no such dummy application! How how does it get tested, then?
About the model: ok, I see the file lib/acts_as_taggable_on/tag.rb that seems to be a Tag model:
module ActsAsTaggableOn
class Tag < ::ActiveRecord::Base
# ..........................
end
end
I see that file lib/acts-as-taggable-on.rb requires tag:
require "acts_as_taggable_on/tag"
So I've applied the same approach (assume my plugin is named dftags) :
I have added file lib/dftags/tag.rb:
module Dftags
class Tag < ::ActiveRecord::Base
# attr_accessible :title, :body
end
end
And my lib/dftags.rb looks like this:
module Dftags
end
require "dftags/tag"
I have specs tag_spec.rb:
require 'spec_helper'
describe Tag do
let(:tag) { Tag.new(name: "") }
it { should validate_presence_of :name }
end
And when I run bundle exec rspec spec/, I got error unitialized constant Tag (NameError).
It seems I missed something important. Plus, again, I have dummy app for testing, but acts_as_taggable_on doesn't; so, the testing approach should be different..
So, the questions:
How can I supply a model with gem?
How can I test my gem without dummy app?
Are there some advanced docs about writing rails gems? Actually I tried to check out one more famous gem: devise, but the ruby-fu and rails-fu of the authors is too strong for me to understand it. Where do people learn all of it?
How can I test my gem without dummy app?
The dummy app is only a helper that allows you to use your normal rails testing workflow when building a gem / plugin. You could run the tests without a dummy app but you would need a lot more manual work.
acts_as_taggable_on is pretty much active_record only with the exception of a single helper (as far as I can tell from a quick glance). The author therefore decided that the overhead of maintaining the dummy app was not worth the effort and is setting up active_record by hand. See here https://github.com/mbleigh/acts-as-taggable-on/blob/master/spec/spec_helper.rb#L24 how he establishes the connection to the database.
This code would not be necessary when using a dummy app as rails is taking care of it.
The same is true for the helper. Instead of using the test methods provided by rails he creates a new Class that includes the helper and uses an instance of this class to test it (h/acts-as-taggable-on/blob/master/spec/acts_as_taggable_on/tags_helper_spec.rb#L11).

Creating an install generator for a Ruby gem

I'm pretty new to building Ruby gems and am taking a stab at my first one. I'm in the middle of writing a generator for my gem that will generate a migration in my Rails app. I'm looking to simply include the gem in the Rails application, run "rails g mygem:install" to have it create the migrations, then run "rake db:migrate" to finalize everything.
I've found several different ways to accomplish similar tasks, but so far nothing has worked. I cannot seem to get the Rails application to find the generator. The latest guide I've tried is located here: http://www.railsdispatch.com/posts/how-rails-3-enables-more-choices-part-1.
Here is what my current gem structure looks like:
-lib/
-generators/
-templates/
-some_migration.rb
-install_generator.rb
-gemname/
-rails/
-railtie.rb
-engine.rb
-tasks/
-gemname.rake
-gemname.rb
-spec/
-gemname.gemspec
Here is what my install_generator.rb file looks like:
require 'rails'
module Gemname
class InstallGenerator < ::Rails::Generators::Base
include Rails::Generators::Migration
source_root File.expand_path('../templates', __FILE__)
desc "add the migrations"
def self.next_migration_number(path)
unless #prev_migration_nr
#prev_migration_nr = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
else
#prev_migration_nr += 1
end
#prev_migration_nr.to_s
end
def copy_migrations
migration_template "some_migration.rb", "db/migrate/some_migration.rb"
end
end
end
I'm not sure if there's something I'm missing. I'm testing with a Rails 3.2 application that has my gem listed in its Gemfile and the gem is installed. Is there anything wrong with gem folder structure that might be preventing my generator from showing up? Do I need to require something somewhere?
Any help is appreciated.
Rails no longer automatically loads everything in lib by default, like it used to. For a few methods of requiring your files, check out the answers to this question - Best way to load module/class from lib folder in Rails 3?.
Well, I'm not sure which of the changes I made fixed my problem, but here is what I did:
Changed the name of my generator to gemname_generator.rb and the directory structure of my generator to this:
- lib/
-generators/
-templates/
-gemname_generator.rb
Added this line to my generator:
namespace 'gemname'
I'm now able to run "rails g gemname" in my Rails application and have it call my generator.

Converting Rails 2 plugin to Rails 3 gem

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.

How to gemify a Rails (engine) plugin?

I have a engine style Rails plugin from which I can create a gem using Jeweler. But when I require it in my Rails environment (or erb) the models within the plugin are not loaded. I have followed a number of tutorials and read just about everything on the subject.
# environment.rb
config.gem 'myengine'
# in irb
require 'myengine'
I have unpacked the gem and verified that all files are present. My init.rb has been moved to a new folder called 'rails' as per. All files in 'lib' are automatically added to the $LOAD_PATH, so require 'myengine' runs lib/myengine.rb. I verified this by putting a puts 'hello' within.
Is it because of the physical presence of plugins in a known place that Rails can add all the models, controller etc. to the relevant load_paths? Do I need to replicate this manually when using a gem?
Would gemspec require_paths be a way of adding additional paths other than lib? I assume however that Rails does not just require every single file, but loads them on demand hence the need for the filename and class name to match?
%w{ models controllers helpers }.each do |dir|
path = File.join(File.dirname(__FILE__), 'app', dir) + '/'
$LOAD_PATH << path
puts 'requiring'
Dir.new(path).entries.each do |file|
if file =~ /\.rb/
puts file
require file
end
end
end
By adding the above to lib/myengine.rb all the models/controllers are required. But like I said in my question this is unlikely to be a good way forward.
Offhand I'd say the part about adding those directories to the search path is right on. What you shouldn't need to do is require each file manually (as you allude to in your last sentence). What Rails does when you reference a non-existent constant is to search for a file with the same name (underscored of course) in the load path.
If for some reason you can not abide by the constraint (think about it long and hard) then you are going to need to dig deeper into Rails and see how the reloading mechanism works so you can tie into it properly in development mode.
The problem was the files (in app) where not being added to the gem because when using Jeweler it only automatically adds files to required_paths which are committed to git.

Resources