DataMapper has n with conditions - ruby

By any chance is it possible to create a conditional association with DataMapper?
For example:
I want the User have n Apps just if that user have the attribute :developer => true
something like this:
class User
include DataMapper::Resource
property :id, Serial
property :name, String, :nullable => false
property :screen_name, String, :nullable => false, :unique => true
property :email, String, :nullable => false, :unique => true, :format => :email_address
property :password, BCryptHash, :nullable => false
property :developer, Boolean, :default => false
#The user just gets apps if developer
has n :apps #,:conditions => "developer = 't'"
end
class App
include DataMapper::Resource
property :id, Serial
property :name, String, :nullable => false
belongs_to :user
end
I know that this would be possible by creating a subclass from User as a Developer::User and in that class, use the has n, but I really would like to know if its possible to make it directly on the association declaration.
Another way I also managed to do when using ARn was to extend the association and rewriting the methods for each action.
So on the extension module I could have something like this:
module PreventDeveloperActions
def new
if proxy_owner.developer?
super
else
raise NoMethodError, "Only Developers can create new applications"
end
end
# and so on for all the actions ...
end
But again, I really would like to avoid the use of this solutions if possible, but just if it's possible to perform a quick and direct method easily with DataMapper :)
Thanks in advance

At the moment, conditions that you include in the relationship declaration only apply to the target. So if the target model has an :active property, you can say things like has n, :apps, :active => true. Unfortunately you can't define relationships that are only active given the current state of the source (yet).
There are some proposals I'm considering to expand the Query logic in DM, but I'm unsure what the impact will be to the code, and what extra capabilities it will provide aside from this. It may be something we tackle after DM 1.0, since it also affects 50+ adapters and plugins.
STI is normally what I'd recommend for something like this, since it will allow you to define relationships that only exist for that type of object. Another approach would be to define the relationships as normal, mark the accessor/mutator methods as private, and then add a proxy method that does the equivalent of return apps if developer?.

Related

Tracking wins & losses with PSQL/DataMapper

I have a User class which allows people to register and play a game; I also have a Game class, which contains the logic of said game (RPS).
When people register, their information is held in a psql database. The informations is obtained using params. It looks like this:
Class User
attr_reader :weapon
include DataMapper::Resource
property :id, Serial
property :name, String, required: true
property :email, String, required: true, unique: true
property :password_digest, Text
attr_reader :password
attr_accessor :password_confirmation
validates_confirmation_of :password
validates_format_of :email, as: :email_address
has n, :games
The corresponding Game class, contains this DB logic:
class Game
include DataMapper::Resource
property :id, Serial
property :win, ?
property :lose, ?
belongs_to :user
My issue is that I really don't know how to keep a record of how many games the user has won/lost. Do I need (or should I have separate classes for wins and losses? What key type should I use (serial/int)? All I want is for 'win' or 'lose' to increment by one each time the player...well, wins or loses.
All help/knowledge shared is greatly appreciated.
Thanks.
One way to do it could be to add two integer columsn "wins" and "losses" to User, and make helper methods to increment:
property :wins, Integer, default: 0
property :losses, Integer, default: 0
# usage: user.increment(:wins) or user.increment(:losses)
def increment(type)
update({ type => (send(:type) + 1) })
end

One-to-one DataMapper association

I'm very new to DataMapper, and I'm trying to create models for the following scenario:
I've got a number of users (with a user name, password etc.), who can also be players or referees or both (so Single Table Inheritance is not an option). The base models would be:
class User
include DataMapper::Resource
property :id, Serial
# Other user properties go here
end
class Player
include DataMapper::Resource
property :id, Serial
# Other player properties go here
# Some kind of association goes here
end
class Referee
include DataMapper::Resource
property :id, Serial
# Other referee properties go here
# Some kind of association goes here
end
DataMapper.finalize
I'm not sure, though, what kinds of associations to add to Player and Referee. With belongs_to :user, multiple players can be associated with the same user, which doesn't make sense in my context. In RDBMS terms I guess what I want is a unique constraint on the foreign key in the Players and Referees tables.
How do I accomplish this in my DataMapper model? Do I have to perform the check myself in a validation?
There are different ways you could do this. Here's one option:
class User
include DataMapper::Resource
property :id, Serial
# Other properties...
has 1, :referee, :required => false
has 1, :player, :required => false
end
class Referee
include DataMapper::Resource
# DON'T include "property :id, Serial" here
# Other properties...
belongs_to :user, :key => true
end
class Player
include DataMapper::Resource
# DON'T include "property :id, Serial" here
# Other properties...
belongs_to :user, :key => true
end
Act on the referee/player models like:
u = User.create(...)
u.referee = Referee.create(...)
u.player = Player.create(...)
u.player.kick_ball() # or whatever you want to call
u.player.homeruns
u.referee.flag_play() # or whatever.
See if this works. I haven't actually tested it but it should be good.
The previous answer works other than :required => false is not recognized for has 1 properties.
It's also confusing because for has n properties, you can use new right on the property or otherwise treat it as a collection. In your example, you would be tempted to code
u = User.create ...
u.referee.create ...
But that fails in the case of has 1 because the property is a single value, which begins life as nil and so you have to use the method the previous answer indicates. Also, having to explicitly make the belongs_to association into the key is a little confusing.
It does seem to execute validations and have the right association actions (so u.save will also save the referred-to Referee). I just wish it were more consistent between has n and has 1.

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.

Need explanation of some Ruby syntax

I am new to ruby on rails, could anybody explain what does the symbol ':' mean, what would be 'validates' and 'create_table'? So much confused...
class Post < ActiveRecord::Base
validates :name, :presence => true
validates :title, :presence => true, :length => {:minimum => 5}
end
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.string :name
t.string :title
t.text :content
t.timestamps
end
end
end
The colon character (:) is the beginning of a syntax literal for a Ruby "Symbol":
:abc.class # => Symbol
"abc".to_sym # => :abc
Symbols are like strings but they are "interned", meaning the Ruby interpreter only has a single copy of it in memory despite multiple possible references (whereas there can be many equivalent strings in memory at once).
The 'validates' token in your example above is a class method (of something in the class hierarchy of the "Post class") that is being called with a symbol argument (:name) and a hash argument with a single key/value pair of :presence => true.
The 'create_table' token is a method which is being called with a single argument (the symbol ":posts") and is given a block which takes a single argument "t" (do |t| ... end).
:foo is a symbol, i.e. a constant string that is guaranteed to be unique. They are often used in Ruby to reference fields or methods.
validates is used in ActiveRecord to set a constraint on field's values.
validates :name, :presence => true means that field name must be always set (not null, undefined or empty) for all instances of Post (and corresponding table in DB). validates :title, :presence => true, :length => {:minimum => 5} means that field title must be always set and its length must be greater than 5.
In Ruby, the : means that it is a Symbol. A Symbol is sort of like a lightweight string that's specifically used as an identifier. For example, in a hash, you use symbols as keys that point to their respective values.
my_hash = {:key_1 => "A", :key_2 => "B"}
In your examples above, you use symbols to specify the properties of your Post model and the columns of your posts table.
Here are a few links for further reading on Ruby symbols:
http://www.randomhacks.net/articles/2007/01/20/13-ways-of-looking-at-a-ruby-symbol
http://glu.ttono.us/articles/2005/08/19/understanding-ruby-symbols
http://www.ruby-doc.org/core/classes/Symbol.html
http://www.robertsosinski.com/2009/01/11/the-difference-between-ruby-symbols-and-strings/
:foo is a "symbol" which is essentially an immutable string. It's major advantage is the fact that it doesn't allocate a new object every time you use it. If you were to use the string "name" every time you needed to use it, you would be making a new String object every time. However, if you use :name instead, you are using the same Symbol object every time (same in terms of pointer equality and object identity).
validates and create_table are both methods. In ruby, a method doesn't need parenthesis when called, so validates :foo is the same as validates(:foo). The methods come via inheritance and module mixins. validates is a class method put onto ActiveRecord objects during the inheritance, and create_table is a instance method
Ruby Documentation and Ruby on Rails Documentation:
"what does the symbol : mean?" Class:Symbol
Symbol objects represent names and some strings inside the Ruby interpreter. They are generated using the :name and :"string" literals syntax, and by the various to_sym methods. The same Symbol object will be created for a given name or string for the duration of a program‘s execution, regardless of the context or meaning of that name. Thus if Fred is a constant in one context, a method in another, and a class in a third, the Symbol :Fred will be the same object in all three contexts.
"what would be validates?" ActiveModel::Validations::ClassMethods
This method is a shortcut to all default validators and any custom validator classes ending in ‘Validator’. Note that Rails default validators can be overridden inside specific classes by creating custom validator classes in their place such as PresenceValidator.
"what would be create_table?" ActiveRecord::ConnectionAdapters::SchemaStatements
Creates a new table
(The link shows examples, showing SQL statements generated by this method.)
#Grimm
As you should know, everything is object on RoR. There are cases where you need to differentiate String from others as the memory handling techniques of String are different from other data structures. Colon : is a form of consideration of such type. They're just symbols like the one that makes hash entry on the memory!
Get used to that, it'll be interesting!! :)
as everybody is saying, : is a start of a symbol. Symbol is simple a string or variable name for me. In your example, :name is a variable/symbol that reflect one of the field name in Post table. Rails automatically creates these symbols when you create a Model class.
In Ruby, you can call a method/function and specify their parameters with/without the brackets. So,
validates :name, :presence => true
can be written as
validates(:name, :presence => true)
So, you are actually passing the :name and the true as the parameters for validates method
hope this help you see clearer about the methods calling in Ruby.
Same as validates, create_table is also a method.
Apart from the symbols, the point to be noted here is that in Ruby, we dont have to implicitly give {} to specify that an argument is a hash if it is the last argument. I mean by calling
validates :name, :presence => true
you are calling
validates :name, {:presence => true}
or
validates(:name, {:presence => true})
then it becomes clear that you are calling a method validates with 2 arguments, a symbol and a hash. If we ignore the symbol and substitute strings in place, like this:
validates("name", {"presence" => true})
it is pretty much similar to a method call in any other language. So, watchout for this as it is used almost in every helper tags Rails use.
For the other methods also you can see this:
validates(:title, {:presence => true, :length => {:minimum => 5}})
In the case of create_table, it is a method call with 2 arguments, a symbol and a block.

How to get DataMapper resource serial and key attributes in Ruby?

I`m trying to figure out how to get serial and key attributes set for Resource object. Basic method DataMapper::Resource.attributes returns a collection of properties, but it does not say anything about types. Of course i can check it via system call: obj.class, but cant understand how to get type information from resource instance.
Example:
class Foo
include DataMapper::Resource
property :id, Serial
property :title, String, :required => true
property :created_at, Time, :required => true
property :flagged, Boolean, :default => false
end
So, is there any way to get this information about internal types for resource?
Not long time ago, i figured it out by myself. All model fields are basically instances of DataMapper::Property
So, all you need is to call ModelName.properties and get options like :index, :key, etc.
Description: http://yardoc.org/docs/datamapper-dm-core/DataMapper/Property

Resources