How to test ElasticSearch in a Rails application (Rspec) - ruby-on-rails

I was wondering how you were testing the search in your application when using ElasticSearch and Tire.
How do you setup a new ElasticSearch test instance? Is there a way to mock it?
Any gems you know of that might help with that?
Some stuff I found helpful:
I found a great article answering pretty much all my questions :)
http://bitsandbit.es/post/11295134047/unit-testing-with-tire-and-elastic-search#disqus_thread
Plus, there is an answer from Karmi, Tire author.
This is useful as well: https://github.com/karmi/tire/wiki/Integration-Testing-Rails-Models-with-Tire
I can't believe I did not find these before asking...

Prefixing your index-names for the current environment
You could set a different index-name for each environment (in your case: the test environment).
For example, you could create an initializer in
config/initializers/tire.rb
with the following line:
Tire::Model::Search.index_prefix "#{Rails.application.class.parent_name.downcase}_#{Rails.env.to_s.downcase}"
A conceivable approach for deleting the indexes
Assuming that you have models named Customer, Order and Product, put the following code somewhere at your test-startup/before-block/each-run-block.
# iterate over the model types
# there are also ways to fetch all model classes of the rails app automaticly, e.g.:
# http://stackoverflow.com/questions/516579/is-there-a-way-to-get-a-collection-of-all-the-models-in-your-rails-app
[Customer, Order, Product].each do |klass|
# make sure that the current model is using tire
if klass.respond_to? :tire
# delete the index for the current model
klass.tire.index.delete
# the mapping definition must get executed again. for that, we reload the model class.
load File.expand_path("../../app/models/#{klass.name.downcase}.rb", __FILE__)
end
end
Alternative
An alternative could be to set up a different ElasticSearch instance for testing on another port, let's say 1234.
In your enviornment/test.rb you could then set
Tire::Configuration.url "http://localhost:1234"
And at a suitable location (e.g. your testing startup) you can then delete all indexes on the ElasticSearch testing-instance with:
Tire::Configuration.client.delete(Tire::Configuration.url)
Maybe you must still make sure that your Tire-Mapping definitions for you model classes are still getting called.

I ran into a quirky bug when deleting my elasticsearch index via tire in my rspec suite. In my Rspec configuration, similar to the Bits and Bytes blog, I have an after_each call which cleans the database and wipes out the index.
I found I needed to call Tire's create_elasticsearch_index method which is responsible for reading the mapping in the ActiveRecord class to set up the appropriate analyzers, etc. The issue I was seeing was I had some :not_analyzed fields in my model which were actually getting analyzed (this broke how I wanted faceting to work).
Everything was fine on dev, but the test suite was failing as facets were being broken down by individual words and not the entire multi word string. It seems that the mapping configuration was not being created appropriately in rspec after the index was deleted. Adding the create_elasticsearch_index call fixed the problem:
config.after(:each) do
DatabaseCleaner.clean
Media.tire.index.delete
Media.tire.create_elasticsearch_index
end
Media is my model class.

I ran into similar issues and here's how I solved it. Bare in mind that my solution builds on top of #spaudanjo solution. Since I'm using spork, I add this inside the spec_helper.rb's Spork.each_run block, but you may add this into any other each/before block.
# Define random prefix to prevent indexes from clashing
Tire::Model::Search.index_prefix "#{Rails.application.class.parent_name.downcase}_#{Rails.env.to_s.downcase}_#{rand(1000000)}"
# In order to know what all of the models are, we need to load all of them
Dir["#{Rails.root}/app/models/**/*.rb"].each do |model|
load model
end
# Refresh Elastic Search indexes
# NOTE: relies on all app/models/**/*.rb to be loaded
models = ActiveRecord::Base.subclasses.collect { |type| type.name }.sort
models.each do |klass|
# make sure that the current model is using tire
if klass.respond_to? :tire
# delete the index for the current model
klass.tire.index.delete
# the mapping definition must get executed again. for that, we reload the model class.
load File.expand_path("../../app/models/#{klass.name.downcase}.rb", __FILE__)
end
end
It basically defines it's own unique prefix for every test case so that there are no in indexes. The other solutions all suffered from a problem where even after deleting the index, Elastic Search wouldn't refresh the indexes (even after running Model.index.refresh) which is why the randomized prefix is there.
It also loads every model and checks if it responds to tire so that we no longer need to maintain a list of all of the models that respond to tire both in spec_helper.rb and in other areas.
As this method doesn't "delete" the indexes after using it, you will have to manually delete it on a regular basis. Though I don't imagine this to be a huge issue, you can delete with the following command:
curl -XDELETE 'http://localhost:9200/YOURRAILSNAMEHERE_test_*/'
To find what YOURRAILSNAMEHERE is, run rails console and run Rails.application.class.parent_name.downcase. The output will be your project's name.

Related

Rails Engine: Create dummy model for relations?

I am trying to make a Rails Engine that can be plugged into my applications and manage friendships between users. To do that, all the logic dealing with friend requests, acceptance, etc, is going to live in a Rails Engine.
When I create my Friendship model, it needs a belongs_to relation for two Users (two friends). But, I don't want to tie a user to this engine. I want this engine to work generically with any User an application has established.
What technique does one use to create a dummy User that is never to be included in the host application? (I want to avoid a migration of the engine pulling in this dummy User.)
Update:
I removed the second question, pertaining to how to then override the engine's User with the host app's User. I found the answer to that in the guides (http://edgeguides.rubyonrails.org/engines.html#configuring-an-engine).
tl;dr
cd into engine's dummy app root
run rails g model User
run rake db:migrate
Discussion
I came here looking for an answer, but seeing how there were no answers, I will share how I ended up solving this.
When creating a mountable Rails engine (via something like rails plugin new . --mountable --dummy-path=spec/dummy), Rails will generate a "dummy" app for your engine that will serve as the main app an engine would normally be required into.
Us RSpec users use the "--dummy-path" directive and put the dummy app in /spec folder, but for you it may be elsewhere. Find where it is and cd into that dummy app's root.
Once inside the dummy app, call rails g model User to generate a User model for the dummy app. Run rake db:migrate to add the table.
Now you have a placeholder User model that should function acceptably in tests as an association.
You may want to define some mock methods on User, do so in the model definition file located in /path/to/dummy/app/models/user.rb
Naturally, you can make a full-fledged model with associations and logic instead of just a placeholder right there in dummy.
Migrations and code in dummy app are not used when an engine is included in an app,as rake railties:install:migrations will show.
This might be a bit hacky!
I have an app with several engines. Each engine needs to call a limited amount of functionality in the other engines.
For example, Engine1 and Engine2 don't depend on (require or load) each other. However, a model such as Engine1::User might need to call a selection of records using a scope called all_active from the Engine2::Department model. That's okay inside the context of the overall Rails app into which both engines are loaded as Gems.
The problem arises for the developer of Engine1 when they want Engine2::Department.all_active, and they don't have it.
The solution I am using is to create a "mocked model" (for want of a better phrase) in Engine2's test/dummy/app/models (or spec/dummy/app/models if you're using RSpec) folder.
Something like this will work in Engine1:
# spec/dummy/app/models/engine2/department.rb
require '../../app/models/engine2/department' if File.exist?('../../app/models/engine2/department')
module Engine2
class Department
unless Engine2::Department < ActiveRecord::Base
def self.all_active
[{ id: 1, name: 'Finance', active: true},
{ id: 2, name: 'Sales', active: true}]
end
end
end
end
Because this file is in the dummy app, it won't get found by the main Rails app, so it won't interfere there. It will only get loaded in the dummy rails app during development and testing.
FYI, the unless Engine2::Department < AR::Base detects if the Department model inherits from ActiveRecord already, and if it does, the enclosing code won't be loaded. This is a safety mechanism to prevent the class method all_active from being overwritten by the hard-coded sample data in the hash shown above.
If you have a lot of engines in your system, perhaps you should consider creating a git repo of an engine template that can be pulled into each engine (containing common code, etc).
Your sample data might benefit from being OpenStruct objects, so your hash keys can be accessed using dot notation. You could then write code such as #engine2_departments.first.name rather than #engine2_departments.first[:name].

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 roles and permissions - how do I do it right?

I've sucessfully rolled my own roles and permissions based authorisation system:
Roles and Permissions are both models in the database that have a many-to-many relationship through another table.
Users belong_to a role and have_many permissions through that role
Admin users are able to create new roles and define which permissions go with which roles.
Controllers and Views check the current user has particular permissions before acting or rendering certain things.
It all works wonderfully. Here's a key part of the code which gets included into application_controller,rb:
def permitted_action (permission_names)
# if the users permissions do not instersect with those given then redirect to root
redirect_to(root_url) if (permission_names & current_user.permissions.map(&:name)).empty?
end
and here's a controller validating user permissions:
before_action only: [:new, :create] do
permitted_action ['create_user']
end
My problem is that permissions are DB rows with unique string names, and I use the names when I want to identify the permissions. So I seed the DB with all the permissions that I need, and then when a View or Controller needs to check the permission I need to get the permission name right at that point. If I check for permission "add_user" but in the db the permission name is "add_users" it goes wrong.
Then in testing I have to put all the permissions in again as fixtures, and get all the names right again.
When I add functionality that requires more permissions I have to add these permissions in with the same string in at least 3 different places. That seems like the wrong thing to me.
Ideally the controllers would define what the permissions are that they use, and the db/seeds.rb file would pick up those and seed the db, and the test fixtures would also pick them up, but I'm not sure if this is possible.
How should I structure this?
Edit:
I think what would help me is a fixtures parser that feeds the db/seeds.rb file. Taking something like this from permissions.yml:
archive_tally:
name: archive_tally
description: Archive or make active any tally
category: 3
And spitting out something like this:
archive_tally = Permission.find_or_initiate_by_name("archive_tally")
archive_tally.description = "Archive or make active any tally"
archive_tally.category = 3
archive_tally.save!
I don't think this sort of thing exists yet.
You can actually cut this down to just being the user table (or roles too depending on how many there are) by adding fields like is_admin or is_general_user to group your users into permissions groups. Then the permissions are just a matter of methods on the User Model, for example:
def can_create_new_roles?
self.is_admin
end
So now you can just do
before_action only: [:create, :new] do
redirect_to root_path unless current_user.can_create_new_roles?
end
Which reads much nicer. Plus since this all happens on the User model vs. on its own DB table it would make testing for all of these UserPermissions much easier.
This is what I've come up with and it satisfies me, though it's not the ideal solution that I was hunting for in the question:
It turns out that I want the production database to be seeded with fixtures that are a subset of the fixtures I want appearing in the testing database.
I've made a directory db/seed_fixtures that includes files such as roles.yml and permissions.yml. In these files I've put the fixtures I want to seed the production database with.
In tests/fixtures/roles.yml I've included the other fixtures with this line at the top of the file:
<%= ERB.new(IO.read(Rails.root.join "db/seed_fixtures/roles.yml")).result %>
Same deal with permissions.yml and other YAML files this applies to. I put any testing-only fixtures in there under that include line.
In db/seeds.rb I generate seeding code from the fixtures for production like this: ActiveRecord::Fixtures.create_fixtures("#{Rails.root}/db/seed_fixtures", "roles")
Now I write all my fixtures once each, some for use as seeds in the production database, others are only for testing. To load the seeds in to the database I run rake db:seed (which I do on the production server) and to load all the testing fixtures in the database I run rake db:fixtures:load
Update:
I've just started using Cucumber and I've realised that I can use the code in step 3 above in a Cucumber Step to get the seed data into Cucumber's test database:
Given(/^seed data is loaded into the database$/) do
ActiveRecord::FixtureSet.create_fixtures("#{Rails.root}/db/seed_fixtures", "roles")
_(Role.count).wont_equal 0
end
Cucumber also informed me that ActiveRecord::Fixtures is deprecated in favour of ActiveRecord::FixtureSet
I've come up with a completely different solution: I've created a fixture parser.
Most of my first solution still applies (see my other answer) but now, instead of step 3 in that solution, I've made a parser that reads the fixtures that I want to use for seeds and adds them to the database, checking for whether they are already there or not. I run the parser from db/seeds.rb
I've put the code for the parser up as a gist on github

Reverse Engineering (Generating) Tables or Database Schema from Models and Views in Ruby on Rails

Update: The Question is Still Open, any reviews, comments are always welcome
I am having an existing rails project in which some important files and directories has been missed.
project rails version (2.3.8) i found it in environment.rb
currently what i am having is
app
controllers (already fully coded)
helpers (already fully coded)
models (already fully coded)
reports (already fully coded)
views (already fully coded)
config ---> default configurations (already fully coded)
lib ---> contains nothing
public --> contains images and scripts (already fully coded)
script ---> contains server,runner,plugin,dbconsole....
app directory fully contains working state of codes, app/model contains more than 100 .rb files , so i assume it will be more than 100 tables
the mainly missing things are db directory, .gem file, rake file, doc, test, vendor, database,schema.rb and migrations
Note:
i don't have the table schema and database for that project
i am in Need to generate tables or complete database from models and views and
i am looking for reverse engineering kind of stuff for generating db schema from models or views
I am newbie to rails and i am from java background , in java by using hibernate there is an pojo(model in rails) to database option available, i am looking for similar kind of stuffs for rails , and my main aim to run that project , so guys please help me.
To recreate the database schema, it will take quite a bit of time.
You can get a lot of information about the database in the app/models, app/controllers app/views directory.
You should know that ActiveRecord does not require you to explicitly list all the attributes of a model. This has important implications - you can only infer what attributes you still have to add to the database, based on whether an attribute is referred to! This means doing this will be a bit of an ART. And there are no CLEAR steps to complete this work. But below are some rules which you can use to HELP you.
This is a BIG project, below are guidelines, rules and tips to help you. But be aware that this could take a long time, and be frustrating at times to get this done.
What Tables you need:
Each table will normally have a matching ActiveRecord::Base model. So in the app/models directory, check each file, and if the class inherits from ActiveRecord::Base, it is an extra table.
The table name is by default a pluralized snake case version of the name of the class.
class UserGroup < ActiveRecord::Base # for this class
the name of the table is user_groups. Notice it is plural, and instead of camel case, it is lowercase, with underscores to separate the words.
All these tables will have an "id" integer column. By default, the tables also have a "created_at", and "updated_at" column of type datetime.
Associations and foreign keys:
You can infer what foreign keys exist by the associations in the Models. All associations are explicitly listed, so this is not too hard.
For example:
class UserGroup < ActiveRecord::Base # for this class
belongs_to :category
This means that the user_groups table has a column named "category_id", which is a foreign key for the categories table.
This means that the Category model likely has an inverse relationship (but no extra column):
class Category < ActiveRecord::Base
has_many :user_groups
The main other association is the has_many_and_belongs_to association. Eg.
class A < ActiveRecord::Base
has_and_belongs_to_many :bs
end
class B < ActiveRecord::Base
has_and_belongs_to_many :as
end
This means that there is a join table to add called "as_bs" (as and bs are sorted alphabetically), with the foreign keys "a_id" and "b_id".
All foreign keys are integers.
Attributes
Ok, so that's the table associations. Now for the normal attributes...
You should check the app/views/user_groups/ or other similar app/views directories.
Inside you will find the view templates. You should look at the _form.html.erb templates (assuming it is .erb templates, otherwise it could be .haml etc templates).
The _form.html.erb template, if it exists, will normally have many of the attributes listed as form fields.
In the form_for block, check if it says something like f.text_field :name, it means there is an attribute/(column in the table) called "name". You can infer what type the column should be by what type of field it is. Eg. in this case, it is a string, so maybe a VARCHAR(255) is appropriate (referred to as string in Rails).
You might also need to infer what type is appropriate based on the name of the attribute (eg. if it mentions something like :time, then it is probably either of type Time or DateTime).
This may give you all the other attributes in the table. But in some cases, you might miss the attributes. If you find a reference to other attributes in the controller, eg. app/controllers/user_groups_controller.rb, then you should add that as a column in your table. You can leave this until the end when you test it though, because when you test it, if an attribute is missing, then it will throw a NoMethodError for the object of the relevant model. Eg. if it says that #user_group variable, of class UserGroup, is missing a method named title, then it probably is missing a column named "title" of type string.
Recreate your migration/database
Ok, so now you know what the database tables and column names and types should be.
You should generate/recreate a migration for your database.
To do this, just use the command rails generate migration RecreateTables.
Then you should find a file in db/migrate/???_recreate_tables.rb.
Inside, start writing ruby code to create your tables. Reference for this can be found at http://guides.rubyonrails.org/migrations.html.
But essentially, you will have something like:
class RecreateTables < ActiveRecord::Migration
def up
create_table :user_groups do |t|
t.string :name # adds a string (VARCHAR) column called "name"
t.text :description # adds a textarea type column called "description
t.timestamps # adds both "created_at" and "updated_at" columns for you
end
end
def down
drop_table :products # this is the reverse commands to undo stuff in "up"
end
end
To recreate your Gemfile:
Start by adding a default Gemfile. This can be done by using rails new testapplication somewhere to create an empty rails application. Then copy the Gemfile to your actual application. It will get you started by including rails and other common gems.
It is VERY hard to work out exactly what gems are needed. The best you can do is try adding them one by one as you look through the code.
Again, here, MethodNotFound errors are your FRIEND. When you test the application, based on the gems you have added, it might detect some missing methods which might be supplied by gems. Some missing methods on models might indicate missing gems (or they might indicate missing fields/columns in the database). However, missing methods on Controller or ActiveRelation classes are VERY likely because of missing gems.
You will have to look through the code and try to infer what gems to add.
If it uses methods like can, can?, and has a file app/models/ability.rb, then you need gem 'cancan'. If it calls devise in a model, it needs gem 'devise'. Many common gems can be seen at http://ruby-toolbox.com.
After adding gems to your Gemfile, you should run bundle on your command line to install the new gems before testing again. When you test it again, you should restart your test server. Rerun bundle exec rails server to start a local test server on localhost:3000 or something like that.
You can simply copy the Rakefile from rails new testapp, and it will probably include everything you need.
Missing Tests
The missing test/ directory is not relevant to your actual application. It is not required to run the application. However, it does hold automatic scripts to test your application. You will have to re-write new tests if you want to automatically test your application. However for the purpose of getting your application back up, you can ignore it for now.
Missing vendor directory
Some extra code is not installed as a gem, but as a plugin. Anything installed as a plugin is lost if you don't have the vendor directory. As with gems, the best you can do is try to infer what might be missing, and re-download the missing plugin, either re-installing the plugin, or using a gem replacement.
Additional tips:
Try reading some of the comments which might name some of the gems used.
If a method or set of methods are missing, that you think are not database fields/columns, it might be due to a missing gem. The best thing to do is to search google for those method names. Eg. if it is missing "paginate", you can search "rails paginate gem", and see what likely gems you might need. This example will probably come up with "will_paginate", and "kaminari". Then you have to try and infer which of the gems are required. Maybe do a grep will_paginate app -r on the command line to see if it is using will paginate. The grep command searches for the string "will_paginate", in the directory called "app", -r makes it do this recursively for all files
Even though rails is a full stack web framework it would work with out some parts as well, if you wish to,
Ex: in your case
db - directory is there for keep the migrations to create you DB/tables, but if you are using a legacy DB or the database stuff is handled by DB administrators, you might not want it. (you can simply connect to the DB via database.yml file)
Gem file is helping you to keep all the gems (libraries) in one place as you do with Maven (in java)
test, again if you done write test cases (which is absolutely a bad idea), you done need this
vendor, is for 3rd party plugins and doc is for documentation, so same rule applies, if you done need them you can skip them
Hibernate in rails called "Activerecord", same concept, a model is bind with a database table (technically model represents a raw in the table)
So if you really want them add them but if not just leave them
BUT, I think having a proper Gem file and test cases is a must
welcome come to Rails
HTH
In the following, I assume you already know how to:
dump your database schema into an SQL file
start a Rails console (rails c)
generate a Rails migration
Here's what I think you should do.
Identify which of your classes correspond to physical tables (you mention some views in your question, which leads me to believe a subset of your models are bound to database views instead of actual tables). To do this you need to match the definitions of your models (classes which extend ActiveRecord::Base) to CREATE TABLE statements in your schema dump. For instance, class Person in your Ruby code matches to CREATE TABLE people in your DB schema dump.
Once you identified those models (class names), you start up a Rails console and you type those model names, one at a time, and press Enter. The console output for a model called Person would presumably look like this:
>> Person
=> Person(id: integer, first_name: string, last_name: string)
You then take what's inside the parentheses, strip the leading id: integer,, get rid of commas, get rid of those blanks after the colons, thus obtaining something like this:
first_name:string last_name:string
Having done this, the command to generate the migration would look like this:
rails g migration Person first_name:string last_name:string
You then start a new Rails project somewhere else, perform all of these migrations and inspect the contents of db/migrate. Your migrations are most likely 90% done, what you still need to do is replace some instances of t.integer with t.references, and other minor stuff that's completely domain-specific and impossible to capture in a generic answer.
HTH.

Model-specific SQL logging in rails

In my rails application, I have a background process runner, model name Worker, that checks for new tasks to run every 10 seconds. This check generates two SQL queries each time - one to look for new jobs, one to delete old completed ones.
The problem with this - the main log file gets spammed for each of those queries.
Can I direct the SQL queries spawned by the Worker model into a separate log file, or at least silence them? Overwriting Worker.logger does not work - it redirects only the messages that explicitly call logger.debug("something").
The simplest and most idiomatic solution
logger.silence do
do_something
end
See Logger#silence
Queries are logged at Adapter level as I demonstrated here.
How do I get the last SQL query performed by ActiveRecord in Ruby on Rails?
You can't change the behavior unless tweaking the Adapter behavior with some really really horrible hacks.
class Worker < ActiveRecord::Base
def run
old_level, self.class.logger.level = self.class.logger.level, Logger::WARN
run_outstanding_jobs
remove_obsolete_jobs
ensure
self.class.logger.level = old_level
end
end
This is a fairly familiar idiom. I've seen it many times, in different situations. Of course, if you didn't know that ActiveRecord::Base.logger can be changed like that, it would have been hard to guess.
One caveat of this solution: this changes the logger level for all of ActiveRecord, ActionController, ActionView, ActionMailer and ActiveResource. This is because there is a single Logger instance shared by all modules.

Resources