Using ActiveRecord inside another class - ruby

I'm trying to set up an IRC bot using ActiveRecord on the back end to handle all the data heavy lifting (probably overkill, but this is partly a learning experience for me :3)
The issue I'm running in to is that, after defining my database schema, later on in the same script when I try to reference the table I created, I get an error from the SQLite gem saying that it could not find the table.
Furthermore, my IDE (RubyMine) complains that it is "Unable to find the rails model for :notes association field"
Something tells me this would not be happening if I weren't constrained from operating as a class of the bot framework, but that is only a wild guess at this point.
What am I doing wrong here?
require 'cinch'
require 'active_record'
puts 'Memobox loaded'
class Memobox
include Cinch::Plugin
ActiveRecord::Base.establish_connection(
:adapter => 'sqlite3',
:database => ':memory:'
)
ActiveRecord::Schema.define do
create_table :notes do |table|
table.column :id, :integer
table.column :timeset, :DateTime
table.column :sender, :string
table.column :recipient, :string
table.column :text, :string
end
end
class Note < ActiveRecord::Base
has_many :notes
end
match(/note.*/, :prefix => "?")
def execute(m)
Memobox::Note.create(
:timeset => (Time.new).ctime,
:sender => m.user.nick,
:text => m.message,
:recipient => (m.message).split("_").at(1)
)
end
end
Error:
C:/Ruby193/lib/ruby/gems/1.9.1/gems/activerecord-3.2.8/lib/active_record/connection_adapters/sqlite_adapter.rb:472:in `table_structure': Could not find table 'notes' (ActiveRecord::StatementInvalid)

You should replace this
class Note < ActiveRecord::Base
has_many :notes
end
with
class Note < ActiveRecord::Base
end
The descendants of ActiveRecord::Base class represents single row in a table, not a whole table.
So to find some note by id you need just to call Note.find(123), where 123 is the id of note record in db table.

Thanks to everyone for the clarification on the use of has_many and the syntax, but my problem ended up being the use of an in memory table instead of an on disk one. Once I changed line seven to say
:database => 'notes.db'
instead of
:database => ':memory:'
and removed the has_many declaration from the Notes class (I did try it without doing this and got a different error) , everything works :)

Related

I do not understand a many_to_one <=> one_to_one model association

how to say? I do not understand what the sequel documentation tries to tell me about associations in case of a two models linked over a foreign key in one model A being a primary key in the other in a may_to_one case.
I always thought: If it is many_to _one in one direction it has to be one_to_many in the other... but the sequel provides a confusing chapter meant to clarify the topic with in addition an example I cannot follow.
It says in
"Differences Between many_to_one and one_to_one"
If you want to setup a 1-1 relationship between two models, where the foreign > key in one table references the associated table directly, you have to use
many_to_one in one model, and one_to_one in the other model. How do
you know which to use in which model? The simplest way to remember is
that the model whose table has the foreign key uses many_to_one, and
the other model uses one_to_one"
And continues to provide this strange example:
# Database schema:
# artists albums
# :id <----\ :id
# :name \----- :artist_id
# :name
class Artist
one_to_one :album
end
class Album
many_to_one :artist
end
In albums I may find several rows pointing to same artist... why shouldn't the artist point back to all his/her albums?
The sequel docu is crazy hard to read in many cases but this chapter reads easy but makes no sense for me:(
Same issue for me.
require "logger"
require "sequel"
db = Sequel.connect "postgres://localhost/postgres", :logger => Logger.new(STDOUT)
db.drop_table :artists, :cascade => true if db.table_exists?(:artists)
db.create_table :artists do
primary_key :id
foreign_key :album_id, :albums
end
db.drop_table :albums, :cascade => true if db.table_exists?(:albums)
db.create_table :albums do
primary_key :id
foreign_key :artist_id, :artists
end
class Artist < Sequel::Model(db[:artists])
one_to_one :album
end
class Album < Sequel::Model(db[:albums])
one_to_one :artist
end
artist_1 = Artist.create
album_1 = Album.create
artist_1.update :album => album_1
album_1.reload
puts album_1.artist.nil?
artist_2 = Artist.create
album_2 = Album.create
album_2.update :artist => artist_2
artist_2.reload
puts artist_2.album.nil?
We can fix this example by replacing any of one_to_one into many_to_one.
class Album
many_to_one :artist
end
In this case artist.album_id won't be used.
class Artist
many_to_one :albums
end
In this case album.artist_id won't be used.
The problem is that method names one_to_one and many_to_one were selected by underlying sequel logic and they are not user friendly.
You can create user friendly aliases for these methods. I prefer just to use it with comments. For example:
db.create_table :artists do
primary_key :id
foreign_key :album_id, :albums
end
db.create_table :albums do
primary_key :id
end
class Artist < Sequel::Model(db[:artists])
many_to_one :album # I have album_id foreign key
end
class Album < Sequel::Model(db[:albums])
one_to_one :artist # I don't have artist_id foreign key
end

Sequel - Query Many to Many Associations

I am having issues constructing the proper models, associations, and query for the following scenario and then returning results as JSON using Sequel with Ruby.
The database structure___
You can create a list of books. Each library contains books. Defined by the following:
db.create_table(:books) do
primary_key :id
String :name
String :author
DateTime :created
end
db.create_table(:libraries) do
primary_key :id
String :name
String :city
String :state
DateTime :created
end
db.create_table(:libraries_books) do
Integer :library_id
Integer :book_id
primary_key [:library_id, :book_id]
end
class Library < Sequel::Model(:libraries)
many_to_many :libraries_books, :left_key=>:library_id, :right_key=>:book_id, :join_table=>:libraries_books
one_to_many :libraries_books, :key=>:library_id
end
class LibraryBook < Sequel::Model(:libraries_books)
many_to_one :libraries
many_to_one :books
end
I am trying to determine the correct way to access all the book names for a given library. I initially tried to follow the Sequel Associations guide but was not able to figure out how I could use LibraryBook with associations to get all the books for a library and join on the Book model to get the proper columns.
After getting stuck with some of the methods described, I attempted to create my own query as such:
LibraryBook.select(:books.*)
.join_table(:inner, :libraries, :id => :library_id)
.join_table(:inner, :books, :id => :book_id)
.where(:library_id => 1)
Which seems to get me partially there. However, when I use the serialization extension, I get an error when the results are being converted:
undefined method `book_id' for #<LibraryGame:0x007fa9e904b470>
Any insight into that can be provided would be very helpful!
Try the following:
db.create_table(:books) do
primary_key :id
String :name
String :author
DateTime :created
end
db.create_table(:libraries) do
primary_key :id
String :name
String :city
String :state
DateTime :created
end
db.create_table(:books_libraries) do
foreign_key :library_id, :libraries, key: :id
foreign_key :book_id, :books, key: :id, index: true
primary_key [:library_id, :book_id]
end
class Library < Sequel::Model
many_to_many :books
end
class Book < Sequel::Model
many_to_many :libraries
end
Note renaming the libraries_books table to books_libraries and the use of the foreign_key directive for referential integrity. Conventions should allow things to just work.
Library[7].books # returns all books for library '7'
Or alternatively:
Book.where(libraries: Library[7])
Or multiple libraries:
Book.where(libraries: Library.where(id: [3,7,9]))
If sequel is not able to do the inflection for Library/Libraries then you may need to add your own inflection rule, eg:
Sequel.inflections do |inflect|
inflect.irregular 'Library', 'Libraries'
end

Ruby Motion: Removing a Ruby Class Completely from Object Space

I'm running into a brick wall testing class redefinitions and just don't know how to approach it. Here's the scenario I'm testing (this is not Core Data):
Application is run with a model in version 1
Eager programmer modifies model by adding/removing/redefining columns
Application is run with the model in version 2
Where I'm running into problems is in simulating the actual removal of the application from memory and rebuilding of it from scratch. This is important because a number of model-specific things are set up when the MotionModel::Model module is included, and that only happens once: When the module is included in the class. Here is what I felt might work:
it "column removal" do
class Removeable
include MotionModel::Model
columns :name => :string, :desc => :string
end
#foo = Removeable.create(:name=> 'Bob', :desc => 'who cares anyway?')
Removeable.serialize_to_file('test.dat')
#foo.should.respond_to :desc
Object.send(:remove_const, :Removeable) # Should remove all traces of Removeable
class Removeable
include MotionModel::model # Should include this again instead
columns :name => :string, # of just reopening the old Removeable
:address => :string # class
end
Removeable.deserialize_from_file # Deserialize old data into new model
Removeable.length.should == 1
#bar = Removeable.first
#bar.should.respond_to :name
#bar.should.respond_to :address
#bar.should.not.respond_to :desc
#bar.name.should == 'Bob'
#bar.address.should == nil
end
end
Unfortunately, Object.send(:remove_const, :Removeable) does not do what I'd hoped it would, and Ruby just thinks it can reopen Removeable and not run the self.included() method of the MotionModel::Model module.
Any ideas on how to emulate creation of this class from scratch in the context of a spec example?
I'd try working with anonymous classes (you'd have to tell MotionModel the table name).
Fictional example:
model_before_update = Class.new do
# This tells MotionModel the name of the class (don't know if that actually exists)
table_name "SomeTable"
include MotionModel::Model
columns :name => :string, :desc => :string
end
You do not remove the class at all, you just define another (anonymous) class with the same table name.
model_after_update = Class.new do
table_name "SomeTable"
include MotionModel::model
columns :name => :string,
:address => :string
end
Thinking of it, if there is a table_name setter like above, you don't even need to use anonymous classes, in case that does not work with RubyMotion.

Pony and Sequel associations conflict?

I've run into an issue when using Pony and Sequel in a Sinatra application.
Without Pony everything goes just fine, but just requiring Pony sequel's associations break.
Here's my models for a blog:
class Post < Sequel::Model
one_to_many :comments, :order => :date.asc(), :conditions => {:approved => 1}
set_schema do
primary_key :id
varchar :title
varchar :text
varchar :category
varchar :status
datetime :date
varchar :link
end
end
class Comment < Sequel::Model
plugin :validation_helpers
many_to_one :posts
attr_accessor :ip, :user_agent, :referrer, :permalink
set_schema do
primary_key :id
integer :post_id
varchar :author
varchar :comment
DateTime :date
varchar :email
varchar :url
varchar :approved
end
Then I call them like this in a route
post '/:link' do
#post = Post[:link=>params[:link]]
params[:comment].merge!( {
:ip => request.ip.to_s,
:user_agent => request.env['HTTP_USER_AGENT'].to_s,
:referrer => request.env['REFERER'].to_s,
:permalink => request.env['REFERER'].to_s
} )
begin
#comment = Comment.create params[:comment]
#post.add_comment #comment
rescue
#message = $!
end
#title = #post.title
haml :posts
end
I don't even have to call pony somewhere, just requiring it #post.add_comment #comment fails. It says
NoMethodError - undefined method `_add_comments' for #<Post:0x102b09890>:
/Library/Ruby/Gems/1.8/gems/sequel-3.21.0/lib/sequel/model/associations.rb:1078:in `send'
/Library/Ruby/Gems/1.8/gems/sequel-3.21.0/lib/sequel/model/associations.rb:1078:in `add_associated_object'
/Library/Ruby/Gems/1.8/gems/sequel-3.21.0/lib/sequel/model/associations.rb:743:in `add_comment'
Seems to me like a conflict with send? I don't even know how to start to debug it.
This is caused by an ActiveSupport issue, believe it or not. You should drop down to ActiveSupport 3.0.3 or manually require the default ActiveSupport inflections via:
require 'active_support/inflections'
Basically, after 3.0.3, ActiveSupport made it possible to load the inflector without the default inflections, which results in broken singularize and pluralize methods. The mail gem, which I'm guessing pony uses, is one of libraries that is known to be broken by this change.
The Rails developers apparently do not consider this a bug in ActiveSupport, but a bug in the libraries that use ActiveSupport.

ActiveRecord/sqlite3 column type lost in table view?

I have the following ActiveRecord testcase that mimics my problem. I have a People table with one attribute being a date. I create a view over that table adding one column which is just that date plus 20 minutes:
#!/usr/bin/env ruby
%w|pp rubygems active_record irb active_support date|.each {|lib| require lib}
ActiveRecord::Base.establish_connection(
:adapter => "sqlite3",
:database => "test.db"
)
ActiveRecord::Schema.define do
create_table :people, :force => true do |t|
t.column :name, :string
t.column :born_at, :datetime
end
execute "create view clowns as select p.name, p.born_at, datetime(p.born_at, '+' || '20' || ' minutes') as twenty_after_born_at from people p;"
end
class Person < ActiveRecord::Base
validates_presence_of :name
end
class Clown < ActiveRecord::Base
end
Person.create(:name => "John", :born_at => DateTime.now)
pp Person.all.first.born_at.class
pp Clown.all.first.born_at.class
pp Clown.all.first.twenty_after_born_at.class
The problem is, the output is
Time
Time
String
When I expect the new datetime attribute of the view to be also a Time or DateTime in the ruby world. Any ideas?
I also tried:
create view clowns as select p.name, p.born_at, CAST(datetime(p.born_at, '+' || '20' || ' minutes') as datetime) as twenty_after_born_at from people p;
With the same result.
Well, after more investigation, I found that:
MySQL works:
%w|pp rubygems active_record irb active_support date|.each {|lib| require lib}
ActiveRecord::Base.establish_connection(
:adapter => "mysql",
:username => "root",
:database => "test2"
)
ActiveRecord::Schema.define do
create_table :people, :force => true do |t|
t.column :name, :string
t.column :born_at, :datetime
end
execute "create view clowns as select p.name, p.born_at, (p.born_at + INTERVAL 20 MINUTE) as twenty_after_born_at from people p;"
end
class Person < ActiveRecord::Base
validates_presence_of :name
end
class Clown < ActiveRecord::Base
end
Person.create(:name => "John", :born_at => DateTime.now)
pp Person.all.first.born_at.class
pp Clown.all.first.born_at.class
pp Clown.all.first.twenty_after_born_at.class
Produces:
Time
Time
Time
Reading the sqlite3 adapter source code, I found out that it uses PRAGMA table_info(table_name) to get the type information, and that does not return the types for views:
sqlite> pragma table_info('people');
0|id|INTEGER|1||1
1|name|varchar(255)|0||0
2|born_at|datetime|0||0
sqlite> pragma table_info('clowns');
0|name|varchar(255)|0||0
1|born_at|datetime|0||0
2|twenty_after_born_at||0||0
Therefore it may be a limitation of the adapter or just a sqlite3's views limitation. I have opened a ticket for ActiveRecord:
Also, quoting this mail in sqlite-users:
RoR should be using the
sqlite3_column_type() API to determine
the type of the values returned from a
query. Other APIs like
sqlite3_column_decltype() and pragma
table_info are returning other
information, not the type of the
result value.
Well, basically there is no datatime type in SQLite as opposed to MySQL. In your example you explicitly define types for the table but do not specify types for the view. That might be the problem. Can not check it since I have never touched ruby.

Resources