In a previous section, we created Tweet
objects directly using Ruby code (i.e. bypassing the API). Any idea why we did that? Is there a problem with the following API call?
jruby-1.7.18 :023 > xput '/model/tweet', {text: 'Learning about #xnlogic is fun!'}
The problem is that we do not relate between a tweet and the user who created it. In fact, in our data model, it doesn’t make sense to create a tweet without a user, as tweets are always created by users.
This issue can be solved by defining an action. In xnlogic, actions can be thought of as instance methods that can be called from the API.
Let’s see how we define our post_tweet
action …
post_tweet
actionWe need to update the code in lib/my_app/parts/user.rb
to define our post_tweet
instance method and expose it to the API.
Note: xnlogic has a consistent design philosophy - We give you “full access” when writing Ruby code that interacts with your data, but require you to explicitly expose the functionality to the API.
We will start by defining the instance method. Add the following code to the User
module.
module Vertex
def post_tweet(t)
tweet = self.app.create(M::Tweet, {text: t})
tweet.user = self
tweet
end
end
The code above defines the post_tweet
instance method that creates a tweet, and sets its from-one user relation to self
.
There are a few things to notice:
* The method is defined inside a module called Vertex
.
* This method changes the graph (by creating an object), and, therefore, will have to be called inside a transaction.
* For convenience, this method returns the created tweet to the caller.
Note: The statement
tweet.user = self
creates the relation between the user and the tweet. Alternatively, we could have defined the same relation with the statementself.add_tweets(tweet)
.
Next, we will expose our post_tweet
method to the API action, by adding the following code to the User
module.
action :post_tweet, args: [{:t => :text}] do |context, t|
self.post_tweet(t)
end
The code above declares an action called post_tweet
with a single parameter (whose name is t
and type is text
). It also defines the action as a Ruby block.
Note: Actions are automatically wrapped in a transaction.
Tip: Keep your action definition (i.e. the block) simple. Ideally, it should only contain a call to a corresponding instance method.
At this point, lib/my_app/parts/user.rb
should contain the following code.
module MyApp
module User
xn_part
property :username, type: :text
property :email, type: :text
to_many :Tweet
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
Actions are performed using POST requests.
action/ACTION_NAME
to the path of the request.For example, if we want user 73 to post the tweet 'Hello World'
, we make the following request.
jruby-1.7.18 :023 > xpost '/model/user/id/73/action/post_tweet', {t: "Hello World"}
=> [{:meta=>{:xnid=>"/model/job_result/id/86", :model_name=>"job_result", :rendered=>["record", "job"], :format=>"partial", :rel_limit=>50}, :id=>86, :name=>nil, :model_name=>"job_result", :description=>nil, :created_at=>2015-02-18 20:46:55 UTC, :status=>:complete, :messages=>nil, :request_url=>nil, :source_url=>"/model/user/id/73", :action_type=>"action", :action_name=>"post_tweet", :action_args=>{"t"=>"Hello World"}, :part_name=>"user", :value=>nil, :display_name=>"post_tweet"}]
We can verify that we can see our new tweet in the user’s tweets.
jruby-1.7.18 :024 > xget '/model/user/id/73/rel/tweets/properties/text'
[85, "Hello World"] [82, "Learning #xnlogic is fun"] [81, "No more snow! #wintersucks"]
We can also verify that we can access the user from the tweet.
jruby-1.7.18 :025 > xget '/model/tweet/id/85/rel/user/properties/username'
[73, "cynthia"]
Let’s see how we can define actions that will allow users to follow/unfollow one another.
As before, all of our code updates will happen in lib/my_app/parts/user.rb
, and we start by defining the following instance methods, inside the Vertex
module.
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
We continue by exposing these instance methods as API actions, and add the following code to the User
module.
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
At this point, lib/my_app/parts/user.rb
should contain the following code.
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 :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}] 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
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
We are now ready to test our actions (don’t forget to MyApp.reload!
, if necessary).
Let’s make user 73 follow a few users.
jruby-1.7.18 :026 > xpost '/model/user/id/73/action/follow', {user_id: 75}
jruby-1.7.18 :027 > xpost '/model/user/id/73/action/follow', {user_id: 77}
We can verify that the following relations have been created, and can be accessed in both directions.
jruby-1.7.18 :028 > xget '/model/user/id/73/rel/follows/properties/username'
[75, "randy.marsh"] [77, "cool.dude"]
Total: 2
jruby-1.7.18 :029 > xget '/model/user/id/75/rel/followed_by/properties/username'
[73, "cynthia"]
We should also verify that our unfollow actions works.
jruby-1.7.18 :030 > xpost '/model/user/id/73/action/unfollow', {user_id: 75}
jruby-1.7.18 :031 > xget '/model/user/id/73/rel/follows/properties/username'
[77, "cool.dude"]
Total: 1
jruby-1.7.18 :032 > xget '/model/user/id/75/rel/followed_by/properties/username'
Total: 0
/action/ACTION_NAME
to the URL path.Vertex
module.action
declaration.