Second spec: View blog
When i visit home page, i could click on one blog, and see all entries in the blog.
require 'rails_helper'
feature 'guest can view blog' do
scenario 'guest can view blog', :js => true do
b = Blog.create(name: 'My first blog')
b.entries.create(title: 'First entry', content: 'React.js is awesome')
visit '/'
click_link 'My first blog'
expect(page).to have_content 'First entry'
expect(page).to have_content 'React.js is awesome'
end
end
There are some errors when you run this test because:
- There is no method entries for Blog instance.
- There is nothing to show when click on blog path
Let's fix them.
Firstly, we need to use gem database_cleaner
to use database truncation strategy. The reason is your javascript call will open new transaction to database, but the default transactional strategy of Rspec will keep data in all transactions independent. That's why you will se no data in a javascript interaction session. So let's add to Gemfile
, in group: :test
gem 'database_cleaner'
And install it:
bundle install
We also configure Rspec
to use database_cleaner:
The spec/rails_helper.rb
now have the following content:
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require 'spec_helper'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'capybara/poltergeist'
Capybara.javascript_driver = :poltergeist
require 'database_cleaner'
# Requires supporting ruby files with custom matchers and macros, etc, in
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
# run as spec files by default. This means that files in spec/support that end
# in _spec.rb will both be required and run as specs, causing the specs to be
# run twice. It is recommended that you do not name files matching this glob to
# end with _spec.rb. You can configure this pattern with with the --pattern
# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
# Checks for pending migrations before tests are run.
# If you are not using ActiveRecord, you can remove this line.
ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration)
RSpec.configure do |config|
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
config.fixture_path = "#{::Rails.root}/spec/fixtures"
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
#config.use_transactional_fixtures = true
config.use_transactional_fixtures = false
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
# RSpec Rails can automatically mix in different behaviours to your tests
# based on their file location, for example enabling you to call `get` and
# `post` in specs under `spec/controllers`.
#
# You can disable this behaviour by removing the line below, and instead
# explicitly tag your specs with their type, e.g.:
#
# RSpec.describe UsersController, :type => :controller do
# # ...
# end
#
# The different available types are documented in the features, such as in
# https://relishapp.com/rspec/rspec-rails/docs
config.infer_spec_type_from_file_location!
end
Let's generate model:
rails g model Entry title:string content:text blog_id:integer
rake db:migrate
rake db:test:clone
app/models/blog.rb:
class Blog < ActiveRecord::Base
has_many :entries, :dependent => :destroy
def as_json(options={})
super(:only => [:id, :name], :include => :entries)
end
end
app/models/entry.rb:
class Entry < ActiveRecord::Base
belongs_to :blog
def as_json(options={})
super(:only => [:title, :content])
end
end
config/routes.rb:
resources :blogs do
resources :entries
end
Add BlogView
in assets/javascripts/index.js.jsx:
var BlogView = React.createClass({
getInitialState: function(){
return {name: '', entries: []}
},
componentWillMount: function(){
$.ajax({
url: '/blogs/' + this.props.blog_id,
dataType: 'json',
type: 'GET',
success: function(data){
this.setState({name: data.name, entries: data.entries});
}.bind(this)
});
},
render: function(){
var x = this.state.entries.map(function(d){
if (d != null) {
return <li key={d.title}>
<p>{d.title}</p>
<p>{d.content}</p>
</li>
}
})
return (
<div>
<h1>{this.state.name}</h1>
<ul>
{x}
</ul>
</div>
)
}
});
The Router
class now have the code like that:
var Router = Backbone.Router.extend({
message: '',
routes : {
"" : "index",
"blogs/new" : "new_blog",
"blogs/:blog_id": "view_blog"
},
index : function() {
var self = this;
React.renderComponent(
<HomeView message={self.message}/>,
document.getElementById('new-blog')
);
},
new_blog : function() {
React.renderComponent(
<NewBlogView message={self.message}/>,
document.getElementById('new-blog')
);
},
view_blog: function(blog_id){
React.renderComponent(
<BlogView blog_id={blog_id}/>,
document.getElementById('new-blog')
)
}
});
And lastly, add method show
to controller app/controllers/blogs_controller.rb:
def show
@blog = Blog.find(params[:id])
render json: @blog
end
The test should pass now.