I'm using DataMapper connected to an SQLite backend. I need to create a Unique index across my four belongs_to columns. Here is the table.
class Score
include DataMapper::Resource
property :id, Serial
property :score, Integer
belongs_to :pageant
belongs_to :candidate
belongs_to :category
belongs_to :judge
#What we want is a UNIQUE INDEX on the four properties!
end
Things I've done:
A unique index on the four via something like :unique_index => :single_score. This works only if you have a property already included.
validates_uniqueness_of, I think the scope only works for a 2-column unique index.
My current solution, which is to just create a dummy field "dont_mind_me", just so I can put :unique_index => single_score in it and everything works. Is this something that's okay to do?
Create an index using raw SQL, SQLite supports a unique index among the four fields.
Basically there are two parts of this question:
Is my solution okay, or should I find another one? I'm at wit's end dealing with what seems to be something trivial, even with raw SQL
How do I create an "after_create_table" hook in DataMapper? The hooks in the documentation only tell about post-CRUD data.
Related
I have a case where one model is related 2 other ones. I am trying to correctly setup the model relationships between these 3 models.
A simplified example... The first 2 tables are clients and invoices:
db.create_table(:clients) do
primary_key :id
String :name
end
db.create_table(:invoices) do
primary_key :id
String :description
Integer :balance
end
A third table, called files, contains records for files which can be related to either clients or invoices:
db.create_table(:files) do
primary_key :id
String :name
String :path
String :type # [image, pdf, word, excel]
end
There are 2 joiner tables to connect files to clients and invoices:
db.create_table(:clients_files) do
Integer :client_id
Integer :file_id
end
db.create_table(:files_invoices) do
Integer :invoice_id
Integer :file_id
end
The question is, how to correctly set up the relationships in the models, such that each client and invoice can have one or more related files?
I can accomplish this using many_to_many and has_many :through associations, however, this doesn't seem to be the right approach, because a given file can belong to only one customer or invoice, not to many.
I can also do this using polymorphism, but the documentation discourages this approach:
Sequel discourages the use of polymorphic associations, which is the
reason they are not supported by default. All polymorphic associations
can be made non-polymorphic by using additional tables and/or columns
instead of having a column containing the associated class name as a
string.
Polymorphic associations break referential integrity and are
significantly more complex than non-polymorphic associations, so their
use is not recommended unless you are stuck with an existing design
that uses them.
The more correct association would be one_to_many_through or many_to_one_through, but I can't find the right way to do this. Is there a vanilla Sequel way to achieve this, or is there a model plugin that provides this functionality?
With your current schema, you just want to use a many_to_many association to files:
Client.many_to_many :files
Invoice.many_to_many :files
To make sure each file can only have a single client/invoice, you can make file_id the primary key of clients_files and files_invoices (a plain unique constraint/index would also work). Then you can use one_through_one:
File.one_through_one :client
File.one_through_one :invoice
Note that this still allows a File to be associated to both a client and an invoice. If you want to prevent that, you need to change your schema. You could move the client_id and invoice_id foreign keys to the files table (or use a single join table with both keys), and have a check constraint that checks that only one of them is set.
Note that the main reason to avoid polymorphic keys (in addition to complexity), is that it allows the database to enforce referential integrity. With your current join tables, you aren't creating foreign keys, just integer fields, so you aren't enforcing referential integrity.
It was my understanding that when you destroy a parent document in Mongo that you also destroy its children and it will cascade down the chain until all referenced documents have been removed.
I have a collection structure like the following
class A
include Mongoid::Document
field :name, :type => String
has_many :bs
end
class B
include Mongoid::Document
field :name, :type => String
has_many :cs
end
class C
include Mongoid::Document
field :name, :type => String
end
I came across a situation in my code where I needed to delete one of Class A and all of its relevant documents. Since Each of these models were based of Mongoid I used the destroy_all method like so
a = A.where({'_id' => "123456789"})
a.bs.destroy_all
=> 'however many a's I had'
From reading the documentation I thought that each of the referenced documents would be removed aswell.
Unfortunately what has happened is all my class b's are gone and I have a bunch of orphaned class c's in my database.
So:
A) Assuming destroy_all doesn't do what I thought it would. Is there anything that can be used to actually delete a parent and all of its referenced documents in mongoid?
B) Although I performed this operation on a local machine, I would still like to know, is there any way to remove orphan documents from an altered collection?
You'll need to add:
:dependent => :destroy
to your associations.
See "DEPENDENT BEHAVIOR": http://two.mongoid.org/docs/relations/referenced/1-n.html
It was my understanding that when you destroy a parent document in Mongo that you also destroy its children
Only when it is a single document. You are showing a structure of many documents.
I am not a Ruby programmer and I have never used mongoid however it seems that destroy_all is essentially remove that matches more than one document as is supported by the docs: http://two.mongoid.org/docs/persistence/standard.html#destroy_all
Deletes all matching documents in the database given the supplied
conditions. See the criteria section on deletion for preferred ways to
perform these actions. This runs destroy callbacks on all matching
documents.
I am guessing that if you wish to remove the children as well you will be required to manually specify them since MongoDB has no relational behaviour and so has no ability to cascade your "relations" on its own.
The only real way, I would say, to remove orphaned documents is most likely the hard way by going through all distinct references to the documents parents within the child collection querying the parent collection to see if it exists. If it does not exist, remove it.
I have an existing legacy Firebird database with nonstandard table and field names.
I would like to write a Sinatra app that can access it and display information. I've seen stuff like dm-is-reflective that appears to work when a database has proper naming conventions, but how do I use DataMapper (or ActiveRecord whichever is the easiest) to access those tables?
For example, assuming I had these two tables:
Bookshelfs
shelf_id: integer
level: integer
created: timestamp
Book
id: integer
id_of_shelf: integer
title: string
pages: integer
Something like with odd naming conventions that don't follow any set pattern and where one table's record might "own" multiple entries in another table even though there is not foreign_key assigned.
How would you set up datamapper (or activerecord) to communicate with it?
Look in to this gem to get setup with ActiveRecord on Sinatra:
https://github.com/bmizerany/sinatra-activerecord
As for how to define the relations, activerecord can do this easily.
class Book < ActiveRecord::Base
belongs_to :bookshelf, :class_name => 'Bookshelf', :foreign_key => 'id_of_shelf'
end
class Bookshelf < ActiveRecord::Base
has_many :books, :class_name => 'Book', :foreign_key => 'id_of_shelf'
end
Assuming you figured out how to connect to your legacy database using ActiveRecord's Firebird adapter, the next thing I would do is define a view on top of each table, e.g.
CREATE VIEW books AS SELECT * FROM Book;
CREATE VIEW bookshelves AS SELECT * FROM Bookshelfs;
This way you can simply define models Book and Bookshelf in ActiveRecord as per usual and it will find everything in the right place inside the database.
I'm just getting started with DataMapper and I'm trying to figure out why you need to specify a has and a belongs_to.
For instance, look at the example on the DataMapper website. Isn't this redundant? If Post has n comments, then doesn't Comment automatically belongs_to post? Why do I have to specify this?
class Post
include DataMapper::Resource
property :id, Serial
has n, :comments
end
class Comment
include DataMapper::Resource
property :id, Serial
property :rating, Integer
belongs_to :post # defaults to :required => true
def self.popular
all(:rating.gt => 3)
end
end
You specify both sides of the relationship only when you want to use the methods generated by the extra specification. It's completely optional: If you never need to get to the Post from the Comment (e.g. #comment.post), you won't have to specify the belongs_to relation in Comment.
One advantage is that your instances are a bit cleaner because in Comment the additional methods are not autogenerated. On the other hand, if you need them, those extra methods would not bother you.
See also the documentation about associations in ActiveRecord.
That gives you the methods to access the relational object easily. Such as #post.comments #comment.post. I see what you mean, applying has_many could imply the belongs_to. Though given the developer overhead to add the belongs_to, is probably better than adding more system overhead to dynamically add the methods to the right class.
Another thing would be when using the has_many relation ship through another has_many relationship. That would cause odd belong_to relationships, and would probably cause issues with the SQL.
For example:
class User < ActiveRecord::Base
has_many :roles, :through => :roles_users
has_many :roles_users
end
The RolesUser being a join table that has belongs_to for the both the user and the role models. Implying the belongs to in this case would then add a belongs_to to the role model to a user. Nor does this not make sense, it also wouldnt work due to the lack of the database column being there. Of course this could be adjusted when the through option is there, but again this would highly raise the complexity of the code when it's not needed. As Daan said in his answer, you do not need both for it to work, it's optional.
I'd like to add to these good answers that if you have required dm-constraints (directly or via data_mapper) and use auto_migrate!, then belongs_to will automatically add foreign key constraints at the database level, whereas has alone will not do this.
e.g.:
require 'data_mapper'
class Post
include DataMapper::Resource
property :id, Serial
end
class Comment
include DataMapper::Resource
property :id, Serial
belongs_to :post
end
Produces this (via MySQL adapter):
~ (0.140343) CREATE TABLE comments (id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
post_id INT(10) UNSIGNED NOT NULL, PRIMARY KEY(id)) ENGINE = InnoDB
CHARACTER SET utf8 COLLATE utf8_general_ci
~ (0.234774) CREATE INDEX index_comments_post ON comments (post_id)
~ (0.433988) ALTER TABLE comments ADD CONSTRAINT comments_post_fk
FOREIGN KEY (post_id) REFERENCES posts (id) ON DELETE NO ACTION ON UPDATE NO ACTION
If you use has n, :comments within Post but choose not to include belongs_to :post within Comment, the post_id column in the comments table will still be created and indexed, but no foreign key constraint will be added.
I am new to Ruby and I read about a "polymorphic relationship".
What I read was over my head. Can you help me understand what a polymorphic relationship is in simple terms?
Expanding on the post suggested by Jinesh, the overall concept can be explained by this:
A belongs_to association is given by a field in a table that points to a record in another table. For example, if you want to model a Person and their address, you have
class Person
has_one :address
end
class Address
belongs_to :person #Has a field person_id
end
But then, if you have another model Company that will use addresses as well, you would have to share the field person_id. So you make it an addressable_id, and both Person and Company are "addressable" objects to the Address model. So, when you specify
class Person
has_one :address, :as => :addressable
end
you're telling Rails that whenever you search for a person's address, it will look on the addresable_id field on the Address table.
Have you looked at this one? http://wiki.rubyonrails.org/howtos/db-relationships/polymorphic
It would be good if you could ask specific questions which you are finding hard to understand so that the community can address that.