Brought to you by the good folks at
Introduction to
Create lib/my_app/parts/user.rb
module MyApp
module User
xn_part
property :username, type: :text
property :email, type: :text
end
endEdit lib/my_app/models.rb
client_models = {
#...
user: [User]
#...
}
Define the User part
Define a user model, composed of a single part, User.
> xput '/model/user', {username: 'cynthia', email: 'sin.t.yeah@gmail.com'}Load the application, and get ready to simulate API calls from the console.
> app = PM['db01']
> include app.console> xget '/model/user'> xpatch '/model/user/id/71', {email: 'john@doe.com'} > xdelete '/model/user/id/71'Create - PUT
Read - GET
Update - PATCH
Delete - DELETE
GET /model/userGET /model/user/id/56GET /model/user/propertiesGET /model/user/id/42/properties/PROP_NAME1,PROP_NAME2Get all objects of type foo.
Get a foo object by id.
Get the names of all properties defined in type foo.
Get values of specific properties
GET /model/user/first
GET /model/user?limit=15
GET /model/user?offset=15&limit=15Limit the number of results.
Edit lib/my_app/parts/user.rb
module MyApp
module User
xn_part
property :username, type: :text
property :email, type: :text
to_many :Tweet
end
endCreate lib/my_app/parts/tweet.rb
module MyApp
module Tweet
xn_part
property :text, type: :text
from_one :User
end
end to_many :TweetGET /model/user/id/73/rel/tweets
GET /model/tweet/id/82/rel/user/properties/emailGet related objects
You may want to create some data first.
What about CRUD operations for relations?
Not necessary. Update entities instead.
Edit lib/my_app/parts/user.rb
module MyApp
module User
xn_part
property :username, type: :text
property :email, type: :text
to_many :Tweet
to_many :User, to: :follows
from_many :User, to: :follows, from: :followed_by
end
endThe to attribute
GET /model/user/id/73/rel/follows
GET /model/user/id/73/rel/followed_byAccess the relation using the API
Tip: In the xn-console, don't forget to the changes.
MyApp.reload! to_many :User, to: :follows
from_many :User, to: :follows, from: :followed_byIn our example, creating a tweet will be a user's action:
Add the following to the User module in lib/my_app/parts/user.rb
module Vertex
def post_tweet(t)
tweet = self.app.create(M::Tweet, {text: t})
tweet.user = self
tweet
end
endaction :post_tweet,
args: [{:t => :text}] do |context, t|
self.post_tweet(t)
endDefine the logic:
Expose the action to the API:
At this point, lib/my_app/parts/user.rb should look this:
module MyApp
module User
xn_part
property :username, type: :text
property :email, type: :text
to_many :Tweet
to_many :User, to: :follows
from_many :User, to: :follows, from: :followed_by
action :post_tweet, args: [{:t => :text}] do |context, t|
self.post_tweet(t)
end
module Vertex
def post_tweet(t)
tweet = self.app.create(M::Tweet, {text: t})
tweet.user = self
tweet
end
end
end
end> xpost '/model/user/id/73/action/post_tweet', {t: "Hello World"}> xget '/model/user/id/73/rel/tweets/properties/text'> xget '/model/tweet/id/85/rel/user/properties/username'Perform an action by send a POST request with JSON data.
Verify that we can get the tweet from the user.
Verify that we can get the user from tweet.
As before, we will simulate API calls using the xn-console.
GET model/user/id/73/rel/followed_by/rel/followed_by/rel/tweetsSo far, we have seen the most basic API queries:
As queries become more complex (and interesting), URLs can become very long.
GET model/user/id/73/to/suggested_tweetsIn other words, turn something like
GET model/user/id/73/rel/follows/rel/followed_by/unique/rel/tweets/...Defining traversals, similar to actions, involves:
Extend the API with custom traversals.
Into
Add the following to the User module in lib/my_app/parts/user.rb
route_traversal :similar_users,
return_parts: :User do
self.similar_users.uniq
endmodule Route
def similar_users()
self.as(:user).follows.followed_by.is_not(:user)
end
endDefine (most of) the logic.
Declare the API method.
GET model/user/id/73/to/similar_usersGET model/user/id/73/to/similar_users/properties/usernameGET model/user/id/73/to/similar_users/rel/tweets/properties/textUse our custom traversal
Chain it with other queries
Recommend new users to follow.
Let's declare the API action
route_traversal :should_follow, return_parts: :User do
self.recommended_users_to_follow(10)
endWe'll create the recommend_users_to_follow method in the next slide.
This will allow us to make the following API calls:
GET model/user/id/73/to/should_follow
GET model/user/id/73/to/should_follow/rel/follows/unique/rel/tweets/property/textAdd the following inside Route module of the User part.
def recommended_users_to_follow(limit)
self.similar_users.follows
.except(self).except(self.follows)
.most_frequent(0...limit)
endSince the similar_users method doesn't remove duplicates.
Edit lib/my_app/parts/user.rb
module MyApp
module User
xn_part
property :username, type: :text
property :email, type: :text
# ...GET model/user/filter/username?username=lenoraSearch for a user whose username is lenora
, filter: trueEnable filter/search by the username property.
GET model/user/filter/username?username[regex]=.*x.*Using regular expressions, search for all users whose username contains the letter x.
GET model/user/filter/username/rel/follows/filter/username~1/count?username=lenora&username~1=lorenDoes lenora follow loren?
GET model/user/filter/username/rel/follows?username=lenoraWho does lenora follow?
Edit lib/my_app/parts/user.rb
module MyApp
module User
xn_part
property :username, type: :text
property :email, type: :text
# ... display :badge do
self.followed_by.count > 10 ? "Popular" : "--"
endDisplay properties are
GET model/user/properties/username,badgeUsing the API, we can get display properties like any other property.
Edit the User module in lib/my_app/parts/user.rb
action :post_tweet, args: [{:t => :text}], do |context, t|
self.post_tweet(t)
end def guard_post_tweet(errors, context, t)
if t.length > 140
errors.add(:t, "Tweet is too long.")
end
end> xpost '/model/user/id/73/action/post_tweet', {t: "X" * 140}
> xpost '/model/user/id/73/action/post_tweet', {t: "X" * 141}
=> {:response=>:validation_errors, ... , :errors=>{"t"=>["Tweet is too long."]}}
Test API calls in the console
module Vertex
#..., guard: :guard_post_tweetEdit the User module in lib/my_app/parts/user.rb
document :stats do |context|
self.statistics_report()
end def statistics_report
{'follows_count' => self.follows.count,
'followed_by_count' => self.followed_by.count}
end> xget '/model/user/id/73/document/stats'
=> {"follows_count"=>14, "followed_by_count"=>12}Test API calls in the console
module Vertex
#...The full source code in lib/my_app/parts/user.rb
module MyApp
module User
xn_part
property :username, type: :text, filter: true
property :email, type: :text
display :badge do
self.followed_by.count > 10 ? "Popular" : "--"
end
to_many :Tweet
to_many :User, to: :follows
from_many :User, to: :follows, from: :followed_by
action :follow, args: [{:user_id => :numeric}] do |context, user_id|
self.follow(user_id)
end
action :unfollow, args: [{:user_id => :numeric}] do |context, user_id|
self.unfollow(user_id)
end
action :post_tweet, args: [{:t => :text}],
guard: :guard_post_tweet do |context, t|
self.post_tweet(t)
end
document :stats do |context|
self.statistics_report()
end
route_traversal :similar_users, return_parts: :User do
self.similar_users.uniq
end
route_traversal :should_follow, return_parts: :User do
self.recommended_users_to_follow(10)
end
module Route
def similar_users()
self.follows.followed_by.except(self)
end
def recommended_users_to_follow(limit)
self.similar_users.follows.except(self).except(self.follows).most_frequent(0...limit)
end
end
module Vertex
def statistics_report
{'follows_count' => self.follows.count,
'followed_by_count' => self.followed_by.count}
end
def guard_post_tweet(errors, context, t)
if t.length > 140
errors.add(:t, "Tweet is too long.")
end
end
def post_tweet(t)
tweet = self.app.create(M::Tweet, {text: t})
tweet.user = self
tweet
end
def follow(user_id)
self.add_follows(self.app.graph.vertex(user_id, M::User))
end
def unfollow(user_id)
self.remove_follows(self.app.graph.vertex(user_id, M::User))
end
end
end
endThe full source code in lib/my_app/parts/tweet.rb
module MyApp
module Tweet
xn_part
property :text, type: :text
from_one :User
end
endnot_descriptive!