How to Handle ActiveRecord Migrations in a Distributed Gem? - activerecord

I am trying to write an app as a gem using ActiveRecord without Rails.
My problem is how to migrate a database already deployed by a user who will not have rake, etc. I have just distributed a schema.rb file and created the db from that. But now I want to allow users to update to a new gem and migrate their db.
I've looked at ActiveRecord::Migrator, but can't figure out how to use it.
For example, how would I tell ActiveRecord::Migrator to run all migrations up from whatever is the current_migration?
Anyone have any suggestions for how to go about this or a good reference?

After going at this fresh this morning I came up with the following which is working well:
module Byr
module Db
class << self
attr_accessor :config
attr_accessor :adapter
attr_accessor :db_name
end
def self.create_sqlite(config)
require 'sqlite3'
config = config.merge('database' => File.join(Byr.db_dir, config['database']))
ActiveRecord::Base.establish_connection(config)
end
def self.create_pg(config)
require 'pg'
# Connect to the postgres db to create the db
ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres',
'schema_search_path' => 'public'))
begin
result = ActiveRecord::Base.connection.create_database(config['database'])
rescue PG::Error, ActiveRecord::StatementInvalid => e
unless e.message =~ /already exists/
raise
end
end
true
end
def self.mysql_creation_options(config)
#charset = ENV['CHARSET'] || 'utf8'
#collation = ENV['COLLATION'] || 'utf8_unicode_ci'
{:charset => (config['charset'] || #charset), :collation => (config['collation'] || #collation)}
end
def self.create_mysql(config)
require 'mysql2'
error_class = config['adapter'] =~ /mysql2/ ? Mysql2::Error : Mysql::Error
begin
ActiveRecord::Base.establish_connection(config.merge('database' => nil))
ActiveRecord::Base.connection.create_database(config['database'], mysql_creation_options(config))
ActiveRecord::Base.establish_connection(config)
rescue error_class => sqlerr
access_denied_error = 1045
if sqlerr.errno == access_denied_error
print "#{sqlerr.error}. \nPlease provide the root password for your mysql installation\n>"
root_password = $stdin.gets.strip
grant_statement = "GRANT ALL PRIVILEGES ON #{config['database']}.* " \
"TO '#{config['username']}'#'localhost' " \
"IDENTIFIED BY '#{config['password']}' WITH GRANT OPTION;"
ActiveRecord::Base.establish_connection(config.merge(
'database' => nil, 'username' => 'root', 'password' => root_password))
ActiveRecord::Base.connection.create_database(config['database'], mysql_creation_options(config))
ActiveRecord::Base.connection.execute grant_statement
ActiveRecord::Base.establish_connection(config)
else
Byr.warn sqlerr.error
Byr.warn "Couldn't create database for #{config.inspect}, charset: #{config['charset'] || #charset}, collation: #{config['collation'] || #collation}"
Byr.warn "(if you set the charset manually, make sure you have a matching collation)" if config['charset']
end
rescue ActiveRecord::StatementInvalid => e
ActiveRecord::Base.establish_connection(config)
end
end
def self.migrate
sys_migration_dir = File.join(Byr.install_dir, "db/migrate")
ActiveRecord::Migration.verbose = true
ActiveRecord::Migrator.migrate(sys_migration_dir)
end
def self.connected?
ActiveRecord::Base.connected? and
ActiveRecord::Base.connection_config[:adapter] == adapter
end
def self.disconnect
ActiveRecord::Base.connection_pool.disconnect!
end
# Really only for testing
def self.drop_db
ActiveRecord::Base.connection.drop_database(Byr.db_config)
end
end
end
Then, to initialize the db via the migrations:
module Byr
class << self
attr_accessor :install_dir
attr_accessor :db_dir
attr_accessor :config_dir
attr_accessor :config_file
attr_accessor :config
attr_accessor :db_config
attr_accessor :adapter
attr_accessor :database
def self.create_db
case db_config['adapter']
when /postgresql/
Byr::Db.create_pg(db_config)
when /sqlite/
Byr::Db.create_sqlite(db_config)
when /mysql/
Byr::Db.create_mysql(db_config)
else
raise ByrError "Your config.yml file specifies an unknown database adapter \'#{config['adapter']}\'"
end
end
def self.connect_db(reconnect = false)
unless reconnect
return true if Byr.connected?
end
ActiveRecord::Base.establish_connection(db_config)
end
def self.migrate
Byr::Db.migrate
end
def self.init(connect = true, adapter = nil)
adapter = canonicalize_adapter(adapter) if adapter
setup_db_config
if connect
create_db and connect_db and migrate
end
end
end
Some irrelevant parts of the code are omitted, but I hope this helps
someone else.
Much of the hard stuff comes from the rake tasks in rails.

Related

undefined method for calling methode in class

i try to call my methode check_table_exists for check my table. This methode is on my module, and i dont understand why i get this error .
i know #connexion is a Mysql2::Client instance, which doesn't include the module Sgbd. but i dont see how to include my methode ?
./yamlReadFile.rb:44:in `mysql_connection': undefined method `check_table_exists' for #<Mysql2::Client:0x000000033a7750> (NoMethodError)
$LOAD_PATH << '.'
require 'yaml'
require 'rubygems'
require 'mysql2'
require 'creatDatabase'
#binding.pry
class StreamMysql
include Sgbd
def mysql_connection(conf)
#connexion = Mysql2::Client.new(:host => conf['ost'], :username => conf['user'], :password => conf['password'], :table => conf['table'], :port => conf['port'])
if #connexion
puts check_table_exists
#connexion.check_table_exists
puts "connexion etablie"
else
puts "error connexion"
end
rescue Mysql2::Error => e
puts e.errno
puts e.error
#connexion.close
end
def read_config_file
config = YAML::load_file(File.join(__dir__, 'config.yml'))
conf = config['database']
mysql_connection(conf)
end
end
my module file with the mehode check_table_exists
module Sgbd
# class ModuleCreateDatabase
def create_database
end
def check_table_exists
query=("SHOW TABLES;")
end
end
It’s unclear why would you want to include your module in the foreign class, but it’s doable:
Mysql2::Client.include Sgbd
The line above should be put e. g. before class StreamMysql declaration.

How do I write this ruby class?

I am attempting to write a class to store bans. I want to check if a given IP is banned and return #ip, #time, #reason etc.:
class BannedIP
attr_reader :ip, :time, :reason
def initialize(ip, time, reason)
#ip = ip
#time = time
#reason = reason
end
def banned?(ip)
# What do I use here?
end
end
I need help with the part # What do I use here? so that I can do something like:
if b = BannedIP.banned? '10.10.10.10'
I'm not sure returning ip when you already know ip makes sense but anyhoo...
You could use a custom hash class:
class BannedHash < Hash
def ban(ip, time, reason)
self[ip] = {time: time, reason: reason}
end
def banned?(ip)
if self.include?(ip)
self[ip]
else
"ip: #{ip} not found."
end
end
end
Usage:
def main
b = BannedHash.new
b.ban("10.10.10.10", Time.now, "Some reason")
puts b.banned?("10.10.10.10")
puts b.banned?("11.11.11.11")
end
Output:
{:time=>2015-04-27 21:18:39 +1200, :reason=>"Some reason"}
ip: 11.11.11.11 not found.
Bit of a waste to have a full class for this unless you are planning on putting more in it. If it's just this then track it with an array, it would be easier and cost you less memory.
class BannedIP
attr_accessor :ip, :time, :reason, :status
def initialize(ip)
#ip = ip
#time = Time.new
#reason = reason
#status = status.nil? ? false : true
end
def ban(reason)
#reason = reason
#time = Time.new
#status = true
end
def unban
#reason = ""
#time = Time.new
#status = false
end
def banned?
return #status
end
end
if __FILE__ == $0
puts "ban the bad guy at 10.0.0.1 because he was cheating"
a = BannedIP.new("10.0.0.1")
a.ban "he is a cheater!!"
puts "The status of 10.0.0.1 is " + a.status.to_s
end
$ ruby stack1.rb
ban the bad guy at 10.0.0.1 because he was cheating
The status of 10.0.0.1 is true
Install mysql2 and active record gems
gem install mysql2
gem install active_record
Then execute following code snapshot
require 'active_record'
require 'mysql2'
SOURCE_CREDNTIALS = {
:adapter => "mysql2",
:host => "localhost",
:username => "root",
:password => "password",
:database => "banned_db"
}
class Banned < ActiveRecord::Base
ActiveRecord::Base.establish_connection(SOURCE_CREDNTIALS)
#attr_accessor :id, :ip, :banned_at, :reason
def self.banned?(ip)
where(ip: ip).count > 0
end
end
Now you can use this simple class as
user_foo = Banned.create(ip: '10.10.10.10', banned_at: Time.now, reason: 'violated terms and conditions')
Banned.banned?('10.10.10.10')
BannedIP.banned? method would return true or false depending upon if particular ip is banned or not.

Issue with Shoes setup

When i am running shoes.rb file, which contains code to install gem, it throws error.
Undefined method setup for Shoes:Class
Code:
Shoes.setup do
gem 'activerecord' # install AR if not found
require 'active_record'
require 'fileutils'
ActiveRecord::Base.establish_connection(
:adapter => 'postgresql',
:dbfile => 'shoes_app'
)
# create the db if not found
unless File.exist?("shoes_app.sqlite3")
ActiveRecord::Schema.define do
create_table :notes do |t|
t.column :message, :string
end
end
end
end
class ShoesApp < Shoes
require 'note'
url '/', :index
def index
para 'Say something...'
flow do
#note = edit_line
button 'OK' do
Note.new(:message => #note.text).save
#note.text = ''
#result.replace get_notes
end
end
#result = para get_notes
end
def get_notes
messages = []
notes = Note.find(:all, :select => 'message')
notes.each do |foo|
messages << foo.message
end
out = messages.join("n")
end
end
Shoes.app :title => 'Notes', :width => 260, :height => 350
The problem was using Shoes4, where the setup method was unimplemented.
Shoes4 now implements Shoes.setup for backwards compatibility reasons but you don't really need it, so it doesn't do anything except for printing a warning that you should rather do gem install gem_name instead of using Shoes.setup.

Uninitialized constant (NameError) when using FactoryGirl in module

Here's the error I'm getting when I try to run my tests with RSpec:
C:/Ruby193/lib/ruby/gems/1.9.1/gems/activesupport-3.2.11/lib/active_support/infl
ector/methods.rb:230:in `block in constantize': uninitialized constant User (Nam
eError)
I'm trying to run FactoryGirl with RSpec but without Rails. Here are the files that take part in the testing:
user_spec.rb
require 'spec_helper'
module Bluereader
describe User do
describe 'login' do
user = FactoryGirl.build(:user)
end
describe 'logout' do
end
describe 'create_account' do
end
describe 'delete_account' do
end
end
end
spec/spec_helper
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..'))
$LOAD_PATH.unshift(File.dirname(__FILE__))
require 'rspec'
require 'lib/bluereader'
require 'factory_girl'
FactoryGirl.find_definitions
spec/factories.rb
require 'digest/sha1'
FactoryGirl.define do
sequence(:username) { |n| "user-#{n}" }
factory :user do
username
encrypted_password Digest::SHA1.hexdigest('password')
full_name 'John Doe'
logged_in_at Time.now
logged_out_at 0
end
end
At this point I know that the factories.rb file is being loaded (I tried with the moronic print-debugging). When I remove the user = FactoryGirl.build(:user) line from user_spec.rb I get no errors (and the normal RSpec feedback telling me there are no tests, but no errors). If you are interested, here's my model:
require 'digest/sha1'
module Bluereader
class User < ActiveRecord::Base
has_many :categories, :foreign_key => :user_id
has_many :news, :foreign_key => :user_id
has_many :settings, :foreign_key => :user_id
attr_reader :full_name
class << self
def login(username, password)
encrypted_password = Digest::SHA1.hexdigest(password)
if not User.exists?(:username => username, :encrypted_password => encrypted_password)
user_id = User.id_from_username(username)
update(user_id, :logged_in_at => Time.now, :logged_out_at => 0)
end
end
def logout
update(current_user.id, :logged_out_at => Time.now)
end
def validate_account(username, password, full_name)
if username.empty? or password.empty or full_name.empty?
return 'Please fill in all the fields.'
end
if User.exists?(:username => username)
return 'That username is already in use.'
end
unless username =~ /^\w+$/
return 'Username field should contain only letters, numbers and underscores.'
end
''
end
def create_account(username, password, full_name)
encrypted_password = Digest::SHA1.hexdigest(password)
User.create(:username => username,
:encrypted_password => encrypted_password,
:full_name => full_name,
:logged_in_at => Time.now,
:logged_out_at => 0)
end
def delete_account
current_user.destroy
end
private
def id_from_username(username)
user = where(:username => username).first
user.nil? ? 0 : user.id
end
def current_user
where(:logged_out_at => 0).first
end
end
end
end
SOLUTION
The problem was that the class User was in a module, here's the solution:
factory :user, class: Bluereader::User do
You need to require the rails environment in your spec helper file. Add the following to spec/spec_helper.rb:
require File.expand_path("../../config/environment", __FILE__)
Update
Even if you're not using Rails, you'll still need to require the models in your spec helper.
Taken from the bottom of the question
The problem was that the class User was in a module, here's the solution:
factory :user, class: Bluereader::User do
For anyone clumsy like me, you may have FactoryGirl in your code where you meant to have FactoryBot

You have a nil object when you didn't expect it

I’m interested in the topic of Rails security and using Security on Rails. I'm on Implementing RBAC /page 142/ and i cannot get past the error in the subject.
Here is the code:
module RoleBasedControllerAuthorization
def self.included(base)
base.extend(AuthorizationClassMethods)
end
def authorization_filter
user = User.find(:first,
:conditions => ["id = ?", session[:user_id]])
action_name = request.parameters[:action].to_sym
action_roles = self.class.access_list[action_name]
if action_roles.nil?
logger.error "You must provide a roles declaration\
or add skip_before_filter :authorization_filter to\
the beginning of #{self}."
redirect_to :controller => 'root', :action => 'index'
return false
elsif action_roles.include? user.role.name.to_sym
return true
else
logger.info "#{user.user_name} (role: #{user.role.name}) attempted to access\
#{self.class}##{action_name} without the proper permissions."
flash[:notice] = "Not authorized!"
redirect_to :controller => 'root', :action => 'index'
return false
end
end
end
module AuthorizationClassMethods
def self.extended(base)
class << base
#access_list = {}
attr_reader :access_list
end
end
def roles(*roles)
#roles = roles
end
def method_added(method)
logger.debug "#{caller[0].inspect}"
logger.debug "#{method.inspect}"
#access_list[method] = #roles
end
end
And #access_list[method] = #roles line throwing following exception:
ActionController::RoutingError (You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.[]=):
app/security/role_based_controller_authorization.rb:66:in `method_added'
app/controllers/application_controller.rb:5:in `<class:ApplicationController>'
app/controllers/application_controller.rb:1:in `<top (required)>'
app/controllers/home_controller.rb:1:in `<top (required)>'
I'm using Rails 3.0.3 and Ruby 1.9.2. I'm storing session in database. In finally thank you for every advise.
It seems like you can't access #access_list in method_added. I would try
class << base
attr_accessor :access_list
#access_list = {}
end
Might not solve your particular problem, but otherwise you won't be able to call #access_list[method] = #roles if your access_list attribute is read-only.
I'm not sure if this is the problem, but this looks suspicious:
class << base
#access_list = {}
attr_reader :access_list
end
Shouldn't #access_list be a class variable ##access_list?
Your defining #access_list as a instance variable of the class but your accessing it in as a instance_variable of an instance of the class. The following should probably work:
module AuthorizationClassMethods
def access_list
#access_list ||={}
end
def method_added(method)
logger.debug "#{caller[0].inspect}"
logger.debug "#{method.inspect}"
access_list[method] = #roles
end
end
If you need Auhorization you might want to check out Cancan by Ryan Bates
https://github.com/ryanb/cancan

Resources