Save callbacks not running after append - ruby

I'm having a problem trying to append a relationship using the << operator. If I save after the append, my callback does not seem to run.
I have two models:
Fabric
class Fabric
include DataMapper::Resource
property :id, Serial
property :name, String
property :fixed_color, Boolean
property :active, Boolean, :default => true
has 1, :cut
has n, :colors, :through => Resource
after :save, :add_to_fishbowl
def add_to_fishbowl
puts self.colors.size
end
end
Color
class Color
include DataMapper::Resource
property :id, Serial
property :name, String
has n, :cut
has n, :fabrics, :through => Resource
end
I create two colors and a fabric:
yellow = Color.new(:name => "yellow")
red = Color.new(:name => "red")
f = Fabric.create(:name => "tricot", :fixed_color => false)
If I used the append operator, my callback is not run:
f.colors << red
f.save
f.colors << yellow
f.save
puts f.colors.size
=> 0
=> 2
If I add arrays, it is:
f.colors = f.colors + [red]
f.save
f.colors = f.colors + [yellow]
f.save
puts f.colors.size
=> 0
=> 1
=> 2
=> 2
I'm running ruby 1.9.3p392 and data_mapper (1.2.0).

You missing relations, has n, :cuts
class Color
include DataMapper::Resource
property :id, Serial
property :name, String
has n, :cuts
has n, :fabrics, :through => Resource
end

Related

Retrieve records which have many-to-many association, using ruby and datamapper

I'm learning Sinatra, and I have read datamapper documentation and found this n to n relationship example:
class Photo
include DataMapper::Resource
property :id, Serial
has n, :taggings
has n, :tags, :through => :taggings
end
class Tag
include DataMapper::Resource
property :id, Serial
has n, :taggings
has n, :photos, :through => :taggings
end
class Tagging
include DataMapper::Resource
belongs_to :tag, :key => true
belongs_to :photo, :key => true
end
What I understood from the code above is that one photo may have many or zero tags, and a tag may have many or zero photos. How do I retrieve a list of photos with the tags associated to it already loaded. I know datamapper uses the lazy approach, so it does not automatically loads the associated classes (in this case photo.tag). So this:
photos = Photo.all
would result in an array with Photo objects without the tags. Is there a way to automatically retrieve it or do I have to iterate over the array and set that manually?
Thanks in advance!
I also have a database which has similar relations. Author, Post, Tag are main models, and Subscribedtag and Tagging are built through has n, :through.
class Author
include DataMapper::Resource
property :id, Serial
property :email, String, :unique => true
property :password, String
property :first_name, String
property :last_name, String
property :bio, Text
property :phone, String, :unique => true
property :twitter, String, :unique => true
property :facebook, String, :unique => true
property :show_phone, Boolean, :default => false
property :show_facebook, Boolean, :default => false
property :show_twitter, Boolean, :default => false
property :is_admin, Boolean, :default => false
property :this_login, DateTime
property :last_login, DateTime
property :session_lasting, Integer, :default => 0
has n, :posts
has n, :subscribedtags
has n, :tags, :through => :subscribedtags
end
class Post
include DataMapper::Resource
property :id, Serial
property :title, String, :required => true
property :body, Text, :required => true
property :is_blog_post, Boolean, :default => true
property :viewed, Integer, :default => 0
property :featured, Boolean, :default => false
property :created_at, DateTime
property :updated_at, DateTime
belongs_to :author
belongs_to :category
has n, :comments
has n, :taggings
has n, :tags, :through => :taggings
validates_length_of :title, :min => 3
validates_length_of :body, :min => 20
validates_format_of :title, :with => /\w/
#some other methods
end
class Tag
include DataMapper::Resource
property :id, Serial
property :name, String, :unique => true
has n, :taggings
has n, :posts, :through => :taggings
has n, :subscribedtags
has n, :authors, :through => :subscribedtags
validates_length_of :name, :min => 1
validates_format_of :name, :with => /\w/
# some other methods
end
class Tagging
include DataMapper::Resource
belongs_to :tag, :key => true
belongs_to :post, :key => true
end
class Subscribedtag
include DataMapper::Resource
belongs_to :tag, :key => true
belongs_to :author, :key => true
end
The way you've defined models, allows you to write queries, like that.
2.2.0 :016 > kant = Tag.get(25) # getting tag instance with id 25 and assign it to variable named kant
=> #<Tag #id=25 #name="İmmanuil Kant">
2.2.0 :017 > kant.posts
=> #returns post instances which has this tag.
2.2.0 :018 > kant.posts.count # count of posts with this tag.
=> 2
2.2.0 :021 > kant.posts.first.author.first_name
=> "Ziya" # check my author class and first_name attribute.
Let's say I want to retrieve the tag instances which has no posts.
a simple ruby command.
2.2.0 :024 > Tag.each {|tnp| puts tnp.name if tnp.posts.count == 0}
Latın
Python
Ruby
Sosializm
Hegel
Or retrieving tags based on posts.
2.2.0 :034 > p = Post.get(9)
=> #<Post #id=9 #title="Why we use Lorem Ipsum" #body=<not loaded> #is_blog_post=false #viewed=0 #featured=false #created_at=#<DateTime: 2015-08-02T23:14:04+05:00 ((2457237j,65644s,0n),+18000s,2299161j)> #updated_at=#<DateTime: 2015-08-02T23:14:04+05:00 ((2457237j,65644s,0n),+18000s,2299161j)> #author_id=1 #category_id=1>
2.2.0 :035 > p.tags
=> [#<Tag #id=19 #name="Bundesliqa">]
retrieve posts which has no tag.
2.2.0 :043 > Post.each {|pnt| puts pnt.id if pnt.tags.count.zero?}
8 #post with id has no tags
2.2.0 :044 > Post.get(8).tags.count
=> 0
you can also query via other attributes.
2.2.0 :046 > Tag.first(:name => "Lorem").id
=> 30
iterate over results
2.2.0 :050 > Tag.first(:name => "Lorem").posts.each {|lorempost| puts lorempost.title} # printing post titles which are tagged with lorem.
Get'em all
qwerty
I also associated authors with tags through Subscribedtags model, which I can easily check which author is subscribed to which tag, and vice versa.
2.2.0 :055 > z = Author.get(1)
=> # returns details of author instance
2.2.0 :056 > z.tags
=> [#, #, #, #]
or querying via Subscribedtag
2.2.0 :057 > z.subscribedtags
=> [#<Subscribedtag #tag_id=2 #author_id=1>, #<Subscribedtag #tag_id=4 #author_id=1>, #<Subscribedtag #tag_id=25 #author_id=1>, #<Subscribedtag #tag_id=30 #author_id=1>]
you can also define your own functions to utilize querying. I've defined a subscribed_tags method which returns an array of subscribed tags' names.
2.2.0 :058 > z.subscribed_tags
=> ["Həyat", "Məstan", "İmmanuil Kant", "Lorem"]
If I want to retrieve the first_name attribute of a random author, who is subscribed to tag named "Lorem",
2.2.0 :062 > Tag.first(:name => "Lorem").authors.sample.first_name
=> "Ziya"
As an answer to your 2nd question, yes, most times you have to iterate.
Because Photos.all return a collection of Photo object instances. And this instances individually has tag attributes, not the array consists of Photo instances.
if you call p = Photo.all; print p.tags; it will return all tags associated with all photos, which may or may not be the thing you want.
Feel free to ask more questions, if these are not enough.

Get associated records with Datamapper using Sinatra

I am currently working on a small backend, managing events with associated locations. Unfortunately, it's my first time i work with Ruby/Sinatra/Datamapper. After 3 hours trying to find a solution, i have to write this post.
I have defined two Resources:
class Event
include DataMapper::Resource
property :id, Integer, :key => true
property :name, Text
property :description, Text
has 1, :location
end
class Location
include DataMapper::Resource
property :id, Integer, :key => true
property :name, Text
property :latitude, Float
property :longitude, Float
belongs_to :event
end
This is my route to list all events:
get "/events/" do
#events = Event.all
content_type :json
#events.to_json
end
Is there a easy way the get the location as a parameter in the output of the associated event object?
Thank you very much for your support!
require 'rubygems'
require 'sinatra'
require 'dm-core'
require 'dm-migrations'
require 'dm-sweatshop' # for fixtures
require 'json'
DataMapper::Logger.new($stdout, :debug)
DataMapper.setup(:default, 'sqlite::memory:')
class Event
include DataMapper::Resource
property :id, Serial # will automatically become an auto-increment key
property :name, String # defaults to being max 50 char length
property :description, Text, :lazy => false # defaults to true
belongs_to :location # instead of one-to-one relation, which are rarely useful
end
class Location
include DataMapper::Resource
property :id, Serial
property :name, String
property :latitude, Float # perhaps decimal with less precision would suffice
property :longitude, Float
has n, :events
end
DataMapper.finalize.auto_migrate!
# Define some fixtures to have some data to play around with
def rand_float(min, max); rand * (max - min) + min end
Location.fix {{
:name => /\w+/.gen,
:latitude => rand_float(40.0, 43.0),
:longitude => rand_float(4.8, 5.4)
}}
Event.fix {{
:name => /\w+/.gen,
:description => /[:sentence:]/.gen[5..100],
:location => Location.pick
}}
100.of { Location.gen; Event.gen }
# Search events by properties of its association
get "/events/:location_name" do |location_name|
#events = Event.all(Event.location.name => location_name)
#events.to_json
end
# Return both objects in the same array
get "/events/" do
#events = Event.map {|e| [e, e.location] }
#events.to_json
end
Finally found the answer by myself when i took a deeper look at the to_json options
#events.to_json(:relationships=>{:location=>{}})

DateTime and DataMapper

Problem
I'm essentially trying to save +4 hours from now in DM. I think the DateTime is correct, but the DataMapper doesn't work properly. Any ideas?
Controller
p DateTime.now
t = DateTime.now + params[:hours] / 24
p t
reward = #player.work params[:hours]
action = Action.new(:player => #player, :type => 0, :completed_at => t, :reward => reward)
model
class Action
include DataMapper::Resource
property :id, Serial
property :type, Integer
property :completed_at, DateTime
property :reward, Integer
TYPES = {
0 => "Work",
1 => "Arena",
2 => "Quest"
}
validates_with_method :type, :method => :valid_type?
def valid_type?
if TYPES.key? self.type.to_i
true
else
[false, "Invalid action type."]
end
end
def get_type
case TYPES[self.type]
when "Work"
"you're working"
when "Arena"
"in the arena"
when "Quest"
"in a quest"
end
end
belongs_to :player
end
try:
DateTime.now + (params[:hours] / 24.0)

DataMapper Nested conditions: count issue

Here is the models:
class Foo
include DataMapper::Resource
property :id, Serial
has n, :foo_bars
has n, :bars, :through => :foo_bars
end
class Bar
include DataMapper::Resource
property :id, Serial
has n, :foo_bars
has n, :foos, :through => :foo_bars
end
class FooBar
include DataMapper::Resource
belongs_to :foo, :key => true
belongs_to :bar, :key => true
end
Inserting some data:
f = Foo.create
b1 = Bar.create
b2 = Bar.create
b3 = Bar.create
f.bars = [b1, b2, b3]
f.save
So, now I have one foo, three bars, and the foo has all the bars. Everything is fine.
Now I want to request some foos having bar#1 and bar#3:
Foo.all(Foo.bars.id => [1,3])
=> [#<Foo #id=1>] #ok
Foo.all(Foo.bars.id => [1,3]).count
=> 2 #why?
And here is the question: why array length is 1 and collection count is 2? How can I get both 1? I'd like to stick to the request with the nested conditions. Is it a bug or a misuse?
DM 1.1.0
Unfortunately you've hit a bug. I just reported an issue with your example attached: https://github.com/datamapper/dm-aggregates/issues/3
I think you should be able to get correct result by doing this for now:
Foo.count(Foo.bars.id => [1,3])

Maintaining DataMapper Ordered Many to Many Association

I have a DataMapper many to many relationship, friends, that needs to be kept in the
order. Whats the best way to maintain the order? I placed an order property
in the join model, but cannot seem to figure out a good way to update it.
My code:
class Person
include DataMapper::Resource
property :id, Serial
property :name , String, :required => true
has n, :friendships, :child_key => [ :source_id ]
has n, :friends, self, :through => :friendships, :via => :target
end
class Friendship
include DataMapper::Resource
property :source_id, Integer, :key => true, :min => 1
property :target_id, Integer, :key => true, :min => 1
property :order, Integer
belongs_to :source, 'Person', :key => true
belongs_to :target, 'Person', :key => true
end
a = Person.new
b = Person.new
c = Person.new
a.friends = [b, c] # Keep in this order
a.friends == [b, c] # This should be true
a.friends != [c, b]
a.save
Saving person a should create a friendship between a and b with order value = 1
as well as a second friendship between a and c with order value = 2.
I've been having trouble setting the order values. From what I can tell, the friendships don't actually get created until a is saved, so I can't update them before saving. And I can't updated them after saving because the order is lost.
Any ideas?
You could do this:
Friendship.create(:source => a, :target=> b)
Friendship.create(:source => a, :target=> c)
a.reload
a.friends == [b,c]
This should give you the desired effect, assuming that the order column in your database backend auto increments. If it doesn't then you need to add something like this to your Friendship model:
before :create do
self.order = Friendship.first(:order=>[:order.desc]).order + 1 rescue 0
end
If you care about the order of these friendships then you also need to do this in the Person model:
has n, :friendships, :child_key => [ :source_id ], :order => [:order.asc]

Resources