Plan

0.3 (on the main branch) is a non-breaking release on the 0.2 API: it replaces the internal method_missing with generated methods, fixes the load-order and cross-DB cache bugs, and adds a few convenience methods (see Features). The abandoned STI experiment lives on the sti branch. The next breaking rewrite is tracked in v1.0-roadmap.md.

Intro

There are always some static data(not static page) in application.For example product type or student diploma type.

This gem can map these static data to a Dictionary#method like Dictionary#student_diploma_kind and generate a list of instance method on Student like @student.named_city which return city name (with locale if you need).

Usage

Installation

Version 0.3 supports Rails 7.1–8 and Ruby >= 3.3.

Version 0.2 supports Rails 4–7. Version 0.1 supports Rails 3.1.

gem 'rails_dictionary'

or

gem "rails_dictionary", :git => 'git://github.com/raykin/rails_dictionary'

!!For users who update from old version to 0.0.9.1 or higher,Please run following command on app root after updates.

rails runner "DictType.new.delete_all_caches"

and in production

rails runner "DictType.new.delete_all_caches" -e production

or if you use file cache,just run

rake tmp:clear

See change log for brief info.

Sample

Run following task will give you a simple start.Maybe you should try it in a new application first.

rake dicts:generate
rake dicts:sample_slave
rake db:migrate
rake dicts:sample_data

These task are just generate table dictionaries,dict_types,students and some sample data.The data should be

irb(main):013:0> DictType.select("id,name").all
  DictType Load (0.4ms)  SELECT id,name FROM `dict_types`
  +----+----------------+
  | id | name           |
  +----+----------------+
  | 1  | student_city   |
  | 2  | student_school |
  +----+----------------+
  2 rows in set
 irb(main):014:0> Dictionary.select("id,name_en,name_zh,name_fr,dict_type_id").all
   Dictionary Load (1.2ms)  SELECT id,name_en,name_zh,name_fr,dict_type_id FROM `dictionaries`
   +----+----------+---------+----------+--------------+
   | id | name_en  | name_zh | name_fr  | dict_type_id |
   +----+----------+---------+----------+--------------+
   | 3  | shanghai | 上海     | shanghai | 1            |
   | 4  | beijing  | 北京     | Pékin    | 1            |
   +----+----------+---------+----------+--------------+
   2 rows in set
 irb(main):016:0> Student.select("id,email,city,school").all
   Student Load (0.4ms)  SELECT id,email,city,school FROM `students`
   +----+-------------------+------+--------+
   | id | email             | city | school |
   +----+-------------------+------+--------+
   | 1  | beijing@dict.com  | 4    |        |
   | 2  | shanghai@dict.com | 3    |        |
   +----+-------------------+------+--------+
   2 rows in set

There is one convention on DictType.name .All value of DictType.name is “model_method” : student is model and city is method of student model.

Table Definition

Make sure you have two tables which named as dict_types and dictionaries.

Table dictionaries has one convention of naming column : name_locale.So the name_fr means this column have a french value,you can see more usage later. The students table is not required and variable by your application.

Class Definition

Here is what should be like.Student model can be other models.

class DictType < ActiveRecord::Base
  acts_as_dict_type
end

class Dictionary < ActiveRecord::Base
  acts_as_dictionary
end

class Student < ActiveRecord::Base
  # use acts_as_dict_slave when your rails_dictionary version < 0.2
  acts_as_dict_consumer
end

Features (relies on the above data) :

DictType.all_cached #=> return cache of DictType.all
DictType.all_types = [:student_city,:student_school] # also cached
Dictionary.student_city #=> [Dictionary.find(5),Dictionary.find(6)]

student_city is a method generated for each DictType (one per DictType.name); it returns a list of dictionary objects whose dict_type is “student_city”. The methods are (re)generated at boot and whenever a DictType is created/destroyed. And student_city returns an array,not ActiveRelation.So

Dictionary.student_school #=> []
Dictionary.student_city(query: true)  #=> bypass the cache and hit the DB

If you need a ActiveRelation, try scoped_student_city like

Dictionary.scoped_student_city.where(...)

0.3 convenience methods

Dictionary.categories                       #=> [:student_city, :student_school]
Dictionary.add(:student_city, "wuhan")      #=> create entry, returns the record
Dictionary.remove(:student_city, "wuhan")   #=> destroy matching entries
Dictionary.options_for(:student_city, locale: :en)  #=> [["beijing", 2],["shanghai",1]]

options_for returns options_for_select-ready pairs and is the replacement for the deprecated Dictionary.student_city(locale: …) form below.

You can use it in a form select like

collection_select :student,:city,Dictionary.student_city,:id,:name_en
select :student, :city, Dictionary.options_for(:student_city, locale: :fr)

options_for(:student_city, locale: :fr) returns the french names (from name_fr in Dictionary)

Student.find(1).named_city = "beijing" # when default locale is :en

Here is an other solution for international translation.

Student.find(1).named_city(:zh) = "北京"
Student.find(1).named_city(:fr) = "Pékin"
Student.find(1).named_city(:en) = "beijing"

Make sure your locale is en,not en-US.

Student has two belongs_to assocition which named as city_dict and school_dict,the naming convention is method_dict.

Student.find(1).city_dict #=> Dictionary.find(6)

Sort Feature

Note: passing :locale to the generated lookup (e.g. Dictionary.student_city(locale: :en)) is deprecated; use Dictionary.options_for(:student_city, locale: :en) instead. The sort behavior described here applies to that form.

By default,if the options contains locale,the results are sorted by the name value. If locale is :zh,sort rule is order by GBK encoding. Other locales are just order by alphabetical without case sensitive. You can override Dictionary.sort_dicts to customize your sort rule.But it is not recommended now as the code of sort design maybe change in a few month.

Practical Suggestion

If you start a new application and there are more than 10 kinds of static data,you may have a try with the gem. However,if you see many static data in an old system and want to refactor it,the decision would be judged by the real situations.

Beware

The most used debug method would be DictType.all_types and Dictionary.student_city(or other dynamic generate method) When you get some confused with the output of these method,try running

rails tmp:clear

cause these methods all return static data(may be a mass of data),I just caches these output for better performance.If you change db data in db console(not through Rails) like running

delete from dict_types;

The rails cache would not refresh. In short,when you confused with the debug data,try running “rails tmp:clear” first.

TODO & Problems

Remove engine. Becase for the view layer we can use gem rails_admin. so this gem did not need rails engine. Is there any exist low level method to monitor the change of descendents? Add test code for cache DictType.tab_and_column,then uncomment the cache code.

There are no conventions and implemention to map Class like Ckeditor::Asset to a legal method name.