Getting started with the Friendly ORM - ruby-on-rails

I'm following this tutorial: http://friendlyorm.com/
I'm using InstantRails to run MySQL locally. To run Ruby and Rails, I'm using normal Windows installations.
When I run Friendly.create_tables! I only get an empty Array returned: => [] and no tables are created in my 'friendly_development' database.

Author of Friendly here.
You'll have to require all of your models before calling Friendly.create_tables! Otherwise, there's no way for Friendly to know which models exist. In a future revision, I'll automatically preload all your models.

I have a rake task, with help from a guy called Sutto, that will load in all your models and then call Friendly.create_tables! and print out all the tables involved.
namespace :friends do
desc "load in all the models and create the tables"
task :create => :environment do
puts "-----------------------------------------------"
Dir[Rails.root.join("app", "models", "*.rb")].each { |f|File.basename(f, ".rb").classify.constantize }
tables = Friendly.create_tables!
tables.each do |table|
puts "Table '#{table}'"
end
puts "-----------------------------------------------"
end
end
rake friends:create

not much to go on here. My guess is that it can't find your model file that you are creating in the path?

Related

How temporarily disable "needs_migration?" check when testing migration?

I've written the spec to test my migration but when I run it I got an error:
ActiveRecord::PendingMigrationError:
Migrations are pending. To resolve this issue, run:
bin/rake db:migrate RAILS_ENV=test
I've tried to disable the migration check in the before section but that check is running before all tests.
How to disable the migration check for testing purposes?
Testing Rails migration is a bit of a pain so I would rather step back and think about if this needs to be in a Rails migration / tested in a Rails migration.
There are basically two different types of migrations
Schema migrations
Use mostly Rails built in functions. Unless you do some handcrafted SQL I wouldn't bother testing this and trust the framework here.
Data migrations
Data migrations are used to backfill or change data. As data is one of your most valuable assets and loosing or corrupting it is very painful I would definitely recommend to write tests for data migrations.
As mentioned, testing migrations is a bit of a pain so I would try to abstract the data migration code in it's own (service) class. Something like
class DataMigration::UpdateUsername
def self.run
new.run
end
def run
User.all do |batch|
user.update(name: user.name.capitalize)
end
end
end
You can now test the data migration like a normal class like this:
it 'does capitalize the name' do
user = create(:user, name: 'name')
DataMigration::UpdateUsername.run
expect(user.reload.name).to eq('NAME')
end
Now we can use this class in our Rails migration or e.g. just use it in a Rake task. Using it in a Rake task also has the advantages that we can pass in parameters, run several data migrations in parallel (e.g. you have a large data set) or even in a background job which you can't in a Rails migration.
Example
class DataMigration::UpdateUsername
def initialize(start_id:, finish_id:)
#start_id = start_id
#finish_id = finish_id
end
def run
User.find_in_batches(start: start_id, finish: finish_id) do |batch|
batch.each do |user|
user.update(name: user.name.capitalize)
end
end
end
end
Now we can create a custom task for this
namespace :db do
desc "Runs user data migration"
task :update_user, [:start, :finish] do |task, args|
DataMigration::UpdateUsername.new(start_id: args[:start], finish_id: args[:finish])
end
end
rake db:update_user[0, 10000]
rake db:update_user[10000, 20000]
# ...
In config/environments/test.rb add the line
config.active_record.migration_error = false

Uninitialized constant error when trying to refer to a database record within a service

I'm trying to create a rake task that uses a service. Within that service, I want to load the last saved record of a MonthlyMetrics table within my database.
Within my rake file:
require 'metrics_service'
namespace :metrics do
#metrics_service = MetricsService.new
task :calculate_metrics => [:check_for_new_month, :update_customers, :update_churn, :do_more_stuff] do
puts "Donezo!"
end
# ...more cool tasks
end
And my MetricsService within lib/metrics_service.rb:
class MetricsService
def initialize
#metrics = MonthlyMetric.last
#total_customer_count = total_customers.count
assign_product_values
end
# Methods to do all my cool things...
end
Whenever I try to run something like rake:db:migrate, I get the following error:
NameError: uninitialized constant MetricsService::MonthlyMetric
I'm not sure why it's trying to refer to MonthlyMetric the way it is... As a class within the MetricsService namespace..? It's not like I'm trying to define MonthlyMetric as a nested class within MetricsService... I'm just trying to refer to it as an ActiveRecord query.
I've done other ActiveRecord queries, for example User, in other services within the same directory.
What am I doing wrong here?
I think if you just add => :environment to the end of your rake task, that may fix the problem.
As in:
task :calculate_metrics => [:check_for_new_month, :update_customers, :update_churn, :do_more_stuff] => :environment do
I've run into similar problems where Rails does not initialize the correct environment without this tacked on to each rake task.

Elasticsearch, Chewy, Postgres, and Apartment Multi-Tenancy

I have a multi-tenant rails-api project with rails 4.2.3 and ruby 2.2.2. I found lots of resources out there for dealing with multi-tenancy with rails and postgres, but not much regarding elasticsearch and more specifically the chewy gem. I posted an issue on the chewy gem github page, and I got some good feed back there that helped me eventually find a solution to my problem. I figured that it wouldn't hurt to also post it here for the greater good. Here are the specifics of my question.
I have recently switched from MySQL over to Postgres with multiple schemas, and am having trouble with rake chewy:reset:all. It looks as though it is defaulting to the "public" schema, but I want to specify the schema. I am using the apartment gem, so I put this in one of my indexes:
Apartment::Tenant.switch!('tenant_name')
That fixed the rake problem temporarily, but it got me thinking bigger about elasticsearch and chewy and multi-tenancy in general. Does chewy have any sort of implementation of that? If not, do you have any recommendations?
I created a chewy monkey patch initializer:
# config/initializers/chewy_multi_tenancy.rb
module Chewy
class Index
def self.index_name(suggest = nil)
prefix = Apartment::Tenant.current
if suggest
#index_name = build_index_name(suggest, prefix: prefix)
else
#index_name = build_index_name(
name.sub(/Index\Z/, '').demodulize.underscore,
prefix: prefix
) if name
end
end
#index_name or raise UndefinedIndex
end
end
end
And a custom rake task:
# lib/tasks/elastic.rake
namespace :elastic do
desc "resets all indexes for a given tenant ('rake elastic:reset TENANT=tenant_name')"
task reset: :environment do
if ENV['TENANT'].nil?
puts "Uh oh! You didn't specify a tenant!\n"
puts "Example: rake elastic:reset TENANT=tenant_name"
exit
elsif !Apartment.tenant_names.include?(ENV['TENANT'])
puts "That tenant doesn't exist. Please choose from the following:\n"
puts Apartment.tenant_names
exit
else
Apartment::Tenant.switch!(ENV['TENANT'])
Rake::Task['chewy:reset:all'].invoke
end
end
end
Since I have a completely separate test cluster we don't need to prefix our indexes with "test", so I redefined prefix with the current tenant name. As far as I can tell right now, chewy hits the index_name method every time a specific index is called. It then grabs the correct users index for the current tenant.
Thanks to Eli, got me in the right direction. I have updated Eli's code with latest chewy code.
# config/initializers/chewy_multi_tenancy.rb
module Chewy
class Index
def self.index_name(suggest = nil, prefix: nil, suffix: nil)
tenant_prefix = [Apartment::Tenant.current, prefix]
if suggest
#base_name = (tenant_prefix + [suggest.to_s.presence]).reject(&:blank?).join('_')
else
(tenant_prefix + [ base_name, suffix ]).reject(&:blank?).join('_')
end
end
end
end
and custom rake task:
# lib/tasks/elastic.rake
namespace :elastic do
desc "resets all indexes for a given tenant ('rake elastic:reset TENANT=tenant_name')"
task reset: :environment do
if ENV['TENANT'].nil?
puts "Uh oh! You didn't specify a tenant!\n"
puts "Example: rake elastic:reset TENANT=tenant_name"
exit
elsif !Apartment.tenant_names.include?(ENV['TENANT'])
puts "That tenant doesn't exist. Please choose from the following:\n"
puts Apartment.tenant_names
exit
else
Apartment::Tenant.switch!(ENV['TENANT'])
Rake::Task['chewy:reset'].invoke
end
end
end

Best way to fill development db in rails

I need to fill the test development database with data, for example from factorygirl, but I'd like to use it from rails console.
How I put example data in db so I can fetch it from console and do some test there?
Faker is also a good solution.
Here's how my lib/tasks/sample_data.rake looks like. I run it with rake db:populate.
Creates 50 entries with random info.
require 'faker'
namespace :db do
desc "Fill database with sample data"
task :populate => :environment do
Rake::Task['db:reset'].invoke
50.times do |n|
name = Faker::Company.name
year = 1900+rand(111)
rating = 1+rand(10)
watched = (1 == rand(2) ? true : false)
imdb_id = rand(1000000)
Movie.create!(:name => name,
:year => year,
:rating => rating,
:watched => watched,
:imdb_id => imdb_id)
end
end
end
I've made a gem test_dummy that works like Factory Girl to define lots of fake data. When properly configured you can do things like this:
# Create 100 fake companies
100.times { Company.create_dummy }
# Create a single fake company on-demand
fake_company = Company.create_dummy
The alternative is to use the db/seeds.rb facility or to load in your fixtures into your development environment.
Michael Hartl provides an excellent introduction to this topic as part of the railstutorial.org program.
He uses a gem called Factory Girl, which is designed to ease the process of populating a database with sample data.
E.G.
http://ruby.railstutorial.org/chapters/user-microposts#sec:sample_microposts
https://github.com/railstutorial/sample_app/blob/master/lib/tasks/sample_data.rake
Is it just in the Rails console or just 'from the console'?
I like to use a Thor or Rake task to do that. Instead of Factory Girl I use Machinist.
You may want to check this answer
Rails: Good Rspec2 example usage? (Also: Cucumber, Pickle, Capybara)

are fixtures loaded when using the sql dump to create a test database

Because of some non standard table creation options I am forced to use the sql dump instead of the standard schema.rb (i.e. I have uncommented this line in the environment.rb config.active_record.schema_format = :sql). I have noticed that when I use the sql dump that my fixtures do not seem to be loaded into the database. Some data is loaded into it but, I am not sure where it is coming from. Is this normal? and if it is normal can anybody tell me where this other data is coming from?
This is a very old question but even almost 10 years later, the answer is still the same - it seems that fixtures ignore the schema format and are hard-coded to look for YAML files. Here's the Rake task as of Rails 5.2-stable:
https://github.com/rails/rails/blob/5-2-stable/activerecord/lib/active_record/railties/databases.rake#L198
Line 214 uses Dir["#{fixtures_dir}/**/*.yml"] to find files, so only .yml will be read.
Solutions revolve around loading your SQL fixtures into an otherwise empty database, then dumping them as YAML using the yaml_db gem or something such as that described in this blog post. Since links to blog posts often die quite quickly, I've replicated the source below:
namespace :db do
desc 'Convert development DB to Rails test fixtures'
task to_fixtures: :environment do
TABLES_TO_SKIP = %w[ar_internal_metadata delayed_jobs schema_info schema_migrations].freeze
begin
ActiveRecord::Base.establish_connection
ActiveRecord::Base.connection.tables.each do |table_name|
next if TABLES_TO_SKIP.include?(table_name)
conter = '000'
file_path = "#{Rails.root}/test/fixtures/#{table_name}.yml"
File.open(file_path, 'w') do |file|
rows = ActiveRecord::Base.connection.select_all("SELECT * FROM #{table_name}")
data = rows.each_with_object({}) do |record, hash|
suffix = record['id'].blank? ? conter.succ! : record['id']
hash["#{table_name.singularize}_#{suffix}"] = record
end
puts "Writing table '#{table_name}' to '#{file_path}'"
file.write(data.to_yaml)
end
end
ensure
ActiveRecord::Base.connection.close if ActiveRecord::Base.connection
end
end
end
The code above was published on July 16, 2017 by Yi Zeng. You'd put this in a file called something like lib/tasks/to_fixtures.rake. I loaded my SQL fixture data into the otherwise empty/clean test-mode database, then ran RAILS_ENV=test bundle exec rake db:to_fixtures. It worked as-is for me under Rails 5.2.3.
If you are loading the DB from the script you dumped, that should be all that is in there. If you see anything else I would try dropping the db and recreating it from the script to make sure.
Also, if you just want to load the fixtures, you can run:
rake db:fixtures:load
Update:
You may want to look for a way to include your options in the migrations. In my experiance it nearly always pays off to do things the rails way. If it helps, I would add custom options for using mysql cluster by using the :options option on create table:
class CreateYourTable < ActiveRecord::Migration
def self.up
create_table :your_table, :options => "ENGINE=NDBCLUSTER" do |t|
#...
end
end

Resources