Neo4jRB: Searching through all associations? - ruby

Consider this simple setup:
class Person
include Neo4j::ActiveNode
property :name, type: String
has_many :out, :follows, model_class: Person, rel_class: Friendship
has_many :in, :followed_by, model_class: Person, rel_class: Friendship
end
class Friendship
include Neo4j::ActiveRel
property :key, type: String
type 'friendship'
from_class Person
to_class Person
end
How would I search through all Friendships for those matching a condition? (e.g. Friendships of a certain key).
In an email, Brian Underwood points me to this snippet:
ModelClass.association_name(:node_var, :rel_var).where("rel_var = 'some_condition'")
I've tried playing around with it, but don't understand. Is ModelClass an ActiveNode or ActiveRel instance? What is :node_var and :rel_var?

If you want to search for every friendship that has a specific key property, you'd do that like this:
Person.all.follows.rel_where(key: your_key_var)
# OR
Person.all.follows(:f, :r).where('r.key = {key}').params(key: your_key_var)
These will both generate MATCH (p:Person)-[r:friends]->(f:Person), more or less, with the first example using auto-defined node identifiers and the second using f for the destination Friend node and r for the relationship, as given by the :f, :r arguments. After that, a to_a will return the friend at the END of the chain or you can call pluck with either :f or :r to return the given objects.
The model_class option always describes the NODE class on the other side of the association. In Brian's example, node_var and rel_var are generic names for the identifiers Cypher will use in the statement that it creates.

Related

Configure Mongoid relation to return objects sorted

I have two classes with a 1-n relationship. Like so:
class Band
include Mongoid::Document
has_many :members
end
class Member
include Mongoid::Document
field :name, type: String
field :joined, type: Date
belongs_to :band
end
Now when I call band.members I get the member objects. What I want is that if I call band.members.last to get the member who joined the last. I achieve this by defining the <=> method for Member and sort based on joined:
band.members.sort.last
How can I make this behavior default? I don't want to avoid the extra call to sort. Is this possible and if yes, how?
class Band
include Mongoid::Document
has_many :members, :order => :joined.asc
end

Mongoid: Retrieves all embedded documents

Suppose we have these models:
class Person
include Mongoid::Document
embeds_many :albums
end
class Album
include Mongoid::Document
embeds_many :photos
end
class Photo
include Mongoid::Document
end
What I want is to retrieves all Photo of a particular Person. Is there a mongoid/mongodb shortcuts or the only way is to iterate over person.albums and store all album.photos in a new array?
Thanks.
You have 2 ways to do this, one is through Mongoid, which, AFAIK, will inflate all objects.
Something like:
Person.only("albums.photos").where(id: '1').albums.map(&:photos).flatten
Or you can do it in Moped(driver) which will return only an array of photos.
Person.collection.find(id: "1").select("albums.photos" => 1).
first["albums"].map { |a| a["photos"] }.flatten
On the DB load, both dont make any difference, as they will yield the same query, only difference is that the first one will create way more objects than the second one.

How to define method names and object references before actually having to use them (Ruby)

Say I am keeping track of email correspondances. An enquiry (from a customer) or a reply (from a supporter) is embedded in the order the two parties are corresponding about. They share the exact same logic when put into the database.
My problem is that even though I use the same logic, the object classes are different, the model fields I need to call are different, and the method names are different as well.
How do I put methods and objects references in before I actually have to use them? Does a "string_to_method" method exists or something like that?
Sample code with commentaries:
class Email
include Mongoid::Document
field :from, type: String
field :to, type: String
field :subject, type: String
belongs_to :order, :inverse_of => :emails
def start
email = Email.create!(:from => "sender#example.com", :to => "recipient#example.com", :subject => "Hello")
from_or_to = from # This represents the database field from where I later on will fetch the customers email address. It is either from or to.
enquiries_or_replies = enquiries # This represents a method that should later be called. It is either enquiries or replies.
self.test_if_enquiry_or_reply(from_or_to, enquiries_or_replies)
end
def test_if_enquiry_or_reply(from_or_to, enquiries_or_replies)
order = Order.add_enquiry_or_reply(self, from_or_to, enquiries_or_replies)
self.order = order
self.save
end
end
class Order
include Mongoid::Document
field :email_address, type: String
has_many :emails, :inverse_of => :order
embeds_many :enquiries, :inverse_of => :order
embeds_many :replies, :inverse_of => :order
def self.add_enquiry_or_reply(email, from_or_to, enquiries_or_replies)
order = Order.where(:email_address => email.from_or_to).first # from_or_to could either be from or to.
order.enquiries_or_replies.create!(subject: email.subject) # enquiries_or_replies could either be enquiries or replies.
order
end
end
Judging by the question and the code sample, it sounds like you are mixing concerns too much. My first suggestion would be to re-evaluate your method names and object structure. Ambiguous names like test_if_thing1_or_thing2 and from_or_to (it should just be one thing) will make it very hard for others, and your future self, to understand the code laster.
However, without diverging into a debate on separation of concerns, you can change the methods you call by using public_send (or the private aware send). So you can do
order.public_send(:replies).create!
order.public_send(:enquiries).create!
string to method does exist, it's called eval
so, you could do
method_name = "name"
eval(method_name) #calls the name method

How to get record by ripple many association?

I want to save data into riak by ripple https://github.com/basho/ripple , and I create one model:
class Person
include Ripple::Document
property :name, String
many :friends, :class_name => "Person"
end
If there are two person model instances, how to know one person is other's friends or not ? like:
person.friends.get(somekey)
As person.friends is an array you can call it like this:
person.friends.include? otherperson

Mongoid: can I embed many and reference one of the embedded?

I have a list of games. Each one has an embedded list of scores. I'd like to keep a reference to the best score outside of the scores list.
class Game
include Mongoid::Document
field :best_score_id, type: Moped::BSON::ObjectId
...
embeds_many :scores
class Score
include Mongoid::Document
field :user, type: String
field :score, type: Int
I tried doing an belongs_to and a has_one but got an error message: "Referencing a Score document from the Game document via a relational association is not allowed since the price history is embedded." I suppose I can store the relevant bits of the Score in a hash called "best_score" but it makes more sense to me to embed many scores and then reference one of them as "Best". Is this possible?
You could do something like this -
Write a method to pick the best score in the game model class -
def best_score
score = scores.order_by(:score, :desc).limit(1)
if score.nil?
nil
else
score.first
end
end
And since scores are embedded in the game, there won't be any +1 query to the database also.

Resources