Monthly Archives: October 2014

Weird Rails validates behaviour

I’m working on an application to maintain a set of temporary access accounts with some custom validators in the model (I summarise),

has_one :account_type
 validates :account_type_id, :presence => true, allow_blank: false
 validates :firstname, :surname, :uid, :passwd, :expiry_date, :owner, :presence => true
 validate :expiry_date_cannot_too_far_in_future
def expiry_date_cannot_too_far_in_future
  ed = AccountType.max_expiry?(account_type_id)
  if expiry_date > ed
    atn = AccountType.account_type_name(account_type_id)
    errors.add(:expiry_date, "can't exceed #{atn} account type limit of #{ed.to_s(:my_date)}.")
  end
 end

I also have a generic trap in my application_controller to rescue_from ActiveRecord::RecordNotFound with a flash error message.

But when using the site and forgetting to set the (required) account_type, I was returning to the index page with flash error rather than re-displaying the form and highlighting the missing field, despite the validator. Very odd.

A little bit of digging around and I realised the culprit being the ‘expiry_date_cannot_too_far_in_future’ validator which ist calling AccountType methods without an account_type_id value which generates the exception which is handled by the application_controller.

Two things to do here:

  1. Update the AccountType methods to return nil on RecordNotFound (which would have thrown a nil comparison error in the validator highlighting where the problem was)
  2. Add return false if ! account_type_id to the custom validator to prevent the exception being thrown in the first place.

SQL Server database ownership

Another memory aid.

After importing a database into SQL Server 2008, run the following query to re-associate the privileges of the user:

BEGIN
DECLARE @username VARCHAR(255),
 @query NVARCHAR(2000)
 
 DECLARE users CURSOR
 FOR SELECT sysusers.[name]
 FROM sysusers 
 JOIN master.dbo.syslogins sl
 ON sysusers.[name] = sl.[name] COLLATE Latin1_General_CI_AS
 WHERE sysusers.issqluser = 1 AND
 (sysusers.sid IS NOT NULL AND
 sysusers.sid <> 0x0) AND
 SUSER_SNAME(sysusers.sid) IS NULL AND
 sysusers.[name] <> 'dbo'
OPEN users
 FETCH NEXT FROM users INTO @username
 WHILE @@FETCH_STATUS = 0
 BEGIN
 SET @query = 'exec sp_change_users_login ' + '@Action = ''Auto_Fix'', ' + '@UserNamePattern = ''' + @username + ''''
 PRINT @query
 EXEC sp_executesql @query
 FETCH NEXT FROM users INTO @username
 END
 CLOSE users
 DEALLOCATE users
END

Apparently, in SQL Server 2012 this is no longer necessary.

Custom generators

Another aide-memoir (based on http://guides.rubyonrails.org/generators.html)

Having created templates in libs/templates/erb/scaffold (for views) and lib/templates/rails/scaffold_controller/controller.rb simply run,

rails generate scaffold User name:string …

and the views and controller will contain the templated files. There are a lot more we can do with generators but this is a good start.

Rails and JQuery autocomplete

My last post was for adding a tokeninput field to form that could be dynamically updated, but I’ve kinda used them inappropriately, for single-value text boxes.

So I got brave and decided to have a go at implementing the JQuery autocomplete widget to replace a slightly unwieldy and inflexible select list.

This time the Gemfile needs to include a bit more,

# Use jquery as the JavaScript library
gem 'jquery-rails'
gem 'jquery-ui-rails', '~> 4.0'
gem 'jquery-modal-rails'
gem 'jquery-tokeninput-rails'

(I have left in the modal and tokeninput items used elsewhere). Notice that I have fixed the jquery-ui-rails to version 4. This is because version 5 is out and that’s seriously incompatible with a lot of existing JQuery gems; I did experiment with using V5 (or jquery.ui.rails or something like that) but there was no way I could get any of the ui elements working. All my other RoR sites are based around v4 and I’m determined to maintain a consistent platform across the applications.

(I’m also using turbolinks – not sure why – and that seems to have its own problems with JQuery)

The general methodology with the route, controller action, JSON template are the same as before,but this time the JQuery call is,

$( "#phone_ext_number" ).autocomplete({source: '/phones/'+$("#phone_sub_department_id option:selected").val()+'/extension_list.json'});

With the _form partial containing,

<%= f.text_field :ext_number, {:size => 6, "data-pre" => Extension.select(:extension).where(id: @phone.extension_id).map(&:extension).to_json, :class => "phonebook"} %>

This is so much better than an auto-populated select list, because the data to be inputted falls within well-defined ranges and known beforehand. It’s easy to type in for the user, but the autocomplete only lists extensions not already assigned and uses the entered digits to further constrain the list, from which a user is free to select. It also means I can do a fair bit of data validation to prevent bad data going in; this application is replacing a legacy website that is notorious for inaccurate records and we’re working on the principle of making it harder to submit bad data, but easy to enter good stuff.

I’m quite pleased with the result and will use the autocomplete elsewhere, replacing some of the wayward tokeninput laziness.

JQuery for dynamic updates in Rails

Posted as an aide memoire because I’ll forget what project I’ve used it in and how to do it; I’m a part-time RoR coder, not my day job, though the stuff I am writing is important to other people’s operational work.

To create a select dropdown of items when another form element is changed.

Include some JQuery gems (I suspect this is where all the hard work is done and I’m just adding the easy stuff),

# Use jquery as the JavaScript library
gem 'jquery-rails'
gem 'jquery-modal-rails'
gem 'jquery-tokeninput-rails'

It could pay to be careful with this because of October 2014, version 5 of jquery-rails is out and comes with a lot of changes and breakage. Another post on this soon.

Declare a variable as an anonymous function call (closure). Then register a handler for the change event with another closure. Capture the selected value of the form element that triggers the event (perhaps with a sensible default), issue an JSON request to a controller method and loop through each of the returned values and append them to the select list. Finally register the closure variable to load on the page.

var ready = function() {
 $("#model_name_trigger_id").change(function() {
 var sd = $("#model_name_trigger_id option:selected").val();
 if (!sd) {
 sd = 1;
 };
 $("#model_name_field_id").html('');
 // make a GET call and replace the content
 // If not using a nested model, use,
 // $.getJSON( '/controller_name/list_of_items.json', function(data) {
 $.getJSON( '/controller_name/' + sd + '/list_of_items.json', function(data) {
 for (var x in data) {
 $("#model_name_field_id").append($('<option></option>').val(data[x].id).html(data[x].label));
 };
 });
 });
};
$(document).ready(ready);
$(document).on('page:load', ready);

Define a route for the JSON request to the controller

match 'controller_names/:id/list_of_items', :to => 'controller_names#list_of_items', :via => [:get, :post]

This will capture the JQuery ‘sd’ variable as an :id param in the controller for the nested model
but remove the ‘:id’ if not just using a single model.

Write a method in a rails controller

def list_of_items
  // Grab the params submitted in the JSON request
  i = params[:id]
  @items = Item.nested_model_scope(i)
  respond_to do |format|
    format.html
    format.json { render json: @items } 
  end
end

Then prepare a JSON template in the views,

json.array! items do |item|
  json.extract! item, :id, :label
end

This might not be 100% accurate in terms of the names but it’s enough to the serve the purpose of reminding me of the what I need to include when I do this next time.