In this tutorial we'll extend what we did in Tutorial 1 and generate some models for use in our foosball-ladder project and use some associations to relate our data.
Since we are using devise
it can handle the login for us. At the top of application_controller.rb
add the line:
before_filter :authenticate_user!
Now if you go to your website, you should get an login screen. Follow the sign in and
you will see the batman.js starting guide like you did in the last tutorial.
Lets delete all of that, in batman/html/main/index.html
and replace it with some our own html. You could do something like this:
<div>
<h1>Welcome to the Foosball Ladder</h1>
</div>
We can login now (hurray), now lets add some context to that starting page. It would be nice to greet the user by their email. How do we do that in BatmanJS? We'll need to setup a batman model that mimics the ruby model. You can do it manually, or use the scaffold generator! Models will live in batman/models
and follow a similar naming scheme. To generate the model and some corresponding files run the following command:
$ rails generate batman:model User email:string
This should create a user.js.coffee
in the batman/models
directory. Let's expose the email
field so that we have something to show.
Add the following to the model:
@encode 'email'
This will tell batman that we will get an email
field in the json object describing the User
Now we'll have to pass the user into batman somehow, normally for something like the current user
we'll hook into devise
and grab it.
Open up batman.html.erb
and add the following line:
FoosballLadder.currentUser = FoosballLadder.User.createFromJSON( <%= current_user.to_json.html_safe %> );
Now load your main page again and pop open the javascript console ( you'll be using this a lot for debugging so get used to it!)
If you type in FoosballLadder, you'll see the top level object that describes your application (it's also known as Batman.currentApp
.) Now type in FoosballLadder.currentUser
. Now you'll see your User
model, with a bunch of batman things. In there there will be a attributes Hash
that will house all of your actual data. You can get at them via FoosballLadder.currentUser.get('email')
for example.
Great, we have our current user in batman land now!
Now how do we get that displayed in the HTML? We'll lightly introduce the concepts of data-bind
here. A data-bind
represents a two way binding to a corresponding javascript object. To demonstrate this we will add a message to display a welcome message to the current user.
Open main/index.html
and we'll add the following line:
<p>Hello!: <span data-bind="currentUser.email"></span></p>
There's a lot of power and flexibility in the binding system, but we'll gloss over it for now. The most important thing to know is that if currentUser.email
changes, the corresponding data-bind
will reflect the change as well, which is pretty awesome!
A foosball ladder isn't very good with some Teams, so lets add a model in rails called Team
, and associate it with the User
object. A Team
will have many Users
, and a User
will have one Team
.
To generate a default rails scaffold we can do the following:
$ rails generate scaffold Team name:string
$ rake db:migrate
Now we'll have a Team
object with a name. Lets make a batman object that corresponds to the ruby model.
$ rails generate batman:model Team
So how do we make sure that works? Lets introduce the concept of a data-foreach
binding. This works pretty much how you'd expect, I'll show an example below:
<div data-foreach-team="teams">
<p><span data-bind="team.name"></span></p>
</div>
This is a little like a for loop in another language. In the scope of that div, it will iterator over "teams", with the name "team" for each iteration. We'll also bind the name of the team so that we can see it. We'll see below how to populate "teams".
Use my populate.rake
file in the foosball-ladder project, I'll leave it out for brevity. It's just making a bunch of users and teams with random names.
Now how do we get that data into the binding? Open up main_controller.js.coffee
and look at the index
option.
Add the following code which will load the all the Team
s and set it on the controller, which the data bind will access.
index: (params) ->
FoosballLadder.Team.load (err,teams) =>
@set 'teams', teams
Cool, now you have some data in batman land from ruby land!
Lets add the has_many relationship to Team
, add the following to team.rb
has_many :users
But wait, we don't have the corresponding relationship on the User
object. We'll need to generate a migration that adds the key to the users
table.
$ rails generate migration AddTeamToUsers
Now open up the created migration in db/migrate
( the console will tell you the filename.) And add the following line in the change
method:
add_reference :users, :team, index: true
Now run the migration (rake db:migrate
if you forgot.) You should now have added a team_id
column to the users
table. Now add the relationship into user.rb
, in this case:
belongs_to :team
Ok that was a lot of rails, now back to batman. Let's add the relation to both the User
and the Team
. In user.js.coffee
add:
@belongsTo 'team'
and in team.js.coffee
add:
@hasMany 'users', foreignKey: 'team_id'
The foreignKey
specifies the table column we are associating to.
Let's say we'd like to print the names of the users in each team beside the team name. Notice that 'users' is a @hasMany
which means there's multiple users for each team.
Lets edit some HTML, to add the users for each team! It's similar to the team iteration, just nested.
I've shown it below:
<div>
<h1>Welcome to the Foosball Ladder</h1>
<div>
<p>Hello!: <span data-bind="currentUser.email"></span></p>
<div data-foreach-team="teams">
Team Name: <span data-bind="team.name"></span><br/>
Users:
<div data-foreach-user="team.users">
<span data-bind="user.email"></span></div>
</div>
</div>
</div>
As you can see we can grab the team
object from the iteration above and iterate over it's association
. You will need to add some extra code into the index action in the users_controller
, it's pretty easy to derive but here it is:
def index
if params[:team_id]
@users = User.where('team_id = ?', params[:team_id])
else
@users = User.all
end
respond_to do |format|
format.json { render json: @users }
end
end
Now you should be able to see a list the teams, with their users embedded.