Chaining datamapper relationships across different repositories - ruby

class A
include DataMapper::Resource
def self.default_repository_name
:alt_db
end
property :aid, Integer, :key => true
# other stuff
belongs_to :b, :model => 'B', :child_key => [ :bid ]
end
class B
include DataMapper::Resource
# this one is in the default repo
property :bid, Integer, :key => true
# other stuff
belongs_to :c, :model => 'C', :child_key => [ :cid ]
end
class C
include DataMapper::Resource
# this one is in the default repo
property :cid, Integer, :key => true
# other stuff
end
If I just have A and B, this works fine. If I add C, however, I get an error:
dm-core/model/property.rb:73:in `new': wrong number of arguments (4 for 3) (ArgumentError)
If I want to make a chain of relationships with DataMapper, so that I can give an ID in one place and get a piece of data that's, say, four tables away through a series of references to subsequent tables' primary key ID field, how can I do this?
EDIT: Digging into the DM source from the stack trace:
DataMapper.repository(other_repository_name) do
properties << klass.new(self, name, options, type)
end
That's where the error is raised. Indeed, in this case klass is a DataMapper Integer property, and it's initialize method only accepts three options (model, name, and an options hash).
This whole block is only executed because I'm using more than one repository, though B and C are in the same one so I don't know if that sheds any light on why it's erroring on the cid property.
EDIT2:
I have tried all permutations, and it appears that when you're chaining, once you cross a database-boundary, that must be the end of the chain. For example, since A is :alt_db and B is :default, B is as deep as I can go, regardless of whether C is :default, :alt_db, or a third option.
If instead both A and B were :default, or both were :alt_db, and then C were the opposite one, C would be as deep as I could go.
I don't understand this behavior really, though.

You found a bug actually. It's been fixed in master. You can try grabbing sources from git and see if it works.

Your code works fine for me.
irb(main):001:0> A.first.b.c
DEBUG - "(0.001168) SELECT "aid", "bid" FROM "as" ORDER BY "aid" LIMIT 1"
DEBUG - "(0.000337) SELECT "bid", "cid" FROM "bs" WHERE "bid" = 2 LIMIT 1"
DEBUG - "(0.000046) SELECT "cid" FROM "cs" WHERE "cid" = 3 LIMIT 1"
=> #<C #cid=3>
My gem is dm-core-1.1.0, you should check your version.

It turns out this was a small issue with DataMapper chaining across repositories. Submitted to them and it's allegedly been fixed already!
http://datamapper.lighthouseapp.com/projects/20609/tickets/1506-can-only-chain-up-to-first-time-changing-default-repository#ticket-1506-1

Related

Updating a property set as the key in DataMapper

Is it possible to update a property in DataMapper if :key is set to true?
Say, for example, I have a model set up like this:
class Post
include DataMapper::Resource
property :slug, Text, :unique => true, :key => true
# ...
end
and I made a new instance of this with :slug => "example-post-title".
I tried to update it by accessing the stored
#post = Post.get("example-post-title")
#=> #<Post #slug="example-post-title" ...>
#post.slug = "example-post-title-2"
#=> "example-post-title-2"
#post.save
#=> true
#post = Post.get("example-post-title-2")
#=> nil
but as you can see the slug was never updated. I also tried using the Post#update method:
#post = Post.get("example-post-title")
#=> #<Post #slug="example-post-title" ...>
#post.update(:slug => "example-post-title-2")
#=> true
#post = Post.get("example-post-title-2")
#=> nil
Looking in the database, the index column is not changed by either of these examples. It remains as example-post-title rather than example-post-title-2.
According to the docs, the Post#update method, similar to the Post#save method, should return true if the operation was successful, and false if it was not. It is returning true here, but it's not actually updating the record.
I've searched and searched and can't find anything about it on the Internet. Neither StackOverflow nor the DataMapper rdoc had anything about updating the key.
I know that I can have a unique Serial property and just get instances of Post using the slug (as in, make the Serial property the key instead of the slug), but I'm looking for a way to do it without that, if at all possible.
My hunch is that you can't update a key. According to the doc, they are protected against mass assignment:
Natural Keys are protected against mass-assignment, so their setter= will need to be called individually if you're looking to set them.
They don't talk about updating them but usually in "key => value" stores it is impossible or deprecated to update the key. I'd assume that's the case here as well, even though I can't find any hard evidence to give to you : /

Ruby Sinatra Datamapper/database confusion

I'm confused as to how database relationship works.
Say I have a Border Crossing('crossing'), which has two
Directions('north', 'south'), each of which direction has 2 types of lanes ('normal','fast'), each of which lane has 2 metrics (=data) ('delay','queue_length').
In reality there are several crossings, with more lane types and more metrics.
How the heck should I store that in a database? I've used databases before, but never did table joins or one-to-many or anything like that.
I came across Datamapper and since I'm learning how to us Sinatra I thought I'd give it a go.
In the tutorial (http://datamapper.org/getting-started.html), the "one-to-many" part just screamed "this is what you need", so I started fiddling around.
require 'data_mapper'
DataMapper.setup(:default, ENV['DATABASE_URL'] || "sqlite3://#{Dir.pwd}/development.db")
class Crossing
include DataMapper::Resource
property :id, Serial
property :name, String,:unique=>true
has n, :directions
end
class Direction
include DataMapper::Resource
property :id, Serial
property :direction, String,:unique=>true
belongs_to :crossing
has n, :lanes
end
class Lane
include DataMapper::Resource
property :id, Serial
property :lane, String
belongs_to :direction
has n, :datas
end
class Data
include DataMapper::Resource
property :id, Serial
property :name, String,:unique=>true
property :value, String
belongs_to :lane
end
DataMapper.finalize.auto_migrate!
I just thought this looked so elegantly put: "crossing has n directions, directions has n lanes, etc"
Then:
Crossing.create(:name => "crossing")
Direction.create(:direction => "north")
Direction.create(:direction => "south")
Lane.create(:lane => 'normal')
Lane.create(:lane => 'fast')
Data.create(:data => 'delay')
Data.create(:data => 'queue_length')
// now how do I retrieve find the data of a lane of a direction of a crossing?
Now, what I will input and retrieve all the time is the Data part. Does this whole thing make sense or I'm just not understanding what table associations are for? I know I could just have a gigantic object instead of this but I'm pretty sure that's a weird way of doing things.
#crossing = {
'crossing name' => {
:directions => {
:north => {
:normal => {
:delay => '10 min',
:queue => '100 m'
},
:fast => {
:delay => '1 min',
:queue => '10 m'
}
},
etc etc etc
}
and then access the data like #crossing[:north][:normal][:delay]....but I kinda feel like a database would be better?
Am I making any sense in any way? Anybody got some pointers for a young grasshoper?
I would rather go with this structure:
Data belongs to Crossing, Direction and Lane; it has properties for delay and queue
Direction has many Data, and has exactly two rows
Lane has many Data, and has exactly two rows
Crossing has many Data, and has many rows
The reason is, you don't want to repeat the strings "north", "south" etc in your database.
Then, first seed the database with constant tables:
Direction.create(direction: 'north')
Direction.create(direction: 'south')
Lane.create(lane: 'normal')
Lane.create(lane: 'fast')
Then you can make your crossings:
cool_crossing = Crossing.create(name: 'My Cool Crossing')
not_cool_crossing = Lane.create(name: 'My Not So Cool Crossing')
and add data points:
north = Direction.first(name: "north")
normal = Lane.first(name: "normal")
Data.create(
crossing: cool_crossing,
lane: normal,
direction: north,
delay: 10,
queue: 1
)
and retrieve data by:
all_data_for_cool_crossing = Data.all(
crossing: cool_crossing
)
or
data_for_cool_crossing_normal_north = Data.first(
crossing: cool_crossing,
lane: normal,
direction: north
)

ActiveRecord, find by polymorphic attribute

Having this:
class Event < ActiveRecord::Base
belongs_to :historizable, :polymorphic => true
end
user = User.create!
I can:
Event.create!(:historizable => user)
But I can't:
Event.where(:historizable => user)
# Mysql2::Error: Unknown column 'events.historizable' in 'where clause'
I have to do this instead:
Event.where(:historizable_id => user.id, :historizable_type => user.class.name)
Update
Code that reproduces the issue: https://gist.github.com/fguillen/4732177#file-polymorphic_where_test-rb
This has been implemented in Rails master and will be available in
Rails 4. Thanks.
– #carlosantoniodasilva
I do this:
user.events
This is a proper AR query, you can chain it with other scopes and stuff:
user.events.where(<your event conditions here>)
EDIT: AFAIK the other way around you must specify both fields (makes sense: you could have a user with id 4 and another thing with events, like a Party, also with id 4).
EDIT2: Regarding "Why does create work and where doesn't": create is more highlevel than where, because the former deals with "a complete model", while the latter manages things at the database table level.
ActiveRecord's create (AFAIK) uses a combination of new + update_param somewhere down the line.
update_param uses your model's xxxx= methods for assigning each individual property.
In your example, historizable= is a method built by the belongs_to expression. Since the belongs_to "knows" that it's polymorphic, it can deal with the type and id.
On the other hand, when you pass a hash to the where clause, the parameters there only refer to database fields. Rails provides scopes for "higher level" access:
class Event < ActiveRecord::Base
...
scope :by_historizable, lambda { |h| where(:historizable_id => h.id, :historizable_type => h.class.name) }
end
...
Event.by_historizable(user).where(<your other queries here>)
I've heard that this might change in Rails 4, and where might be more "intelligent". But I have not checked yet.
Try:
Event.joins(:historizable).where(:historizable => {:historizable_type => user})

Best practices for status addition in Rails

I need to add status for an object, and need a hint about the Rails way to do this. Somewhere I've seen status was added into the model, but already lost where it was.
By status, I mean something that tracks about the item state. Like {0: :ORDERED, 1: :CHANGED, 2: :SHIPPED, 3: :ARCHIVED} for order in store. Looks like it needs id that stored in DB, constant or symbol that I could use in code instead of integer id, and one or two human readable messages for UI
There's a couple simple ways to do this. If the names of the statuses are short, I'd do basically what Samy suggested and store them directly in the model. So, in your migration, you'd do
add_column :orders, :status, :string
Then, in your model, you can use the status method to retrieve the status. You'll want to make sure you only store valid statuses, so you the :inclusion validator something like this:
class Order
validates :status, inclusion: { in: %w(ordered changed shipped archived) },
presence: true
end
If the statuses are longer, you can do something very much like the above with a short name for each status, then add an additional method to give you the full status message
class Order
STATUSES = { 'ordered' => 'Order placed',
'changed' => 'A change has been made to the order',
'shipped' => 'The order has been shipped',
'archived' => 'The order has been archived' }
def self.valid_statuses
STATUSES.keys
end
validates :status, inclusion: { in: valid_statuses },
presence: true
def extended_status
STATUSES[status]
end
end
If the problem has some complexity (f.e: lots of states, the object changes its behavior when changing its state...), you could use the gem StateMachine.
MagicFieldNames might be what you are looking for, it has a discriminator type column that you can use for Single Table Inheritance.
If you want simpler, you can use a status column which value can equal ordered, changed, or shipped. You don't even need to create constants in Rails or such a thing.

Fetching values via foreign keys with DataMapper

I have two tables, nodes and terms.
The relevant fields in nodes are: nid (primary key) and value
In terms, they are: value, tid, and nid, where value and tid together are the primary key and nid is a foreign key referencing nodes.nid.
I want to add records to terms. I have the tid and nid, and the value I want to pull from the corresponding node - e.g. look up the value for a given nid in node and then put that as the value in terms.
A way to do this in SQL might be:
INSERT INTO terms(tid, nid, value)
values(mytid, mynid, (
select value from nodes where nid=mynid
));
Could somebody help me do this with DataMapper?
class Node
include DataMapper::Resource
property :nid, Serial, :key => true
property :value, Integer
end
class Term
include DataMapper::Resource
property :tid, Integer, :key => true
# how do I define nid and value?
end
# and then what do I give to Term.new or Term.create and how?
If anyone could point me to a good tutorial of DataMapper as well, I'd appreciate it. I've been using their online docs, but I've found the situations I find myself in are rarely covered there.
From your description the models you're looking for should be setup like that:
class Node
include DataMapper::Resource
property :nid, Serial
property :value, Integer
end
class Term
include DataMapper::Resource
property :tid, Integer, :key => true
property :value, Integer, :key => true
belongs_to :node, :child_key => :nid
end
You can work with these models like that:
# create a node
node = Node.create(:value => 123)
# create a term and associate it with the node
term = Term.create(:tid => 321, :node => node, :value => node.value)

Resources