A “Toggle-Friendship” widget on a user profile page

We take an imaginary forum application. When viewing another user’s profile page, there is often an “Add buddy” button, where you can add users to your friends list. Enough talk, let’s code that.

app/cells/buddy_cell.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class BuddyCell < Apotomo::StatefulWidget
 
  def transition_map
    {:toggle => [:_chumup, :_breakup]}
  end
 
  def toggle
    @user    = current_user
    @viewed  = param(:drinker)
 
    if @user.friends?(@viewed)
      return state_view :break
    end
 
    state_view :chum
  end
 
  def _chumup
    @viewed  = param(:drinker)  # i forgot we're stateful!
    @user.add_friend(@viewed)
 
    jump_to_state :toggle
  end
 
  def _breakup
    @user.remove_friend(@viewed)
    jump_to_state :toggle
  end
end

The widget is inserted in the tree as follows:

app/apotomo/application_widget_tree.rb

1
2
3
profile = cell(:user, :profile, 'user_profile')
  profile << main_col = section('main_col')
    main_col << cell(:buddy, :toggle, 'buddy_toggle')

I add the friendship widget with id buddy_toggle to a section, which itself is a child of the user_profile widget, that we soon will inspect.
Why the section? Well, maybe I like to split my profile into different parts. Sections are great for that.

Since we want to display a button either for chumming up or for breaking up the friendship, toggle is a simple decider state, rendering the respective button view (line 12 and 15).
The chumup view could look like:

app/cells/buddy_cell/chum.rhtml

<%= link_to_widget("Become friend", false, 
  :state => :_chumup ) %> with <%= @viewed.username %>!

Very simple. When the link is clicked, it advises our current widget buddy_toggle to go into the state _chumup. This is allowed by the state machine, since it is a valid transition (line 4). Further on, the state _chumup will execute its state method, add our new friend and jump back to the start state toggle (line 22).
The same applies for the other way round, the breakup.

So where do @user and @viewed come from (line 8 and 9)? Let’s forget about the currently logged in user and assume it is provided by some library call. More interesting is, how comes the currently viewed user (”drinker”) into our widget: Normally you would find out who’s viewed by inspecting the request parameters for something like “&user_id=9″. This is fine, but Apotomo provides a cleaner way to do this.

The call to param (line 9) will travel up the widget tree, asking the ascending widgets for :drinker. Since we await an object of type User, it would be nice, if someone up there could provide this object. Fortunately, the user_profile widget does this.


The User profile

app/cells/buddy_cell.rb

1
2
3
4
5
6
7
class BuddyCell < Apotomo::StatefulWidget
  def profile
    @drinker = User.find(param(:drinker_id))
    set_child_param(nil, :drinker, @drinker)
    nil
  end
end

The profile method provides the view for - guess what - the profile page of the viewed user, or “drinker”, as we call him here. The actual user object is retrieved by asking for the requested drinker id, which in turn is found out by querying param (line 3). This will end up in the root widget looking at the request parameters and returning the value.

Ok, when the profile page is opened, the user_profile widget saves the currently viewed user. How can the descending widgets like buddy_toggle access it? Well, they kindly ask for it.
By calling set_child_param (line 4), the profile widget is now the official provider for the :drinker object. Since we do not set a concrete widget id (the first argument is nil), this widget will provide the :drinker object for any descending widget. Wow.

Please note line 19 in BuddyCell. Here we ask for the object again, although it is already available as instance var in the state. It will retrieve the correct object, since user_profile provides it.

Tags: , ,

Leave a Reply