MySQL berücksichtigt keine Gross- und Kleinschreibung bei select?

Manchmal habe ich echt das Gefühl, ein paar Jahre ganz feste kognitiv eingeschränkt gewesen zu sein. Per Zufall stellte ich heute fest, was das „ci“ bei den Datenbank Collations in Mysql heisst.

<musik typ="leicht-nervig" creator="Synthesizer" dauer="10" />
Moderator: Wahaaas bedeutet das Zeh Ih (_ci) bei den Datenbank Collations in MySQL?
Kandidat: Hmmm, uff, kann ich es im Kontext haben?
Moderator: Natürlich! Zeh Ih wie in utf8_general_ci.
Kandidat: Ui, das weiss ich nicht, gibt es Auswahlmöglichkeiten?
Moderator: Ja sicher:

  1. collation identifier
  2. childish internet
  3. clown institut
  4. corporate identity
  5. case insensitive


Und die Lösung: CASE INSENSITIVE

Was bedeutet _ci in der Praxis?

Tabelle:

+----+----------+----------+------------------------------------------+
| id | username | salt     | password                                 |
+----+----------+----------+------------------------------------------+
|  1 | Admin    | 2IT8mKiX | 8fd334609270a4b78c536dbdee4182b5b1d00bb7 |
+----+----------+----------+------------------------------------------+

Tja, folgender Tabelleneintrag wird gefunden mit folgenden Selects:

SELECT * FROM users WHERE username='Admin'; -- Klar, logo
SELECT * FROM users WHERE username='ADMIN'; -- Ui
SELECT * FROM users WHERE username='admiN'; -- Uiui
SELECT * FROM users WHERE username='admin'; -- Uiui, huch

Und das impliziert wiederum, dass ein User, der sich ADMIN nennt, unter Umständen vor dem eigentlichen Admin gefunden wird. Das ist doch eher hässlich. Wieso ist mir das nicht früher aufgefallen..?

Und die Lösung?

Die Collation ändern! Von utf8_general_ci zu utf8_bin und schon verhält sich die Datenbank erwartungskonform.

Was bedeutet dies für Rails?

Zum Einen sollte die Collation in der Datei config/database.yml festgelegt werden:

development:
  adapter: mysql
  encoding: utf8
  collation: utf8_bin
  database: lomas_development
  pool: 5
  username: lomas
  password: sagichnit
  socket: /var/run/mysqld/mysqld.sock

Leider wird die Collation bei Tests in Ruby on Rails nicht berücksichtigt! Das treibt mich zur Verzweiflung, echt!

Ausserdem gibt es noch etwas zu beachten bei validates_uniqueness_of. validates_uniqueness_of ist case-sensitive. Das heisst, auch hier werden admin und ADMIN akzeptiert. Erst ein weiterer Parameter verbietet dies:

  validates_uniqueness_of :username, :case_sensitive => false

Kleine Tricks in Ruby on Rails

Hier eine kleine Sammlung von Dingen, die mir geholfen haben beim Lösen der Probleme die sich einem N00b so stellen.

Exportieren von Datensätzen ins YAML Format

Rails Fixtures sind Testdatensätze, die im relativ übersichtlichen YAML-Format gespeichert werden. Möchte man bestehende Datenbankinhalte als Testdatensätze verwenden, so können Sie direkt in YAML exportiert werden.

Ich halte mich an den Beitrag von NetManiac. Den Code habe ich nur soweit ergänzt, als dass ich das Exportieren der magischen Felder „id“, „created_at“ und „updated_at“ verhindere und den Dateinamen ändere, damit keine bestehenden Fixturen überschrieben werden.

Aufgerufen mit rake extract_fixtures['mymodel'] speichert es mymodel.dumped.yml im richtigen Verzeichnis.

Datei: libs/tasks/extract_fixtures.rake:

desc 'Create YAML test fixtures from data in an existing database.
Defaults to development database. Set RAILS_ENV to override. Use args
table and limit to dump one table and limit number of records'

task :extract_fixtures, :table, :limit, :needs => :environment do |t, args|
  args.with_defaults(:table => nil, :limit => nil)
  limit = args.limit.nil? ? "" : "LIMIT #{args.limit}"
  sql = "SELECT * FROM %s #{limit}"
  skip_tables = ["schema_info" ]
  if args.table.nil?
    tables = ActiveRecord::Base.connection.tables - skip_tables
  else
    tables = [ "#{args.table}"]
  end

  ActiveRecord::Base.establish_connection
  tables.each do |table_name|
    i = "000"
    File.open("#{RAILS_ROOT}/test/fixtures/#{table_name}.dumped.yml" , 'w' ) do |file|
      data = ActiveRecord::Base.connection.select_all(sql % table_name)
      file.write data.inject({}) { |hash, record|
        record.delete_if{|key, value| ['id', 'created_at', 'updated_at'].include?(key)}
        hash["#{table_name}_#{i.succ!}"] = record
        hash
      }.to_yaml
    end
  end
end

ActiveRecord Attribute beim Auslesen mit Defaultwerten überschreiben

Wenn ein Attribut immer beim Auslesen mit eineb bestimmten Wert belegt werden soll wenn noch nichts vorhanden ist, so geht dies mittels folgendem Codefragment:

def my_attribute
  read_attribute(:my_attribute) or another_attribute.capitalize+" "+yet_another_attribute
end

has_and_belongs_to_many mit Checkboxen in Fieldsets

Eine Rolle hat viele Permissions und umgekehrt. Um solche Assoziationen abzubilden, kann man collection_select verwenden, das aber leider nur ein hässliches Listfeld ausgibt. Eine ambitioniertere Variante arbeitet mit Checkboxen.

HABTM mit Checkboxen

HABTM mit Checkboxen

Zuerst wird in Permission.get_indexed_by_controller ein Hash of Hashes gemacht, der als ersten Schlüssel die Überschriften hat (controller) als zweiten Schlüssel die Feldbeschriftungen mit dem Zustand als Inhalt. Folgendes Partial gibt ein Formular aus und versieht sie mit „(Un-)Check all“ Links:

<%= link_to_function 'Check all', "$$('input.permission').each(function(checkbox) { checkbox.checked = true; })" %> |
<%= link_to_function 'Uncheck all', "$$('input.permission').each(function(checkbox) { checkbox.checked = false; })" %>
<% @permissions_indexed_by_controller=Permission.get_indexed_by_controller %>
<br />
<%- for controller in @permissions_indexed_by_controller.keys -%>
  <% field_set_tag controller do %>
    <%- for permission in @permissions_indexed_by_controller[controller] -%>
      <%= check_box_tag 'role[permission_ids][]', permission.id, @role.permissions.include?(permission),{ :id => "role_permission_ids_"+ permission.id.to_s, :class => "permission"} -%><%= permission.description %>
    <%- end -%>
  <% end %>
<%- end -%>

Wichtig ist, dass im entsprechenden Controller in der update Funktion ein leeres Array erzeugt wird wenn nichts übergeben wird, sonst ändert sich nichts wenn man alle Häckchen rausnimmt:

params[:role][:permission_ids] ||= []

Erzeugen eines Hash of Hashes in Ruby

Die Hash/Array implementierung in PHP ist immernoch ungeschlagen. Will man in Ruby ein Hash of Hashes machen und einen Wert mittels << hinzufügen wenn noch kein leerer Hash besteht, so tut es blöd. Insbesondere wenn man Records in einem Hash abspeichen will, hilft folgendes Idiom:

      (ret[map_key] ||= []) << record

Dynamisches Navigationsmenü mit Highlightning in Ruby on Rails

Und es kam so: Gerade eben aufgestanden und am Gliedersortieren, sprach der Duschkopf zu mir: „Hey, du!“. Obwohl überrascht über diese ungewohnte Intimität des Duzens, war meine Aufmerksamkeit geweckt. „Jetzt mal ehrlich“, so sprachs weiter, „mit dem Symfony Framework hälst Du es gleich wie mit dem Latein: Oftmals angefangen, aber nie ganz warm geworden, gelle.“. Mein Hirn lief schon an der Grenze seiner morgentlichen Leistungsfähigkeit, aber eine Antwort darauf war wahrscheinlich auch gar nicht verlangt. „Du weisst schon, dass PHP eine Templatesprache ist, oder? Wieso hörst du nicht auf mit der Verulkung und versuchst Dich endlich mal am Original?“. Ich entgegnete was in der Richtung, dass die genuinen Römer (TM) schon ziemlich lange die Arena von unten ansehen. „Nein, du Dumpfbacke! Ruby on Rails sollst du versuchen und Freude daran haben! Frohlocken! Beweg deinen alternden Denkapparat du Assemblerheini!“

Natürlich folge ich allen Anweisungen meiner Badezimmerarmaturen, und so habe ich mich unverzüglich an die Arbeit gemacht. Die Standardinstallation hat es natürlich nicht getan (…und Zeit hat man zu viel…), Rails 2 musste es sein, mittlerweile released als Rails 2-2. Auch eine Entwicklungsumgebung musste her, und ich habe mich für Netbeans entschieden, da sie für zwei weitere Projekte (Handygames und JavaFX) eh angesehen werden wollte. Wie das Glück so spielt, hatten auch die Netzbohnen gerade ein grösseres Update auf 6.5 durchgemacht und kleinere Fluchereien mit verwurschtelten Projekten ausgelöst. Naja.

Mit verschiedenen Offline- und Onlinequellen habe ich mich an die Arbeit gemacht, und muss sagen, Ruby on Rails ist, hmm, ungewohnt. Bis jetzt geht es überraschend flüssig, auch wenn ich bei jeder Zeile 4 verschiedene Quellen konsultieren muss um es richtig (TM) zu machen.
Weiterlesen