rails_dictionary 0.0.8 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -1
- data/CHANGELOG +6 -0
- data/Gemfile +7 -1
- data/README.rdoc +19 -7
- data/Rakefile +2 -1
- data/lib/rails_dictionary/engine.rb +4 -0
- data/lib/rails_dictionary/models/active_record_extension.rb +45 -0
- data/lib/rails_dictionary/models/acts_as_dict_slave.rb +55 -0
- data/lib/rails_dictionary/models/acts_as_dict_type.rb +54 -0
- data/lib/rails_dictionary/models/acts_as_dictionary.rb +56 -0
- data/lib/rails_dictionary/railtie.rb +17 -0
- data/lib/rails_dictionary/version.rb +1 -1
- data/lib/rails_dictionary.rb +2 -4
- data/log/development.log +2 -0
- data/rails_dictionary.gemspec +1 -1
- data/spec/fake_app.rb +36 -0
- data/spec/rails_dictionary_spec.rb +90 -0
- data/spec/spec_helper.rb +19 -0
- data/test/rails_dictionary_test.rb +8 -6
- metadata +19 -9
- data/lib/rails_dictionary/acts_as_dict_slave.rb +0 -69
- data/lib/rails_dictionary/acts_as_dict_type.rb +0 -56
- data/lib/rails_dictionary/acts_as_dictionary.rb +0 -57
data/.gitignore
CHANGED
data/CHANGELOG
ADDED
data/Gemfile
CHANGED
@@ -1,4 +1,10 @@
|
|
1
1
|
source "http://rubygems.org"
|
2
2
|
|
3
|
-
# Specify your gem's dependencies in
|
3
|
+
# Specify your gem's dependencies in rails_dictionary.gemspec
|
4
4
|
gemspec
|
5
|
+
|
6
|
+
group :development,:test do
|
7
|
+
gem 'rspec', '>= 2.5.0'
|
8
|
+
gem 'rails', '>= 3.0.0'
|
9
|
+
gem 'sqlite3'
|
10
|
+
end
|
data/README.rdoc
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
= Intro
|
2
|
-
There are
|
3
|
-
|
2
|
+
There are always some static data(not static page) in application.For example product type or diploma kind of student.
|
3
|
+
|
4
|
+
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).
|
4
5
|
|
5
6
|
= Usage
|
6
7
|
== Installation
|
@@ -43,10 +44,11 @@ These task are just generate table dictionaries,dict_types,students and some sam
|
|
43
44
|
| 2 | shanghai@dict.com | 3 | |
|
44
45
|
+----+-------------------+------+--------+
|
45
46
|
2 rows in set
|
46
|
-
There is one
|
47
|
+
There is one convention of DictType.name value.All value of DictType.name is "model_method" : student is model and city is method of student model.
|
47
48
|
|
48
49
|
=== Table Definition
|
49
50
|
Make sure you have two tables which named as dict_types and dictionaries.
|
51
|
+
|
50
52
|
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.
|
51
53
|
The students table is not required and variable by your application.
|
52
54
|
|
@@ -65,13 +67,15 @@ Here is what should be like.Student model can be other models.
|
|
65
67
|
acts_as_dict_slave
|
66
68
|
end
|
67
69
|
|
68
|
-
== Features
|
70
|
+
== Features (relies on the above data) :
|
69
71
|
DictType.all_cached #=> return cache of DictType.all
|
70
72
|
DictType.all_types = %w[student_city student_school] # also cached
|
71
73
|
Dictionary.student_city #=> [Dictionary.find(5),Dictionary.find(6)]
|
72
|
-
student_city is a dynamic method which returns a list of dictionary object which dict_type is "student_city".
|
74
|
+
student_city is a dynamic method which returns a list of dictionary object which dict_type is "student_city".
|
75
|
+
Actually Dictionary will have as many dynamic methods as DictType.count and each dynamic method name is DictType.name.
|
76
|
+
Before version 0.1.0 or 0.2.0 ,all dynamic methods return an array,not ActiveRelation.So
|
73
77
|
Dictionary.student_school = []
|
74
|
-
Dictionary.student_city :locale => :en
|
78
|
+
Dictionary.student_city :locale => :en #=> [["shanghai",1],["beijing",2]]
|
75
79
|
You can use it in form select method like
|
76
80
|
collection_select :student,:city,Dictionary.student_city,:id,:name_en
|
77
81
|
select :student,:city,Dictionary.student_city(params)
|
@@ -80,6 +84,9 @@ If params contains :locale => :fr,it returns a list of french name of student ci
|
|
80
84
|
Here is an other solution for international translation.
|
81
85
|
Student.find(1).named_city(:zh) = "北京"
|
82
86
|
Student.find(1).named_city(:fr) = "Pékin"
|
87
|
+
Student.find(1).named_city(:en) = "beijing"
|
88
|
+
Make sure your locale is en,not en-US.
|
89
|
+
|
83
90
|
Student has two belongs_to assocition which named as city_dict and school_dict,the naming convention is method_dict.
|
84
91
|
Student.find(1).city_dict #=> Dictionary.find(6)
|
85
92
|
|
@@ -97,5 +104,10 @@ The rails cache would not refresh.
|
|
97
104
|
In short,when you confused with the debug data,try running "rails tmp:clear" first.
|
98
105
|
|
99
106
|
= TODO & Problems
|
100
|
-
There are no convention and implemention to map Class like Ckeditor::Asset to a method.
|
107
|
+
There are no convention and implemention to map Class like Ckeditor::Asset to a legal method name.
|
108
|
+
|
109
|
+
Rebuild core code.Using Rails engine.
|
110
|
+
|
111
|
+
Make cached type of DictType to an array of symbols
|
112
|
+
|
101
113
|
Rewrite Testing,make it more maintainable.
|
data/Rakefile
CHANGED
@@ -3,6 +3,7 @@ Bundler::GemHelper.install_tasks
|
|
3
3
|
|
4
4
|
desc "Running Test"
|
5
5
|
task :test do
|
6
|
-
system "ruby -I . test/rails_dictionary_test.rb "
|
6
|
+
# system "ruby -I . test/rails_dictionary_test.rb " # used with version 0.0.8 or before it
|
7
|
+
system "rspec spec/rails_dictionary_spec.rb"
|
7
8
|
end
|
8
9
|
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module RailsDictionary
|
2
|
+
module ActiveRecordExtension
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
# TODO: move macro define in each module file
|
9
|
+
# See Usage in readme.doc.
|
10
|
+
def acts_as_dict_type
|
11
|
+
self.class_eval do
|
12
|
+
has_many :dictionaries
|
13
|
+
validates_uniqueness_of :name
|
14
|
+
after_save :delete_all_caches
|
15
|
+
after_destroy :delete_all_caches
|
16
|
+
end
|
17
|
+
include RailsDictionary::ActsAsDictType
|
18
|
+
end
|
19
|
+
|
20
|
+
def acts_as_dictionary
|
21
|
+
self.class_eval do
|
22
|
+
belongs_to :dict_type
|
23
|
+
after_save :delete_dicts_cache
|
24
|
+
after_destroy :delete_dicts_cache
|
25
|
+
end
|
26
|
+
include RailsDictionary::ActsAsDictionary
|
27
|
+
end
|
28
|
+
|
29
|
+
# :except - remove dict mapping column
|
30
|
+
# :add - add dict mapping column
|
31
|
+
# :locale - add and initialize class attribute default_dict_locale
|
32
|
+
def acts_as_dict_slave(ops={})
|
33
|
+
include RailsDictionary::ActsAsDictSlave
|
34
|
+
class_attribute :default_dict_locale,:instance_writer => false
|
35
|
+
cattr_accessor :dict_mapping_columns,:instance_writes => false
|
36
|
+
self.default_dict_locale = ops[:locale] if ops[:locale]
|
37
|
+
self.dict_mapping_columns = dict_columns(ops)
|
38
|
+
unless dict_mapping_columns.nil?
|
39
|
+
add_dynamic_column_method
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module RailsDictionary
|
2
|
+
module ActsAsDictSlave
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
# return columns that exist in DictType#tab_and_column
|
10
|
+
def columns_in_dict_type
|
11
|
+
DictType.tab_and_column[self.name.underscore.to_sym]
|
12
|
+
end
|
13
|
+
|
14
|
+
# columns which map to dictionary
|
15
|
+
def dict_columns(ops={})
|
16
|
+
conf={except: nil,add: nil}
|
17
|
+
conf.update(ops)
|
18
|
+
cidt=self.columns_in_dict_type || []
|
19
|
+
cidt.delete(conf[:except])
|
20
|
+
case conf[:add]
|
21
|
+
when String
|
22
|
+
cidt.push(conf[:add])
|
23
|
+
when Array
|
24
|
+
cidt.push(*conf[:add])
|
25
|
+
else nil
|
26
|
+
end
|
27
|
+
cidt.uniq! || cidt
|
28
|
+
end
|
29
|
+
|
30
|
+
# add a belongs_to(Dictionary) association and a named_{column} method
|
31
|
+
def add_dynamic_column_method
|
32
|
+
dict_mapping_columns.each { |e| belongs_to "#{e.to_s}_dict".to_sym,class_name: "Dictionary",foreign_key: e }
|
33
|
+
dict_mapping_columns.each { |ele| named_dict_value ele.to_sym }
|
34
|
+
end
|
35
|
+
|
36
|
+
# Generate dynamic instance method named_column to slave model
|
37
|
+
# def named_city(locale=nil)
|
38
|
+
# locale = locale.presence || default_dict_locale.presence || :en
|
39
|
+
# locale = "name_#{locale}"
|
40
|
+
# self.send(city_dict).try(:send,locale)
|
41
|
+
# end
|
42
|
+
def named_dict_value(method_name)
|
43
|
+
belongs_to_name="#{method_name.to_s}_dict".to_sym
|
44
|
+
method_name="named_#{method_name.to_s}"
|
45
|
+
define_method(method_name) do | locale=nil |
|
46
|
+
locale = locale.presence || default_dict_locale.presence || :en
|
47
|
+
locale = "name_#{locale}"
|
48
|
+
self.send(belongs_to_name).try(:send,locale)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module RailsDictionary
|
2
|
+
module ActsAsDictType
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
base.send :include,InstanceMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
def all_types
|
11
|
+
Rails.cache.fetch("DictType.all_types") { all.map(&:name).map(&:to_sym) }.dup
|
12
|
+
end
|
13
|
+
|
14
|
+
def cached_all
|
15
|
+
Rails.cache.fetch("DictType.cached_all") { all }.dup
|
16
|
+
end
|
17
|
+
|
18
|
+
# short method to transfer id to name or name to id
|
19
|
+
def revert(arg)
|
20
|
+
if arg.is_a?(String)
|
21
|
+
DictType.where(name: arg).first.try(:id)
|
22
|
+
elsif arg.is_a?(Fixnum)
|
23
|
+
DictType.where(id: arg).first.try(:name)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# TODO: get a more accurate method name
|
28
|
+
#
|
29
|
+
# Parse the name value to get which column and model are listed in DictType
|
30
|
+
#
|
31
|
+
# Programmer DOC:
|
32
|
+
# There are two chooses to get subclass,one is subclasses the other is descendants,
|
33
|
+
# I don't know which is better,but descendants contains subclass of subclass,it contains more.
|
34
|
+
# Class like +Ckeditor::Asset+ transfer to "ckeditor/asset",but we can not naming method like that,
|
35
|
+
# So it still not support, the solution may be simple,just make another convention to escape "/"
|
36
|
+
def tab_and_column
|
37
|
+
Rails.cache.fetch("DictType.tab_and_column") do
|
38
|
+
all_model_class=ActiveRecord::Base.descendants.map(&:name).map(&:underscore)
|
39
|
+
all_types.map(&:to_s).extract_to_hash(all_model_class)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module InstanceMethods
|
45
|
+
def delete_all_caches
|
46
|
+
Rails.cache.delete("DictType.all_types")
|
47
|
+
Rails.cache.delete("DictType.cached_all")
|
48
|
+
Rails.cache.delete("DictType.tab_and_column")
|
49
|
+
return true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module RailsDictionary
|
2
|
+
module ActsAsDictionary
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
base.send :include,InstanceMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
# Return an instance of array
|
11
|
+
# Programmer DOC:
|
12
|
+
# Following design is failed with sqlite3 in test
|
13
|
+
# scope :dict_type_name_eq,lambda {|method_name| joins(:dict_type).where("dict_types.name" => method_name)}
|
14
|
+
# remove the all seems not pass test,return following errors
|
15
|
+
# TypeError: no marshal_dump is defined for class SQLite3::Database
|
16
|
+
def dict_type_name_eq(method_name)
|
17
|
+
joins(:dict_type).where("dict_types.name" => method_name).all
|
18
|
+
end
|
19
|
+
|
20
|
+
# Generate methods like Dictionary.student_city
|
21
|
+
# Dictionary.student_city - a list of dictionary object which dict type is student_city
|
22
|
+
# Dictionary.student_city(:locale => :zh) - a select format array which can be used
|
23
|
+
# in view method select as choice params
|
24
|
+
# Programmer DOC && TODO:
|
25
|
+
# rethink about the cache.
|
26
|
+
# cache methods like Dictionary.student_city(:locale => :zh,:sort => :name_fr)
|
27
|
+
# but not cache Dictionary.student_city,return it as relation
|
28
|
+
def method_missing(method_id,options={},&block)
|
29
|
+
method_name=method_id.to_s.downcase
|
30
|
+
if DictType.all_types.include? method_id
|
31
|
+
Rails.cache.fetch("Dictionary.#{method_name}") { dict_type_name_eq(method_name) }
|
32
|
+
listed_attr=Rails.cache.read("Dictionary.#{method_name}").dup
|
33
|
+
# Instance of activerelation can not be dup?
|
34
|
+
if options.keys.include? :locale or options.keys.include? "locale"
|
35
|
+
locale="name_#{ options[:locale] }"
|
36
|
+
listed_attr.map! { |a| [a.send(locale),a.id] }
|
37
|
+
listed_attr.sort { |a,b| a.first <=> b.first }
|
38
|
+
# maybe remove the above line,or change some sorting and caching design
|
39
|
+
else
|
40
|
+
listed_attr
|
41
|
+
end
|
42
|
+
else
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module InstanceMethods
|
49
|
+
def delete_dicts_cache
|
50
|
+
method_name=DictType.revert(self.dict_type_id)
|
51
|
+
Rails.cache.delete("Dictionary.#{method_name}")
|
52
|
+
return true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rails'
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__),"array_core_ext")
|
4
|
+
require File.join(File.dirname(__FILE__),"models/acts_as_dict_type")
|
5
|
+
require File.join(File.dirname(__FILE__),"models/acts_as_dictionary")
|
6
|
+
require File.join(File.dirname(__FILE__),"models/acts_as_dict_slave")
|
7
|
+
|
8
|
+
module RailsDictionary
|
9
|
+
class Railtie < ::Rails::Railtie
|
10
|
+
initializer 'rails_dictionary' do |app|
|
11
|
+
ActiveSupport.on_load(:active_record) do
|
12
|
+
require File.join(File.dirname(__FILE__), 'models/active_record_extension')
|
13
|
+
::ActiveRecord::Base.send :include, RailsDictionary::ActiveRecordExtension
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/rails_dictionary.rb
CHANGED
@@ -1,4 +1,2 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require "rails_dictionary/acts_as_dict_type"
|
4
|
-
require "rails_dictionary/array_core_ext"
|
1
|
+
require File.join(File.dirname(__FILE__), 'rails_dictionary/railtie')
|
2
|
+
require File.join(File.dirname(__FILE__), 'rails_dictionary/engine')
|
data/log/development.log
ADDED
data/rails_dictionary.gemspec
CHANGED
@@ -6,7 +6,7 @@ Gem::Specification.new do |s|
|
|
6
6
|
s.name = "rails_dictionary"
|
7
7
|
s.version = RailsDictionary::VERSION
|
8
8
|
s.platform = Gem::Platform::RUBY
|
9
|
-
s.authors = ["
|
9
|
+
s.authors = ["Raykin Lee"]
|
10
10
|
s.email = ["raykincoldxiao@campus.com"]
|
11
11
|
s.homepage = "https://github.com/raykin/rails_dictionary"
|
12
12
|
s.summary = %q{dictionary data for web application}
|
data/spec/fake_app.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
# database
|
3
|
+
ActiveRecord::Base.configurations = {'test' => {:adapter => 'sqlite3', :database => ':memory:'}}
|
4
|
+
ActiveRecord::Base.establish_connection('test')
|
5
|
+
|
6
|
+
# config
|
7
|
+
app = Class.new(Rails::Application)
|
8
|
+
app.config.active_support.deprecation = :log
|
9
|
+
app.initialize!
|
10
|
+
|
11
|
+
# models
|
12
|
+
class DictType < ActiveRecord::Base
|
13
|
+
acts_as_dict_type
|
14
|
+
end
|
15
|
+
|
16
|
+
class Dictionary < ActiveRecord::Base
|
17
|
+
acts_as_dictionary
|
18
|
+
end
|
19
|
+
|
20
|
+
class Student < ActiveRecord::Base
|
21
|
+
end
|
22
|
+
|
23
|
+
#migrations
|
24
|
+
class CreateAllTables < ActiveRecord::Migration
|
25
|
+
def self.up
|
26
|
+
create_table(:dict_types) {|t| t.string :name}
|
27
|
+
create_table(:dictionaries) {|t| t.string :name_en; t.string :name_zh ; t.string :name_fr ; t.integer :dict_type_id}
|
28
|
+
create_table(:students) {|t| t.string :email; t.integer :city; t.integer :school}
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.down
|
32
|
+
drop_table :dict_types
|
33
|
+
drop_table :dictionaries
|
34
|
+
drop_table :students
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require File.expand_path('spec_helper', File.dirname(__FILE__))
|
3
|
+
|
4
|
+
|
5
|
+
describe RailsDictionary::ActiveRecordExtension do
|
6
|
+
%w[acts_as_dict_type acts_as_dictionary acts_as_dict_slave].each do |method_name|
|
7
|
+
it "contains #{method_name}" do
|
8
|
+
ActiveRecord::Base.methods.include?(method_name.to_sym).should be_true
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe RailsDictionary do
|
14
|
+
let!(:dt_stu_city) { DictType.create! :name => "student_city" }
|
15
|
+
let!(:dt_stu_school) { DictType.create! :name => "student_school" }
|
16
|
+
let!(:dy_shanghai) { Dictionary.create! name_en: "shanghai",name_zh: "上海",name_fr: "shanghai",dict_type_id: dt_stu_city.id }
|
17
|
+
let!(:dy_beijing) { Dictionary.create! name_en: "beijing",name_zh: "北京",name_fr: "Pékin",dict_type_id: dt_stu_city.id }
|
18
|
+
let!(:stu_beijing) { Student.create! email: "beijing@dict.com",city: dy_beijing.id }
|
19
|
+
let!(:stu_shanghai) { Student.create! email: "shanghai@dict.com",city: dy_shanghai.id }
|
20
|
+
|
21
|
+
describe DictType do
|
22
|
+
it "parse model and method" do
|
23
|
+
expected_hash={:student => %w[city school]}
|
24
|
+
DictType.tab_and_column.should == expected_hash
|
25
|
+
end
|
26
|
+
|
27
|
+
it "all types" do
|
28
|
+
DictType.all_types.should == [:student_city, :student_school]
|
29
|
+
end
|
30
|
+
|
31
|
+
it "reverts id to name or name to id" do
|
32
|
+
DictType.revert(dt_stu_school.id).should == "student_school"
|
33
|
+
DictType.revert(100).should be_nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it "after one record removed" do
|
37
|
+
dt_stu_school.destroy
|
38
|
+
DictType.all_types.should == [:student_city]
|
39
|
+
DictType.tab_and_column.should == Hash[:student,["city"]]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe Dictionary do
|
44
|
+
it "generate student_city method" do
|
45
|
+
Dictionary.student_city.should == [dy_shanghai,dy_beijing]
|
46
|
+
end
|
47
|
+
|
48
|
+
it "generate student_city method with locale" do
|
49
|
+
Dictionary.student_city(:locale => :zh).should == [["上海", 1],["北京", 2]]
|
50
|
+
end
|
51
|
+
|
52
|
+
it "after record added or removed" do
|
53
|
+
@dy_wuhan=Dictionary.create! name_en: "wuhan",name_zh: "武汉",name_fr: "wuhan",dict_type_id: dt_stu_city.id
|
54
|
+
Dictionary.student_city(:locale => :en).should == [["beijing", 2],["shanghai",1],["wuhan", 3]]
|
55
|
+
@dy_wuhan.destroy
|
56
|
+
Dictionary.student_city(:locale => :en).should == [["beijing", 2],["shanghai",1]]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe Student do
|
61
|
+
before :each do
|
62
|
+
Student.acts_as_dict_slave
|
63
|
+
end
|
64
|
+
it "method of columns_in_dict_type and dict_columns" do
|
65
|
+
Student.columns_in_dict_type.should == %w[city school]
|
66
|
+
Student.dict_columns == %w[city school]
|
67
|
+
end
|
68
|
+
|
69
|
+
it "named_city with different locale" do
|
70
|
+
stu_shanghai.named_city(:en).should == "shanghai"
|
71
|
+
stu_shanghai.named_city("").should == "shanghai"
|
72
|
+
stu_beijing.named_city(:fr).should == "Pékin"
|
73
|
+
end
|
74
|
+
|
75
|
+
it "override default locale" do
|
76
|
+
Student.acts_as_dict_slave :locale => :fr
|
77
|
+
stu_beijing.named_city.should == "Pékin"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe Array do
|
83
|
+
let(:an_array) { %w[root student_school student_city admin_role] }
|
84
|
+
let(:expected_hash) { {:student => %w[school city],:admin => %w[role]} }
|
85
|
+
let(:blank_hash) { Hash.new }
|
86
|
+
it "extract to hash" do
|
87
|
+
an_array.extract_to_hash(%w[student admin]).should == expected_hash
|
88
|
+
an_array.extract_to_hash(%w[students]).should == blank_hash
|
89
|
+
end
|
90
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# copy and modify the test design of kaminari(a paginate gem for Rails3)
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
4
|
+
require 'rails'
|
5
|
+
require 'active_record'
|
6
|
+
require 'action_controller/railtie'
|
7
|
+
require 'action_view/railtie'
|
8
|
+
require 'rails_dictionary'
|
9
|
+
|
10
|
+
require File.join(File.dirname(__FILE__), 'fake_app')
|
11
|
+
|
12
|
+
require 'rspec/rails'
|
13
|
+
|
14
|
+
$stdout = StringIO.new # remove the noise output
|
15
|
+
|
16
|
+
RSpec.configure do |config|
|
17
|
+
config.use_transactional_fixtures = true
|
18
|
+
CreateAllTables.up unless ActiveRecord::Base.connection.table_exists? 'dict_types'
|
19
|
+
end
|
@@ -1,4 +1,7 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
+
# Testing for version 0.0.8 or lower.Copy the test design from acts_as_tree
|
3
|
+
# Usage: run "ruby -I . test/rails_dictionary_test.rb " in root dir
|
4
|
+
#
|
2
5
|
require "test/unit"
|
3
6
|
require "active_support"
|
4
7
|
require "active_record"
|
@@ -6,7 +9,6 @@ require "ruby-debug" # coupled with debugger to debug code
|
|
6
9
|
Object.const_set "RAILS_CACHE", ActiveSupport::Cache.lookup_store
|
7
10
|
require "active_support/cache"
|
8
11
|
require "rails"
|
9
|
-
# $: << "/home/raykin/studio/rails_dictionary/lib" # tmply added for local testing
|
10
12
|
require "#{File.dirname(__FILE__)}/../lib/rails_dictionary.rb"
|
11
13
|
|
12
14
|
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
@@ -53,7 +55,6 @@ end
|
|
53
55
|
class Student < ActiveRecord::Base
|
54
56
|
end
|
55
57
|
|
56
|
-
|
57
58
|
class CoreExtTest < Test::Unit::TestCase
|
58
59
|
def test_array
|
59
60
|
expected_hash={:student => %w[school city],:admin => %w[role]}
|
@@ -92,6 +93,7 @@ class DictTypeTest < Test::Unit::TestCase
|
|
92
93
|
# test revert method in acts_as_dict_type
|
93
94
|
def test_dt_revert
|
94
95
|
assert_equal "student_school",DictType.revert(@dt_stu_school.id)
|
96
|
+
assert_equal nil,DictType.revert(100)
|
95
97
|
end
|
96
98
|
|
97
99
|
# test dynamic instance methods in slave model
|
@@ -111,18 +113,18 @@ class DictTypeTest < Test::Unit::TestCase
|
|
111
113
|
end
|
112
114
|
|
113
115
|
def test_dictionary_method_missing
|
114
|
-
assert_equal [["
|
116
|
+
assert_equal [["beijing",2],["shanghai",1]],Dictionary.student_city(:locale => :en)
|
115
117
|
end
|
116
118
|
|
117
119
|
def test_dictionary_method_missing_with_locale
|
118
|
-
assert_equal [["上海", 1],
|
120
|
+
assert_equal [["上海", 1],["北京", 2]],Dictionary.student_city(:locale => :zh)
|
119
121
|
end
|
120
122
|
|
121
123
|
def test_delete_dicts_cache
|
122
124
|
@dy_wuhan=Dictionary.create! name_en: "wuhan",name_zh: "武汉",name_fr: "wuhan",dict_type_id: @dt_stu_city.id
|
123
|
-
assert_equal [["
|
125
|
+
assert_equal [["beijing", 2],["shanghai",1],["wuhan", 3]],Dictionary.student_city(:locale => :en)
|
124
126
|
@dy_wuhan.destroy
|
125
|
-
assert_equal [["
|
127
|
+
assert_equal [["beijing", 2],["shanghai",1]],Dictionary.student_city(:locale => :en)
|
126
128
|
assert_equal [@dy_shanghai,@dy_beijing],Dictionary.student_city
|
127
129
|
end
|
128
130
|
|
metadata
CHANGED
@@ -2,16 +2,15 @@
|
|
2
2
|
name: rails_dictionary
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.0.
|
5
|
+
version: 0.0.9
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
|
-
-
|
8
|
+
- Raykin Lee
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-
|
14
|
-
default_executable:
|
13
|
+
date: 2011-04-14 00:00:00 Z
|
15
14
|
dependencies: []
|
16
15
|
|
17
16
|
description: Rails plugin for mapping static data of web application to Dictionary class
|
@@ -25,19 +24,26 @@ extra_rdoc_files: []
|
|
25
24
|
|
26
25
|
files:
|
27
26
|
- .gitignore
|
27
|
+
- CHANGELOG
|
28
28
|
- Gemfile
|
29
29
|
- README.rdoc
|
30
30
|
- Rakefile
|
31
31
|
- lib/rails_dictionary.rb
|
32
|
-
- lib/rails_dictionary/acts_as_dict_slave.rb
|
33
|
-
- lib/rails_dictionary/acts_as_dict_type.rb
|
34
|
-
- lib/rails_dictionary/acts_as_dictionary.rb
|
35
32
|
- lib/rails_dictionary/array_core_ext.rb
|
33
|
+
- lib/rails_dictionary/engine.rb
|
34
|
+
- lib/rails_dictionary/models/active_record_extension.rb
|
35
|
+
- lib/rails_dictionary/models/acts_as_dict_slave.rb
|
36
|
+
- lib/rails_dictionary/models/acts_as_dict_type.rb
|
37
|
+
- lib/rails_dictionary/models/acts_as_dictionary.rb
|
38
|
+
- lib/rails_dictionary/railtie.rb
|
36
39
|
- lib/rails_dictionary/version.rb
|
37
40
|
- lib/tasks/dicts.rake
|
41
|
+
- log/development.log
|
38
42
|
- rails_dictionary.gemspec
|
43
|
+
- spec/fake_app.rb
|
44
|
+
- spec/rails_dictionary_spec.rb
|
45
|
+
- spec/spec_helper.rb
|
39
46
|
- test/rails_dictionary_test.rb
|
40
|
-
has_rdoc: true
|
41
47
|
homepage: https://github.com/raykin/rails_dictionary
|
42
48
|
licenses: []
|
43
49
|
|
@@ -61,9 +67,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
61
67
|
requirements: []
|
62
68
|
|
63
69
|
rubyforge_project: rails_dictionary
|
64
|
-
rubygems_version: 1.
|
70
|
+
rubygems_version: 1.7.2
|
65
71
|
signing_key:
|
66
72
|
specification_version: 3
|
67
73
|
summary: dictionary data for web application
|
68
74
|
test_files:
|
75
|
+
- spec/fake_app.rb
|
76
|
+
- spec/rails_dictionary_spec.rb
|
77
|
+
- spec/spec_helper.rb
|
69
78
|
- test/rails_dictionary_test.rb
|
79
|
+
has_rdoc:
|
@@ -1,69 +0,0 @@
|
|
1
|
-
module ActsAsDictSlave
|
2
|
-
def self.included(base)
|
3
|
-
base.extend(ClassMethods)
|
4
|
-
end
|
5
|
-
|
6
|
-
module ClassMethods
|
7
|
-
# :except - remove dict mapping column
|
8
|
-
# :add - add dict mapping column
|
9
|
-
# :locale - add and initialize class attribute default_dict_locale
|
10
|
-
def acts_as_dict_slave(ops={})
|
11
|
-
class_attribute :default_dict_locale,:instance_writer => false
|
12
|
-
cattr_accessor :dict_mapping_columns,:instance_writes => false
|
13
|
-
self.default_dict_locale = ops[:locale] if ops[:locale]
|
14
|
-
self.dict_mapping_columns = dict_columns(ops)
|
15
|
-
unless dict_mapping_columns.nil?
|
16
|
-
add_dynamic_column_method
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
# return columns that exist in DictType#tab_and_column
|
21
|
-
def columns_in_dict_type
|
22
|
-
DictType.tab_and_column[self.name.underscore.to_sym]
|
23
|
-
end
|
24
|
-
|
25
|
-
# columns which map to dictionary
|
26
|
-
def dict_columns(ops={})
|
27
|
-
conf={except: nil,add: nil}
|
28
|
-
conf.update(ops)
|
29
|
-
cidt=self.columns_in_dict_type || []
|
30
|
-
cidt.delete(conf[:except])
|
31
|
-
case conf[:add]
|
32
|
-
when String
|
33
|
-
cidt.push(conf[:add])
|
34
|
-
when Array
|
35
|
-
cidt.push(*conf[:add])
|
36
|
-
else nil
|
37
|
-
end
|
38
|
-
cidt.uniq! || cidt
|
39
|
-
end
|
40
|
-
|
41
|
-
# add a belongs_to(Dictionary) association and a named_{column} method
|
42
|
-
def add_dynamic_column_method
|
43
|
-
self.extend(DynamicInsMethods)
|
44
|
-
dict_mapping_columns.each { |e| belongs_to "#{e.to_s}_dict".to_sym,class_name: "Dictionary",foreign_key: e }
|
45
|
-
dict_mapping_columns.each { |ele| named_dict_value ele.to_sym }
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
module DynamicInsMethods
|
50
|
-
# generate dynamic instance method named_column to slave model
|
51
|
-
# def named_city(locale=nil)
|
52
|
-
# locale = locale.presence || default_dict_locale.presence || :en
|
53
|
-
# locale = "name_#{locale}"
|
54
|
-
# self.send(city_dict).try(:send,locale)
|
55
|
-
# end
|
56
|
-
def named_dict_value(method_name)
|
57
|
-
belongs_to_name="#{method_name.to_s}_dict".to_sym
|
58
|
-
method_name="named_#{method_name.to_s}"
|
59
|
-
define_method(method_name) do | locale=nil |
|
60
|
-
locale = locale.presence || default_dict_locale.presence || :en
|
61
|
-
locale = "name_#{locale}"
|
62
|
-
self.send(belongs_to_name).try(:send,locale)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
end
|
67
|
-
|
68
|
-
end
|
69
|
-
ActiveRecord::Base.class_eval { include ActsAsDictSlave }
|
@@ -1,56 +0,0 @@
|
|
1
|
-
module ActsAsDictType
|
2
|
-
def self.included(base)
|
3
|
-
base.extend(ClassMethods)
|
4
|
-
end
|
5
|
-
|
6
|
-
module ClassMethods
|
7
|
-
def acts_as_dict_type(ops={})
|
8
|
-
self.class_eval do
|
9
|
-
has_many :dictionaries
|
10
|
-
include InstanceMethods
|
11
|
-
validates_uniqueness_of :name
|
12
|
-
after_save :delete_all_caches
|
13
|
-
after_destroy :delete_all_caches
|
14
|
-
|
15
|
-
def self.all_types
|
16
|
-
Rails.cache.fetch("DictType.all_types") { all.map(&:name) }.dup
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.cached_all
|
20
|
-
Rails.cache.fetch("DictType.cached_all") { all }.dup
|
21
|
-
end
|
22
|
-
|
23
|
-
# short method to transfer id to name or name to id
|
24
|
-
def self.revert(arg)
|
25
|
-
if arg.is_a?(String)
|
26
|
-
DictType.where(name: arg).try(:first).id
|
27
|
-
elsif arg.is_a?(Fixnum)
|
28
|
-
DictType.where(id: arg).try(:first).name
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# TODO: get a more accurate method name
|
33
|
-
# Parse the name value to get which column and model are listed in DictType
|
34
|
-
def self.tab_and_column
|
35
|
-
tab_and_column={}
|
36
|
-
# There are two chooses,one is subclasses the other is descendants,
|
37
|
-
# I don't know which is better,but descendants contains subclass of subclass,it contains more.
|
38
|
-
# Class like +Ckeditor::Asset+ transfer to "ckeditor/asset",but we can not naming method like that,
|
39
|
-
# So it still not support, the solution may be simple,just make another convention to escape "/"
|
40
|
-
all_model_class=ActiveRecord::Base.descendants.map(&:name).map(&:underscore)
|
41
|
-
tab_and_column=all_types.extract_to_hash(all_model_class)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
module InstanceMethods
|
47
|
-
def delete_all_caches
|
48
|
-
Rails.cache.delete("DictType.all_types")
|
49
|
-
Rails.cache.delete("DictType.cached_all")
|
50
|
-
return true
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
end
|
55
|
-
end
|
56
|
-
ActiveRecord::Base.class_eval { include ActsAsDictType }
|
@@ -1,57 +0,0 @@
|
|
1
|
-
module ActsAsDictionary
|
2
|
-
def self.included(base)
|
3
|
-
base.extend(ClassMethods)
|
4
|
-
end
|
5
|
-
|
6
|
-
module ClassMethods
|
7
|
-
|
8
|
-
def acts_as_dictionary
|
9
|
-
belongs_to :dict_type
|
10
|
-
after_save :delete_dicts_cache
|
11
|
-
after_destroy :delete_dicts_cache
|
12
|
-
|
13
|
-
# Return an instance of array
|
14
|
-
def dict_type_name_eq(method_name)
|
15
|
-
joins(:dict_type).where("dict_types.name" => method_name).all
|
16
|
-
end
|
17
|
-
#Following design is not successed with sqlite3 in test
|
18
|
-
# scope :dict_type_name_eq,lambda {|method_name| joins(:dict_type).where("dict_types.name" => method_name)}
|
19
|
-
# remove the all seems not pass test,return following errors
|
20
|
-
# TypeError: no marshal_dump is defined for class SQLite3::Database
|
21
|
-
include InstanceMethods
|
22
|
-
# Generate methods like Dictionary.student_city
|
23
|
-
# Dictionary.student_city - a list of dictionary object which dict type is student_city
|
24
|
-
# Dictionary.student_city(:locale => :zh) - a select format array which can be used
|
25
|
-
# in view method select as choice params
|
26
|
-
# Is this design good?
|
27
|
-
def self.method_missing(method_id,options={},&block)
|
28
|
-
method_name=method_id.to_s.downcase
|
29
|
-
if DictType.all_types.include? method_id.to_s
|
30
|
-
Rails.cache.fetch("Dictionary.#{method_name}") { dict_type_name_eq(method_name) }
|
31
|
-
listed_attr=Rails.cache.read("Dictionary.#{method_name}").dup
|
32
|
-
# Instance of activerelation can not be dup?
|
33
|
-
if options.keys.include? :locale or options.keys.include? "locale"
|
34
|
-
locale="name_#{ options[:locale] }"
|
35
|
-
listed_attr.map! { |a| [a.send(locale),a.id] }
|
36
|
-
listed_attr.sort {|a,b| a.last <=> b.last } # maybe remove this line
|
37
|
-
else
|
38
|
-
listed_attr
|
39
|
-
end
|
40
|
-
else
|
41
|
-
super
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
module InstanceMethods
|
49
|
-
def delete_dicts_cache
|
50
|
-
method_name=DictType.revert(self.dict_type_id)
|
51
|
-
Rails.cache.delete("Dictionary.#{method_name}")
|
52
|
-
return true
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
end
|
57
|
-
ActiveRecord::Base.class_eval { include ActsAsDictionary }
|