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
end
Edit 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.
HOST$ xnlogic ssh
$ xn-console
> PM.create_db 'my_app', 'db01'
> app = PM['db01']
> include app.console
> xget '/model/user'
> xpatch '/model/user/id/61', {email: 'john@doe.com'}
> xdelete '/model/user/id/61'
Create - PUT
Read - GET
Update - PATCH
Delete - DELETE
GET /model/user
GET /model/user/id/56
GET /model/user/properties
GET /model/user/id/42/properties/PROP_NAME1,PROP_NAME2
Get 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=15
Limit 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
end
Create lib/my_app/parts/tweet.rb
module MyApp
module Tweet
xn_part
property :text, type: :text
from_one :User
end
end
to_many :Tweet
GET /model/user/id/73/rel/tweets
GET /model/tweet/id/82/rel/user/properties/email
Get 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
end
The to attribute
GET /model/user/id/73/rel/follows
GET /model/user/id/73/rel/followed_by
Access 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_by
In 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
end
action :post_tweet,
args: [{:t => :text}] do |context, t|
self.post_tweet(t)
end
Define 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/tweets
So 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_tweets
In 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
end
module Route
def similar_users()
self.as(:user).follows.followed_by.is_not(:user)
end
end
Define (most of) the logic.
Declare the API method.
GET model/user/id/73/to/similar_users
GET model/user/id/73/to/similar_users/properties/username
GET model/user/id/73/to/similar_users/rel/tweets/properties/text
Use 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)
end
We'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/text
Add 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)
end
Since 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=lenora
Search for a user whose username is lenora
, filter: true
Enable 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=loren
Does lenora follow loren?
GET model/user/filter/username/rel/follows?username=lenora
Who 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" : "--"
end
Display properties are
GET model/user/properties/username,badge
Using 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_tweet
Edit 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
end
The full source code in lib/my_app/parts/tweet.rb
module MyApp
module Tweet
xn_part
property :text, type: :text
from_one :User
end
end
not_descriptive!