Home - StanfordBioinformatics/pulsar_lims GitHub Wiki
Welcome to the Pulsar LIMS wiki!
All models should be related to the User model in order to keep track of the creator of each record in the database.
The rails scaffold generator is a command to generate the model, the template controller, and the template views, all in one go. Say that we need to create a new model to represent a sequencing center in order to track where each library gets sequenced. We can create the model, controller, and views all in one go using the scaffold generator, i.e.
rails generate scaffold sequencing_center name:string address:text
rake db:migrate
This creates the SequencingCenter model with two fields - one for the name of the sequencing center, and the other for the address, using the string and text data types, respectively.
All models in the database should be linked to the User model, in order to track who created each record and when. The steps below demonstrate how to link the new SequencingCenter model to the User model:
- Add a "belongs_to :user" relationship in the SequencingCenter model at app/models/sequencing_center.rb.
- Add a "has_many :biosamples" relationship in the SequencingCenter model.
- Add a user attribute in the SequencingCenter model to serve as the foreign key for the User model:
- rails g migration add_user_to_biosample user:references
- rake db:migrate
Even though you've defined the relationship between User and SequencingCenter, you must still add the foreign key field to the SeqeuncingCenter model - you always add the foreign key in the model that declares the belongs_to relationshop. Lets do that now:
rails generate migration add_user_to_sequencing_centers user:references
This generates a migration in db/migrate that will add a user_id foreign key field to the SequencingCenter model. The contents of the migration are shown below:
class AddUserToSequencingCenters < ActiveRecord::Migration
def change
add_reference :sequencing_centers, :user, index: true
add_foreign_key :sequencing_centers, :users
end
end
Lets run the migration:
rake db:migrate
You can verify interactively in the rails console that the SequencingCenter model now has the user_id foreign key field defined:
> rails c
Loading development environment (Rails 4.2.0)
2.0.0-p598 :001 > SequencingCenter.column_names
=> ["id", "name", "address", "created_at", "updated_at", "user_id"]
Note that you can create a foreign key relationship when you create the model. For example, I could have issued this command when generating the scaffold:
rails generate scaffold sequencing_center name:string address:text user:references
Which would have avoided the need for a migration later on to add the user_id foreign key to the SequencingCenter model. Nonetheless, it's good to see both ways of doing it.
Register the new model with the ApplicationPolicy policy class that Pundit generated. Later, we'll add authorization calls to each of the actions in the SequencingCenter controller, which will call methods in the ApplicationPolicy class to determine whether a given user is authorized to perform a given action.
In sequencing_center.rb, add the following:
def self.policy_class
ApplicationPolicy
end
For any given model, if you want a user to be able to create a new record, edit an existing record, or list all records, then you'll need to make sure the views are laid out uniformly across the application in order to maintain a common presentation theme. Continuing with the example above, you'll need to make some standard updates to the SequencingCenter views that were generated as part of the scaffold that are located at app/views/sequencing_centers.
First, delete this line that is generated at the top of each the page:
<p id="notice"><%= notice %></p>
Add the following header tag block:
<header>
<h1><%= @sequencing_center.name %></h1>
</header>
The header tag has been styled in the application.css.scss stylesheet such that it extends the Bootstrap class called .page-header and makes a few positional and spatial tweaks in order to generate a nice page header. All of the HTML views should begin with a header tag.
Next, update the form submit button section at the bottom so that it looks like this:
<div class="form-actions">
<%= f.button :submit, class: "btn btn-primary" %>
</div>
I've added the Bootstrap class "btn-primary" to make this look like a blue button instead of a plain white one.
The show.html.erb will need two have two buttons added to it - one to edit the record, and another to destroy the record. First, add the same header tag as described earlier. We'll add more to this header tag here. Add the following, after the line with the nested h1 tag:
<ul class="actions">
<%= render partial: "application_partials/auth_edit_record", locals: {record: @sequencing_center} %>
<%= render partial: "application_partials/auth_destroy_record", locals: {record: @sequencing_center} %>
</ul>
The header should now look like this:
<header>
<h1><%= @sequencing_center.name %></h1>
<ul class="actions">
<%= render partial: "application_partials/auth_edit_record", locals: {record: @sequencing_center} %>
<%= render partial: "application_partials/auth_destroy_record", locals: {record: @sequencing_center} %>
</ul>
</header>
This adds two new buttons to the show view, but only if the user is authorized to see them based on the actions they perform. The first partial listed is to display a button in current view that will allow the user to edit the record. The second partial adds a button to allow the user to delete the record. These partials are located at app/views/application_partials/_auth_edit_record.html.erb and app/views/application_partials/_auth_destroy_record.html.erb, respectively. Authorization is handled by the Pundit gem, and these partials check whether the current user has permission to perform the edit and delete actions. For any action that the user isn't authorized to perform, the corresponding button will not be added to the view.
Next, add the following after the header tag block:
<%= render partial: "application_partials/record_meta", locals: { record: @sequencing_center} %>
This renders another partial, located at app/views/application_partials/_record_meta.html.erb, whose job is to add two fields at the top of the current view, which indicate the user that created the record and when.
Finally, delete the line in show.html.erb that contains the edit link show below:
<%= link_to 'Edit', edit_sequencing_center_path(@sequencing_center) %>
We don't need this anymore since now we have a nice and pretty button that manages this.
As you did in _form.html.erb, remove this line at the top of index.html.erb:
<p id="notice"><%= notice %></p>
Then add your header tag. The text within the nested h1 tag should contain the text "Sequencing Centers". Moreover, the text there within should be the plural form of the model class name, with spaces separating words. We're also going to add one action button in the header section - a button to create a new SequencingCenter record. To do so, edit the header section to look as follows:
<header>
<h1>Sequencing Centers</h1>
<ul class="actions">
<%= render partial: "application_partials/auth_new_record", locals: {policy_class: SequencingCenter} %>
</ul>
</header>
This uses another partial at app/views/application_partials/_auth_new_record.html.erb, which will add a button to the current view only if the current user has the permission to create a new SequencingCenter record.
Finally, delete the link at the bottom of the view that the scaffold generator made, which is:
<%= link_to 'New Sequencing center', new_sequencing_center_path %>
Add a header tag as shown below:
<header>
<h1>New Sequencing Center</h1>
</header>
Add the header tag such that it reads
<header>
<h1>Edit Sequencing Center</h1>
</header>
Delete the "Show" link at the bottom of the view that the scaffold generator made.
The controller is for the new SequencingCenter model is stored at app/controllers/sequencing_centers_controller.rb. First we'll update the create action to set the value of the current user. Add the following after the line in the create action that says @sequencing_center = SequencingCenter.new(sequencing_center_params)
:
@sequencing_center.user = current_user
Now, whenever a new record is created, it will keep track of the user that created it, and the show view can display that information. Note that I user the attribute user and not user_id. With foreign keys, the "_id" suffix is only added at the database table level. In the Ruby class (model) level, a method will be defined by the name you used in the migration, or when you created the SequencingCenter model if you added the reference when you generated the scaffold.
Next, we'll add the authorization checks in each of the controller actions, beginning with the show action. Here is what that action should look like:
def show
authorize @sequencing_center
end
The authorize helper comes from Pundit, and works by calling the show? method in the registered Pundit policy class. Recall from earlier that in the SequencingCenter model, we registered the policy class to be ApplicationPolicy? If we hadn't registered a policy class, then Pundit by default would have looked for one by the name of the current class we are in (SequencingCenter) with the word Policy appended to it. That is to say, it would have look for a file located at app/policies/sequencing_center_policy.rb with a class named SequencingCenterPolicy. But since we don't need fine-grained, model-specific authorization policy classes, we just stick with the default ApplicationPolicy policy class that I created (and modified) when I ran rails g pundit:install
.
Authorization in the new and edit actions is identically implemented:
def new
@sequencing_center = SequencingCenter.new
authorize @sequencing_center
end
def edit
authorize @sequencing_center
end
The former will look for a method named new? in the ApplicationPolicy class, whereas the latter will look for one named edit?. In fact, for each action, Pundit will look for a method within the associated policy class that is named after the action, and ends with "?".
In the create action, add the same authorization line after the line that sets the @sequencing_center variable, and before the line that calls current_user.
For the update and destroy actions, add the same authorization line as the first line in the method.
For the index action, use a different Pundit helper called policy_scope. The method should look like this:
def index
@sequencing_centers = policy_scope(SequencingCenter)
end