Unable to destroy a parent object - ruby

I have a large and complicated User model that looks something like this:
class User
class Link
include DataMapper::Resource
property :id, Serial, :key => false
belongs_to :follower, :model => 'User', :key => true
belongs_to :followed, :model => 'User', :key => true
end
include DataMapper::Resource
property :id, Serial
property :username, String, :required => true
has n, :links_to_followers, :model => 'User::Link', :child_key => [:followed_id]
has n, :links_to_followed, :model => 'User::Link', :child_key => [:follower_id]
has n, :comments
has 1, :profile_image
end
My problem is that Datamapper is not letting me Destroy it. I thought this was a result of Datamapper not wanting to destroy an object with un-destroyed child objects so I put in a method destroy_deep that calls destroy on the links_to_followers, links_to_followed, underlying comments, and the profile image (these are all destroyed correctly).
However, even if I call user.destroy after that, the user is not destroyed. There are no error messages of any kind. Is there some kind of cascading delete command that I am missing?

I resolved this.
Apparently to debug destroy, object.errors isn't useful. Instead track exceptions like:
begin
u.destroy
rescue Exception => e
p e
end
The solution was that one of the children fields didn't map back to User. I had a class Like that belonged to User, but User didn't have n Likes.

Related

Ruby: undefined method `delete', deleting parent and child data at the same time

I'm a newbie programmer, basically I'm trying to delete a record from a database table, however this record would be used on a child table with a composed primary key taking both from different tables.
This is the class of the table I need to delete the child data from.
class Detalle_Mapa
include DataMapper::Resource
storage_names[:default] = 'detalle_ma'
property :clave_mapa, String, :length => 2, :required => true, :key => true
property :clave_asig, String, :length => 10, :required => true, :key => true
property :clave_cuatri, String, :length => 2, :required => true
belongs_to :mapa_curricular, 'Mapa_Curricular', :child_key => [:clave_mapa], :key => true
belongs_to :cuatrimestre, 'Cuatrimestre', :child_key => [:clave_cuatri], :key => true
belongs_to :asignatura, 'Asignatura', :child_key => [:clave_asig]
end
So now when I try to delete data from the parent table, it won't delete it since the PK is being used on the child table for other data. If this was normal SQL sintax it wouldn't be a big deal but I'm having a hard time finding a way around it on Ruby.
This is the basic method that works when deleting data that's not being used as a FK.
delete '/deleteMapCurricular/:clave_mapa' do
#mapa = Mapa_Curricular.get(params[:clave_mapa])
if #mapa
#mapa.destroy
redirect '/catMapCurricular'
end
end
And this is one of the ways I've tried to delete the child data, which is clearly not right...
Detalle_Mapa.where(:clave_mapa => [params[:clave_mapa]]).delete_all
Is there an easy way to just delete a bunch of data from a database that I'm not aware of? Or what I'm I not getting about this ActiveRecords on Ruby?
I'm sorry if the question is ambiguous or if I'm not explaining myself clearly, I usually find everything on forums and there is no need to ask myself. Any help will be greatly appreciated :)
Sorry for not giving out the exact answer. But I can tell a concept in this answer
class User < ActiveRecord::Base
has_many :fruits, dependent: :destroy
end
class Fruit < ActiveRecord::Base
belongs_to :user
end
So in the above example, if you delete a user, the associated fruits for that user will also be deleted automatically.

DataMapper filter records by association count

With the following model, I'm looking for an efficient and straightforward way to return all of the Tasks that have 0 parent tasks (the top-level tasks, essentially). I'll eventually want to return things like 0 child tasks as well, so a general solution would be great. Is this possible using existing DataMapper functionality, or will I need to define a method to filter the results manually?
class Task
include DataMapper::Resource
property :id, Serial
property :name , String, :required => true
#Any link of type parent where this task is the target, represents a parent of this task
has n, :links_to_parents, 'Task::Link', :child_key => [ :target_id ], :type => 'Parent'
#Any link of type parent where this task is the source, represents a child of this task
has n, :links_to_children, 'Task::Link', :child_key => [ :source_id ], :type => 'Parent'
has n, :parents, self,
:through => :links_to_parents,
:via => :source
has n, :children, self,
:through => :links_to_children,
:via => :target
def add_parent(parent)
parents.concat(Array(parent))
save
self
end
def add_child(child)
children.concat(Array(child))
save
self
end
class Link
include DataMapper::Resource
storage_names[:default] = 'task_links'
belongs_to :source, 'Task', :key => true
belongs_to :target, 'Task', :key => true
property :type, String
end
end
I would like to be able to define a shared method on the Task class like:
def self.without_parents
#Code to return collection here
end
Thanks!
DataMapper falls down in these scenarios, since effectively what you're looking for is the LEFT JOIN query where everything on the right is NULL.
SELECT tasks.* FROM tasks LEFT JOIN parents_tasks ON parents_tasks.task_id = task.id WHERE parents_tasks.task_id IS NULL
You parents/children situation makes no different here, since they are both n:n mappings.
The most efficient you'll get with DataMapper alone (at least in version 1.x) is:
Task.all(:parents => nil)
Which will execute two queries. The first being a relatively simple SELECT from the n:n pivot table (WHERE task_id NOT NULL), and the second being a gigantic NOT IN for all of the id's returned in the first query... which is ultimately not what you're looking for.
I think you're going to have to write the SQL yourself unfortunately ;)
EDIT | https://github.com/datamapper/dm-ar-finders and it's find_by_sql method may be of interest. If field name abstraction is important to you, you can reference things like Model.storage_name and Model.some_property.field in your SQL.

How can I have two many-to-many relationships to the same model in DataMapper?

edit: Updated question to show my use of :child_key => [:comparison_id] as suggested in the comment.
I have two models that look like this:
class Comparison
include DataMapper::Resource
property :id, Serial
end
class Msrun
include DataMapper::Resource
property :id, Serial
property :name, String
end
Comparison come from comparing two sets of Msruns. I thought I would represent this through two many-to-many relationships from Comparison to Msrun, but I am beating my head against the wall as to how to do this in DataMapper. I know that many-to-many relationships are available by adding something like this:
has n, :whatevers, :through => Resource
However, this will only make one many-to-many relationship between the two models. I have also tried creating two join models and manually specifying the relationships, and manually specifying the child key for each relationship like so:
# Join model for the comparison-msrun many-to-many relationship.
class First
include DataMapper::Resource
belongs_to :msrun, :key => true
belongs_to :comparison, :key => true
end
# Join model for the comparison-msrun many-to-many relationship.
class Second
include DataMapper::Resource
belongs_to :msrun, :key => true
belongs_to :comparison, :key => true
end
class Comparison
include DataMapper::Resource
property :id, Serial
has n, :firsts
has n, :msrun_firsts, 'Msrun', :through => :firsts, :child_key => [:msrun_id]
has n, :seconds
has n, :msruns_seconds, 'Msrun', :through => :seconds, :child_key => [:msrun_id]
end
class Msrun
include DataMapper::Resource
property :id, Serial
property :name, String
has n, :firsts
has n, :comparison_firsts, 'Comparison', :through => :firsts, :child_key => [:comparison_id]
has n, :seconds
has n, :comparison_seconds, 'Comparison', :through => :seconds, :child_key => [:comparison_id]
end
Running automigrate results in the following error:
rake aborted!
No relationships named msrun_firsts or msrun_first in First
What am I doing wrong here? How can I make this work?
What you're observing, is the fact that relationships are stored in a set like object under the hood, more specifically, a set that uses the relationship's name as discriminator. So what happens in your case, is that the latter definition overwrites the former, as sets don't allow duplicate entries (and in our case, replace the older entry with the newer, for the set's purposes, identical one).
There are practical reasons for this. It makes no sense to declare two supposedly different relationships on one model, but name them the same. How would you distinguish them when trying to access them? This manifests itself in DM's implementation, where a method named by the relationship name gets defined on the Resource. So what DM ends up doing in your case of trying to add a duplicate to the set, is that it will just use the latter options to generate the implementation of that method. Even if it were to accept duplicate relationship names, the latter relationship would lead to an overwritten/redefined version of the same method, thus leaving you with the same net effect.
As a consequence, you would need to define differently named relationships on your models. When you think about it, it really makes sense. To help DM with inferring the model to use, you can pass the model name (or the constant itself) as the 3rd parameter to the has method, or as the 2nd parameter for belongs_to
class Comparison
include DataMapper::Resource
property :id, Serial
has n, :firsts
has n, :first_msruns, 'Msrun', :through => :firsts
has n, :seconds
has n, :second_msruns, 'Msrun', :through => :seconds
end
class Msrun
include DataMapper::Resource
property :id, Serial
property :name, String
has n, :firsts
has n, :first_comparisons, 'Comparison', :through => :firsts
has n, :seconds
has n, :second_comparisons, 'Comparison', :through => :seconds
end
Hope that helps!
As per the DataMapper docs
I believe you can do:
class Msrun
include DataMapper::Resource
property :id, Serial
property :name, String
has n, :firsts #This line could probably be omitted
has n, :first_comparisons, 'Comparison', :through => :firsts
has n, :seconds #This line could probably be omitted
has n, :second_comparisons, 'Comparison', :through => :seconds
end

Correct way to make a DataMapper association

I want to have a table of users. These users shall have n contacts and n messages..
My code is:
...
class User
include DataMapper::Resource
property :id, Serial, :key => true
property :nickname, String
has n, :contacts
has n, :messages
end
class Contact
include DataMapper::Resource
belongs_to :user
property :id, Serial, :key => true
property :authgiven, String
has 1, :user
end
class Message
include DataMapper::Resource
belongs_to :user
property :id, Serial, :key => true
property :data, String
end
#apply models (validation etc.)
DataMapper.finalize
...
There are no errors initializing DataMapper, but when I try to create a new User or whatever, save always returns false... Can someone please point out what is wrong?
I'm quite new to DataMapper, it always worked for me with simple tables without relationships, so I believe it has to do with the way I declared the 1:n relationship...
Hey you should remove that has 1, :user line from Contact model and you should be good.

Datamapper's hooks won't work

Can't understand why hooks don't work. I have the following model:
class DirItem
include DataMapper::Resource
# property <name>, <type>
property :id, Serial
property :dir_cat_id, Integer, :required => true
property :title, String, :required => true
property :price, Integer, :default => 0
belongs_to :dir_cat
has n, :dir_photos
has n, :dir_field_values
before :destroy do
logger.debug "==============DESTROYING ITEM ##{id}, TITLE
#{title}"
dir_field_values.destroy
dir_photos.destroy
end
end
When I call destroy method either from my app or irb, it returns false. The errors hash is empty, the log message doesn't print and the record won't delete.
This hook works for me (ruby 1.9.2 / DM 1.0.2):
require 'rubygems'
require 'dm-core'
require 'dm-migrations'
# setup the logger
DataMapper::Logger.new($stdout, :debug)
# connect to the DB
DataMapper.setup(:default, 'sqlite3::memory:')
class DirItem
include DataMapper::Resource
# property <name>, <type>
property :id, Serial
property :dir_cat_id, Integer, :required => true
property :title, String, :required => true
property :price, Integer, :default => 0
has n, :dir_photos
before :destroy do
dir_photos.destroy
end
end
class DirPhoto
include DataMapper::Resource
property :id, Serial
belongs_to :dir_item
end
DataMapper.finalize.auto_migrate!
#i = DirItem.create(:title => 'Title', :dir_cat_id => 1)
#i.dir_photos.create
#i.dir_photos.create
#i.dir_photos.create
#i.destroy
The DM logger reveals that each of the dir_photos are destroyed before the dir_item is. Instead of using hooks, you might want to look into using dm-constraints though. With something like:
has n, :dir_photos, :constraint => :destroy
you can be sure that all the dir_photos will be destroyed when the dir_item is destroyed, and this will also be enforced by database level foreign key constraints.

Resources