RailsCasts Pro episodes are now free!

Learn more or hide this

Tom Harrison's Profile

GitHub User: tomharrisonjr

Site: http://tomharrisonjr.com

Comments by Tom Harrison

Avatar

config/application.example.yml should be a file containing the skeleton of the variables you want to keep secret (don't put real values there); check this in, then in the deployed application rename the skeleton to config/application.yml and manually add the private data.

The purpose is to keep private data out of source control. The need here is clear when your SCM is public, but even in a small organization with multiple developer, one might want to set up an environment where not everyone has access to everything.

Avatar

Thanks @bdudney -- I had the same issue, I much prefer your second solution. Thanks for saving me some time.

Avatar

Hi Ryan --

I was working out a different problem and wanted to get to a known starting point, so ended up at this episode, which you have since revised. The code here doesn't work in a default Rails 3.2 environment for a couple of reasons which are resolved in the revised version.

I wonder if you might want to add a feature to your site, that provides a notification when an episode has been revised with a link to the new one? Or perhaps if you haven't yet revised an episode just a note. For example, before the revision, this one might have a note like "As of Rails 3.2 the code in this example does not work. See comments for solutions."

I have been using Rails since 2007, and it's a testament to your understanding of how all of us actually learn to code that you have picked topics that live for years!

You rock!

Tom

Avatar

In the earlier part of the episode, Ryan used a cattr_accessor which is a Rails shortcut for creating class methods.

(I think this is right, but anyone who knows better, please correct!) The reason to consider thread safety is that unlike instance variables, class methods are considered "static". Depending on how you have Rails deployed in a production environment this is not inherently thread-safe -- Ryan did a recent RailsCast on thread safety that explains more, but the short answer is whether or not something will be thread-safe depends on an external configuration that might change with a new version, or work differently in a few months, etc.

For me, at least, that's reason enough to add this little bit of safety.

I don't have a solid answer on the use of the instance variable, but I think it boils down to this: is @tenant_id that you reference at one moment necessarily the one you reference at another, or is it one on another thread? Again, the fact that this is a class method, not an instance method is what makes the difference.

(And if my explanation is wrong, I do hope someone can correct me so we all understand :-)

Avatar

It's not really all that different than what Ryan shows in this episode, or perhaps not at all -- there's something in the request that identifies which tenant is current (the current_tenant method, for example), and there's some sort of table that contains details about the tenant.

You can decide how you want to set up your web server -- you might set up a virtual host for each domain, or in the case of a small number, a single vhost that handles all domains (e.g. using Apache's ServerAlias directive). While it's also possible to let all requests through on the default host (sometimes called the "catch all host") and handle the ones you know, I prefer having a more declarative setup.

I also recommend having some sort of immutable unique text key (all lowercase, single word) that you use to identify the tenant in the database. Once you do that, you can use it as a kind of namespace that allows your app to pick up tenant-specific CSS selectors, locations for assets like logos, and so on.

Avatar

PostgreSQL schemas are a great thing, but I completely agree with Martin -- whether you have multiple databases, or a single database with schema namespaces, you're only solving one part of the problem -- the application still needs to be aware of multi-tenancy.

For example, the Admin back-end of an app I just finished usually needed to see information across tenants. Further, we wanted admins to be able to log in to our tenants' sites with their normal username (that had a role whose permissions allowed this).

And you're creating a larger problem managing the system, etc., and again, while PostgreSQL is (IMHO) by far the best RDBMS out there, why tie yourself to a specific database feature unless you really have to?

The bottom line is that building a multi-tenant app is an up-front design decision that needs to be baked in at many levels. It's harder to write, test, and so on, but that's just because it's a hard problem. Not impossible, just harder. This is most definitely not something I would suggest most apps farm out to a gem -- it's never that simple.

Tom

Avatar

Agree! And one other point we learned the hard way: we had used subdomains for environments for testing and staging (e.g. tenant1.staging.example.com) and didn't realized until too late that wildcard domains only give you one level of subdomain. A better strategy is to do something like tenant1-staging.example.com.

Avatar

Thanks for this link to DHH's presentation. The russian doll caching strategy is really the first I have seen (Rails or otherwise) that doesn't have a big long list of tradeoffs -- they figured out how to make it just work. It's worth backing up to watch all of DHH's stuff. Much of what he talks about is how to get all the benefits of über-responsive client-side performance without completely chucking Rails and moving to Backbone.JS

There are at least three whole topics are performance and caching for Ryan to cover. Memoization as covered here is a tiny little aspect of caching in comparison.

(Update: I am behind on my RailsCasts -- Ryan has done a lot of them already. Go Ryan!)

Avatar

Ryan -- you briefly mentioned the default format of datepicker was mm/dd/yyyy, and changed to something ruby can parse, like yyyy-mm-dd. I wanted the date format displayed as an American date in forms (and elsewhere).

I am sure I have an inelegant solution, so any recommendations are welcome.

To handle date parsing, I used the https://github.com/jeremyevans/ruby-american_date gem, which make Date.parse work as it did in ruby 1.9.2, accepting the mm/dd/yyyy format.

For the form field, I needed to handle both edit and create:

ruby
  <div class="field">
    <%= f.label :begins_on, "Start Date" %>
    <%= f.text_field :begins_on, :value => date_formatted(@my_model.begins_on || Time.now), :data => {:behavior => 'datepicker'} %>
  </div>

The date_formatted method is a helper in application_controller.rb like this

ruby
  helper_method :date_formatted
  def date_formatted(datetime)
    return nil if datetime.blank?
    datetime.strftime("%m/%d/%Y")
  end

and the JS is done (definitely inelegantly) in an application-wide file

CoffeeScript
$ ->
  `$(document).on("focus", "[data-behavior~='datepicker']", function(e){
      $(this).datepicker({"format": "mm/dd/yyyy", "weekStart": 1, "autoclose": true});
  });`

Hope this is helpful for someone with similar requirements.

-- Tom

Avatar

Hi -- I think the issue you're facing is that since ruby 1.9.3 Date.parse doesn't handle the ambiguity between dd/mm/yyyy and mm/dd/yyyy.

I dealt with this using a gem called american_date -- since your format is non-american, you may want to use the gem code (short, sweet) as an example.

Avatar

Three items to add: twitter bootstrap inline form, non-text fields, reset form

ruby
  <%= search_form_for @search, :html => {:class => "form-inline"} do |f| %>
    <%= f.label :account_name_cont, "Name" %>
    <%= f.text_field :account_name_cont, :class => "input-medium search-field" %>
    <%= f.label :payee_name_cont, "Payee" %>
    <%= f.text_field :payee_name_cont, :class => "input-medium search-field" %>
    <%= f.label :status_eq, "Status" %>
    <%= f.select :status_eq, Bill::STATUS.invert, :include_blank => true, :class => "input-medium search-field" %>
    <%= f.submit "Search", :name => nil, :class => "btn" %>
    <%= f.submit "Clear", :name => nil, :id => :q_reset %>
  <% end %>

with CoffeeScript to handle the reset button:

javascript
$ ->
  $("#q_reset").click ->
    $(".search-field").val('')
Avatar

Jasper, I might look at this the opposite way: hey, this site is doing something good!

Avatar

Timothy -- not sure what you mean regarding caching. This SO thread suggests it's not an issue (http://stackoverflow.com/questions/174348/will-web-browsers-cache-content-over-https).

I am, however totally sure about your issue with satellite internet. It makes you pine for the days of 28.8 modems :-) To be sure, the additional negotiation costs of SSL would become more evident with a really slow connection.

For most users, however, I would bet that slowness is trivial compared to some of the sloppy sites, shared servers, and other bad programming and hosting practices that are so prevalent. Seems to me the Rails community should be leading the way in the good practice of good, strong security, even if there's a minor cost.

Avatar

Awesome screencast, as always, and timely considering the recent LinkedIn and eHarmony hacks -- we should all use these instances as a reminder to get secure and locked down!

If you're using AWS (EC2) for hosting, there's a very interesting option using their elastic load balancer (ELB): install your certificate there and configure traffic on port 443 to be sent to an instance via port 80. This simplifies configuration in a lot of cases because you don't need to mess with Apache or Nginx or set up keys for each of your servers, and locally everything can run on simple HTTP.

There are a couple of cases where things can get tricky, but for our use-case, this method is working beautifully.

By the way, just because you have a load balancer doesn't mean you need to have more than one server, which is great for staging and other environments.

Avatar

This screencast is brilliant -- much needed by many (obviously, from the comments!), and good to have out there.

I am a big fan of running the app as a special user, rather than root, so glad to see that.

It's also funny -- I love the occasional remarks like "this probably won't work the first time" for Capistrano. Understatement is the best form of humor ;-)

Finally a vote for a fantastic VPS host I have used since 2006 -- RimuHosting has great prices, many options, and best of all, really competent people to help when things go wrong.

Avatar

If you're having problems getting Rails to save your date...

Several people have noted that Rails will silently to update the column is the date is in the wrong format, e.g. MM/DD/YYYY. Here's a little CoffeeScript function that you can use to extend the jQuery .datepicker() method:

CoffeeScript
datePicker = (selector) ->
  $real = $(selector)
  $display = $real.clone().attr({class: null, id: null, name: null})
  $real.hide()
  $display.insertBefore($real)
  $display.datepicker({altField: $real, altFormat: 'yy-mm-dd'})

The function just creates a new input, using the original as a template, hiding it, but still using as the altField that Rails will read on the PUT request, using a Rails-lovin' date format.

Call the function from my_model.js.coffee like so

CoffeeScript
$ ->
  datePicker("#model_datefieldname")
Avatar

Great screencast. Thanks!

A quick note: the close box on the alerts is not working (using Rails 3.2.2).

There's a small typo in the ASCII-cast; the anchor link for the close X is not ... closed with :-) But that didn't fix things.

Some have said this is because you need JQ 1.7, so I updated Gemfile to gem 'jquery-rails', '~> 2.0.1' and while the new jQuery is loading, the problem still exists.