ransack_ffcrm 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. data/.gitignore +4 -0
  2. data/.travis.yml +9 -0
  3. data/Gemfile +40 -0
  4. data/LICENSE +20 -0
  5. data/README.md +137 -0
  6. data/Rakefile +19 -0
  7. data/lib/ransack/adapters/active_record/3.0/compat.rb +166 -0
  8. data/lib/ransack/adapters/active_record/3.0/context.rb +161 -0
  9. data/lib/ransack/adapters/active_record/3.1/context.rb +166 -0
  10. data/lib/ransack/adapters/active_record/base.rb +33 -0
  11. data/lib/ransack/adapters/active_record/context.rb +41 -0
  12. data/lib/ransack/adapters/active_record.rb +12 -0
  13. data/lib/ransack/configuration.rb +35 -0
  14. data/lib/ransack/constants.rb +23 -0
  15. data/lib/ransack/context.rb +124 -0
  16. data/lib/ransack/helpers/form_builder.rb +203 -0
  17. data/lib/ransack/helpers/form_helper.rb +75 -0
  18. data/lib/ransack/helpers.rb +2 -0
  19. data/lib/ransack/locale/en.yml +70 -0
  20. data/lib/ransack/naming.rb +53 -0
  21. data/lib/ransack/nodes/attribute.rb +49 -0
  22. data/lib/ransack/nodes/bindable.rb +30 -0
  23. data/lib/ransack/nodes/condition.rb +212 -0
  24. data/lib/ransack/nodes/grouping.rb +183 -0
  25. data/lib/ransack/nodes/node.rb +34 -0
  26. data/lib/ransack/nodes/sort.rb +41 -0
  27. data/lib/ransack/nodes/value.rb +108 -0
  28. data/lib/ransack/nodes.rb +7 -0
  29. data/lib/ransack/predicate.rb +70 -0
  30. data/lib/ransack/ransacker.rb +24 -0
  31. data/lib/ransack/search.rb +123 -0
  32. data/lib/ransack/translate.rb +92 -0
  33. data/lib/ransack/version.rb +3 -0
  34. data/lib/ransack/visitor.rb +68 -0
  35. data/lib/ransack.rb +27 -0
  36. data/ransack_ffcrm.gemspec +30 -0
  37. data/spec/blueprints/articles.rb +5 -0
  38. data/spec/blueprints/comments.rb +5 -0
  39. data/spec/blueprints/notes.rb +3 -0
  40. data/spec/blueprints/people.rb +4 -0
  41. data/spec/blueprints/tags.rb +3 -0
  42. data/spec/console.rb +21 -0
  43. data/spec/helpers/ransack_helper.rb +2 -0
  44. data/spec/ransack/adapters/active_record/base_spec.rb +67 -0
  45. data/spec/ransack/adapters/active_record/context_spec.rb +45 -0
  46. data/spec/ransack/configuration_spec.rb +31 -0
  47. data/spec/ransack/helpers/form_builder_spec.rb +137 -0
  48. data/spec/ransack/helpers/form_helper_spec.rb +38 -0
  49. data/spec/ransack/nodes/condition_spec.rb +15 -0
  50. data/spec/ransack/nodes/grouping_spec.rb +13 -0
  51. data/spec/ransack/predicate_spec.rb +55 -0
  52. data/spec/ransack/search_spec.rb +225 -0
  53. data/spec/spec_helper.rb +47 -0
  54. data/spec/support/en.yml +5 -0
  55. data/spec/support/schema.rb +111 -0
  56. metadata +229 -0
@@ -0,0 +1,75 @@
1
+ module Ransack
2
+ module Helpers
3
+ module FormHelper
4
+
5
+ def search_form_for(record, options = {}, &proc)
6
+ if record.is_a?(Ransack::Search)
7
+ search = record
8
+ options[:url] ||= polymorphic_path(search.klass)
9
+ elsif record.is_a?(Array) && (search = record.detect {|o| o.is_a?(Ransack::Search)})
10
+ options[:url] ||= polymorphic_path(record.map {|o| o.is_a?(Ransack::Search) ? o.klass : o})
11
+ else
12
+ raise ArgumentError, "No Ransack::Search object was provided to search_form_for!"
13
+ end
14
+ options[:html] ||= {}
15
+ html_options = {
16
+ :class => options[:as] ? "#{options[:as]}_search" : "#{search.klass.to_s.underscore}_search",
17
+ :id => options[:as] ? "#{options[:as]}_search" : "#{search.klass.to_s.underscore}_search",
18
+ :method => :get
19
+ }
20
+ options[:as] ||= 'q'
21
+ options[:html].reverse_merge!(html_options)
22
+ options[:builder] ||= FormBuilder
23
+
24
+ form_for(record, options, &proc)
25
+ end
26
+
27
+ def sort_link(search, attribute, *args)
28
+ raise TypeError, "First argument must be a Ransack::Search!" unless Search === search
29
+
30
+ search_params = params[:q] || {}.with_indifferent_access
31
+
32
+ attr_name = attribute.to_s
33
+
34
+ name = (args.size > 0 && !args.first.is_a?(Hash)) ? args.shift.to_s : Translate.attribute(attr_name, :context => search.context)
35
+
36
+ if existing_sort = search.sorts.detect {|s| s.name == attr_name}
37
+ prev_attr, prev_dir = existing_sort.name, existing_sort.dir
38
+ end
39
+
40
+ options = args.first.is_a?(Hash) ? args.shift.dup : {}
41
+ default_order = options.delete :default_order
42
+ current_dir = prev_attr == attr_name ? prev_dir : nil
43
+
44
+ if current_dir
45
+ new_dir = current_dir == 'desc' ? 'asc' : 'desc'
46
+ else
47
+ new_dir = default_order || 'asc'
48
+ end
49
+
50
+ html_options = args.first.is_a?(Hash) ? args.shift.dup : {}
51
+ css = ['sort_link', current_dir].compact.join(' ')
52
+ html_options[:class] = [css, html_options[:class]].compact.join(' ')
53
+ options.merge!(
54
+ :q => search_params.merge(:s => "#{attr_name} #{new_dir}")
55
+ )
56
+ link_to [ERB::Util.h(name), order_indicator_for(current_dir)].compact.join(' ').html_safe,
57
+ url_for(options),
58
+ html_options
59
+ end
60
+
61
+ private
62
+
63
+ def order_indicator_for(order)
64
+ if order == 'asc'
65
+ '▲'
66
+ elsif order == 'desc'
67
+ '▼'
68
+ else
69
+ nil
70
+ end
71
+ end
72
+
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,2 @@
1
+ require 'ransack/helpers/form_builder'
2
+ require 'ransack/helpers/form_helper'
@@ -0,0 +1,70 @@
1
+ en:
2
+ ransack:
3
+ search: "search"
4
+ predicate: "predicate"
5
+ and: "and"
6
+ or: "or"
7
+ any: "any"
8
+ all: "all"
9
+ combinator: "combinator"
10
+ attribute: "attribute"
11
+ value: "value"
12
+ condition: "condition"
13
+ sort: "sort"
14
+ asc: "ascending"
15
+ desc: "descending"
16
+ predicates:
17
+ eq: "equals"
18
+ eq_any: "equals any"
19
+ eq_all: "equals all"
20
+ not_eq: "not equal to"
21
+ not_eq_any: "not equal to any"
22
+ not_eq_all: "not equal to all"
23
+ matches: "matches"
24
+ matches_any: "matches_any"
25
+ matches_all: "matches all"
26
+ does_not_match: "doesn't match"
27
+ does_not_match_any: "doesn't match any"
28
+ does_not_match_all: "doesn't match all"
29
+ lt: "less than"
30
+ lt_any: "less than any"
31
+ lt_all: "less than all"
32
+ lteq: "less than or equal to"
33
+ lteq_any: "less than or equal to any"
34
+ lteq_all: "less than or equal to all"
35
+ gt: "greater than"
36
+ gt_any: "greater than any"
37
+ gt_all: "greater than all"
38
+ gteq: "greater than or equal to"
39
+ gteq_any: "greater than or equal to any"
40
+ gteq_all: "greater than or equal to all"
41
+ in: "in"
42
+ in_any: "in any"
43
+ in_all: "in all"
44
+ not_in: "not in"
45
+ not_in_any: "not in any"
46
+ not_in_all: "not in all"
47
+ cont: "contains"
48
+ cont_any: "contains any"
49
+ cont_all: "contains all"
50
+ not_cont: "doesn't contain"
51
+ not_cont_any: "doesn't contain any"
52
+ not_cont_all: "doesn't contain all"
53
+ start: "starts with"
54
+ start_any: "starts with any"
55
+ start_all: "starts with all"
56
+ not_start: "doesn't start with"
57
+ not_start_any: "doesn't start with any"
58
+ not_start_all: "doesn't start with all"
59
+ end: "ends with"
60
+ end_any: "ends with any"
61
+ end_all: "ends with all"
62
+ not_end: "doesn't end with"
63
+ not_end_any: "doesn't end with any"
64
+ not_end_all: "doesn't end with all"
65
+ 'true': "is true"
66
+ 'false': "is false"
67
+ present: "is present"
68
+ blank: "is blank"
69
+ 'null': "is null"
70
+ not_null: "is not null"
@@ -0,0 +1,53 @@
1
+ module Ransack
2
+ module Naming
3
+
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ def persisted?
9
+ false
10
+ end
11
+
12
+ def to_key
13
+ nil
14
+ end
15
+
16
+ def to_param
17
+ nil
18
+ end
19
+
20
+ def to_model
21
+ self
22
+ end
23
+ end
24
+
25
+ class Name < String
26
+ attr_reader :singular, :plural, :element, :collection, :partial_path, :human, :param_key, :route_key, :i18n_key
27
+ alias_method :cache_key, :collection
28
+
29
+ def initialize
30
+ super("Search")
31
+ @singular = "search".freeze
32
+ @plural = "searches".freeze
33
+ @element = "search".freeze
34
+ @human = "Search".freeze
35
+ @collection = "ransack/searches".freeze
36
+ @partial_path = "#{@collection}/#{@element}".freeze
37
+ @param_key = "q".freeze
38
+ @route_key = "searches".freeze
39
+ @i18n_key = :ransack
40
+ end
41
+ end
42
+
43
+ module ClassMethods
44
+ def model_name
45
+ @_model_name ||= Name.new
46
+ end
47
+
48
+ def i18n_scope
49
+ :ransack
50
+ end
51
+ end
52
+
53
+ end
@@ -0,0 +1,49 @@
1
+ module Ransack
2
+ module Nodes
3
+ class Attribute < Node
4
+ include Bindable
5
+
6
+ attr_reader :name
7
+
8
+ delegate :blank?, :present?, :==, :to => :name
9
+ delegate :engine, :to => :context
10
+
11
+ def initialize(context, name = nil)
12
+ super(context)
13
+ self.name = name unless name.blank?
14
+ end
15
+
16
+ def name=(name)
17
+ @name = name
18
+ context.bind(self, name) unless name.blank?
19
+ end
20
+
21
+ def valid?
22
+ bound? && attr
23
+ end
24
+
25
+ def type
26
+ if ransacker
27
+ return ransacker.type
28
+ else
29
+ context.type_for(self)
30
+ end
31
+ end
32
+
33
+ def eql?(other)
34
+ self.class == other.class &&
35
+ self.name == other.name
36
+ end
37
+ alias :== :eql?
38
+
39
+ def hash
40
+ self.name.hash
41
+ end
42
+
43
+ def persisted?
44
+ false
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,30 @@
1
+ module Ransack
2
+ module Nodes
3
+ module Bindable
4
+
5
+ attr_accessor :parent, :attr_name
6
+
7
+ def attr
8
+ @attr ||= ransacker ? ransacker.attr_from(self) : context.table_for(parent)[attr_name]
9
+ end
10
+ alias :arel_attribute :attr
11
+
12
+ def ransacker
13
+ klass._ransackers[attr_name]
14
+ end
15
+
16
+ def klass
17
+ @klass ||= context.klassify(parent)
18
+ end
19
+
20
+ def bound?
21
+ attr_name.present? && parent.present?
22
+ end
23
+
24
+ def reset_binding!
25
+ @parent = @attr_name = @attr = @klass = nil
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,212 @@
1
+ module Ransack
2
+ module Nodes
3
+ class Condition < Node
4
+ i18n_word :attribute, :predicate, :combinator, :value
5
+ i18n_alias :a => :attribute, :p => :predicate, :m => :combinator, :v => :value
6
+
7
+ attr_accessor :predicate
8
+ attr_writer :is_default
9
+
10
+ class << self
11
+ def extract(context, key, values)
12
+ attributes, predicate = extract_attributes_and_predicate(key)
13
+ if attributes.size > 0
14
+ combinator = key.match(/_(or|and)_/) ? $1 : nil
15
+ condition = self.new(context)
16
+ condition.build(
17
+ :a => attributes,
18
+ :p => predicate.name,
19
+ :m => combinator,
20
+ :v => predicate.wants_array ? Array(values) : [values]
21
+ )
22
+ # TODO: Figure out what to do with multiple types of attributes, if anything.
23
+ # Tempted to go with "garbage in, garbage out" on this one
24
+ predicate.validate(condition.values, condition.default_type) ? condition : nil
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def extract_attributes_and_predicate(key)
31
+ str = key.dup
32
+ name = Predicate.detect_and_strip_from_string!(str)
33
+ predicate = Predicate.named(name)
34
+ raise ArgumentError, "No valid predicate for #{key}" unless predicate
35
+ attributes = str.split(/_and_|_or_/)
36
+ [attributes, predicate]
37
+ end
38
+ end
39
+
40
+ def valid?
41
+ attributes.detect(&:valid?) && predicate && valid_arity? && predicate.validate(values, default_type) && valid_combinator?
42
+ end
43
+
44
+ def valid_arity?
45
+ values.size <= 1 || predicate.wants_array
46
+ end
47
+
48
+ def attributes
49
+ @attributes ||= []
50
+ end
51
+ alias :a :attributes
52
+
53
+ def attributes=(args)
54
+ case args
55
+ when Array
56
+ args.each do |attr|
57
+ attr = Attribute.new(@context, attr)
58
+ self.attributes << attr if attr.valid?
59
+ end
60
+ when Hash
61
+ args.each do |index, attrs|
62
+ attr = Attribute.new(@context, attrs[:name])
63
+ self.attributes << attr if attr.valid?
64
+ end
65
+ else
66
+ raise ArgumentError, "Invalid argument (#{args.class}) supplied to attributes="
67
+ end
68
+ end
69
+ alias :a= :attributes=
70
+
71
+ def values
72
+ @values ||= []
73
+ end
74
+ alias :v :values
75
+
76
+ def values=(args)
77
+ case args
78
+ when Array
79
+ args.each do |val|
80
+ val = Value.new(@context, val)
81
+ self.values << val
82
+ end
83
+ when Hash
84
+ args.each do |index, attrs|
85
+ val = Value.new(@context, attrs[:value])
86
+ self.values << val
87
+ end
88
+ else
89
+ raise ArgumentError, "Invalid argument (#{args.class}) supplied to values="
90
+ end
91
+ end
92
+ alias :v= :values=
93
+
94
+ def combinator
95
+ @attributes.size > 1 ? @combinator : nil
96
+ end
97
+
98
+ def combinator=(val)
99
+ @combinator = ['and', 'or'].detect {|v| v == val.to_s} || nil
100
+ end
101
+ alias :m= :combinator=
102
+ alias :m :combinator
103
+
104
+ def build_attribute(name = nil)
105
+ Attribute.new(@context, name).tap do |attribute|
106
+ self.attributes << attribute
107
+ end
108
+ end
109
+
110
+ def build_value(val = nil)
111
+ Value.new(@context, val).tap do |value|
112
+ self.values << value
113
+ end
114
+ end
115
+
116
+ def value
117
+ predicate.wants_array ? values.map {|v| v.cast(default_type)} : values.first.cast(default_type)
118
+ end
119
+
120
+ def build(params)
121
+ params.with_indifferent_access.each do |key, value|
122
+ if key.match(/^(a|v|p|m)$/)
123
+ self.send("#{key}=", value)
124
+ end
125
+ end
126
+
127
+ self
128
+ end
129
+
130
+ def persisted?
131
+ false
132
+ end
133
+
134
+ def default?
135
+ @is_default
136
+ end
137
+
138
+ def key
139
+ @key ||= attributes.map(&:name).join("_#{combinator}_") + "_#{predicate.name}"
140
+ end
141
+
142
+ def eql?(other)
143
+ self.class == other.class &&
144
+ self.attributes == other.attributes &&
145
+ self.predicate == other.predicate &&
146
+ self.values == other.values &&
147
+ self.combinator == other.combinator
148
+ end
149
+ alias :== :eql?
150
+
151
+ def hash
152
+ [attributes, predicate, values, combinator].hash
153
+ end
154
+
155
+ def predicate_name=(name)
156
+ self.predicate = Predicate.named(name)
157
+ end
158
+ alias :p= :predicate_name=
159
+
160
+ def predicate_name
161
+ predicate.name if predicate
162
+ end
163
+ alias :p :predicate_name
164
+
165
+ def arel_predicate
166
+ predicates = attributes.map do |attr|
167
+ attr.attr.send(predicate.arel_predicate, formatted_values_for_attribute(attr))
168
+ end
169
+
170
+ if predicates.size > 1
171
+ case combinator
172
+ when 'and'
173
+ Arel::Nodes::Grouping.new(Arel::Nodes::And.new(predicates))
174
+ when 'or'
175
+ predicates.inject(&:or)
176
+ end
177
+ else
178
+ predicates.first
179
+ end
180
+ end
181
+
182
+ def validated_values
183
+ values.select {|v| predicate.validator.call(v.value)}
184
+ end
185
+
186
+ def casted_values_for_attribute(attr)
187
+ validated_values.map {|v| v.cast(predicate.type || attr.type)}
188
+ end
189
+
190
+ def formatted_values_for_attribute(attr)
191
+ formatted = casted_values_for_attribute(attr).map do |val|
192
+ val = attr.ransacker.formatter.call(val) if attr.ransacker && attr.ransacker.formatter
193
+ val = predicate.format(val)
194
+ val
195
+ end
196
+ predicate.wants_array ? formatted : formatted.first
197
+ end
198
+
199
+ def default_type
200
+ predicate.type || (attributes.first && attributes.first.type)
201
+ end
202
+
203
+ private
204
+
205
+ def valid_combinator?
206
+ attributes.size < 2 ||
207
+ ['and', 'or'].include?(combinator)
208
+ end
209
+
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,183 @@
1
+ module Ransack
2
+ module Nodes
3
+ class Grouping < Node
4
+ attr_reader :conditions
5
+ attr_accessor :combinator
6
+ alias :m :combinator
7
+ alias :m= :combinator=
8
+
9
+ i18n_word :condition, :and, :or
10
+ i18n_alias :c => :condition, :n => :and, :o => :or
11
+
12
+ delegate :each, :to => :values
13
+
14
+ def initialize(context, combinator = nil)
15
+ super(context)
16
+ self.combinator = combinator.to_s if combinator
17
+ end
18
+
19
+ def persisted?
20
+ false
21
+ end
22
+
23
+ def translate(key, options = {})
24
+ super or Translate.attribute(key.to_s, options.merge(:context => context))
25
+ end
26
+
27
+ def conditions
28
+ @conditions ||= []
29
+ end
30
+ alias :c :conditions
31
+
32
+ def conditions=(conditions)
33
+ case conditions
34
+ when Array
35
+ conditions.each do |attrs|
36
+ condition = Condition.new(@context).build(attrs)
37
+ self.conditions << condition if condition.valid?
38
+ end
39
+ when Hash
40
+ conditions.each do |index, attrs|
41
+ condition = Condition.new(@context).build(attrs)
42
+ self.conditions << condition if condition.valid?
43
+ end
44
+ end
45
+
46
+ self.conditions.uniq!
47
+ end
48
+ alias :c= :conditions=
49
+
50
+ def [](key)
51
+ if condition = conditions.detect {|c| c.key == key.to_s}
52
+ condition
53
+ else
54
+ nil
55
+ end
56
+ end
57
+
58
+ def []=(key, value)
59
+ conditions.reject! {|c| c.key == key.to_s}
60
+ self.conditions << value
61
+ end
62
+
63
+ def values
64
+ conditions + groupings
65
+ end
66
+
67
+ def respond_to?(method_id)
68
+ super or begin
69
+ method_name = method_id.to_s
70
+ writer = method_name.sub!(/\=$/, '')
71
+ attribute_method?(method_name) ? true : false
72
+ end
73
+ end
74
+
75
+ def build_condition(opts = {})
76
+ new_condition(opts).tap do |condition|
77
+ self.conditions << condition
78
+ end
79
+ end
80
+
81
+ def new_condition(opts = {})
82
+ attrs = opts[:attributes] || 1
83
+ vals = opts[:values] || 1
84
+ condition = Condition.new(@context)
85
+ condition.predicate_name = opts[:predicate] || 'eq'
86
+ condition.is_default = true
87
+ attrs.times { condition.build_attribute }
88
+ vals.times { condition.build_value }
89
+ condition
90
+ end
91
+
92
+ def groupings
93
+ @groupings ||= []
94
+ end
95
+ alias :g :groupings
96
+
97
+ def groupings=(groupings)
98
+ case groupings
99
+ when Array
100
+ groupings.each do |attrs|
101
+ grouping_object = new_grouping(attrs)
102
+ self.groupings << grouping_object if grouping_object.values.any?
103
+ end
104
+ when Hash
105
+ groupings.each do |index, attrs|
106
+ grouping_object = new_grouping(attrs)
107
+ self.groupings << grouping_object if grouping_object.values.any?
108
+ end
109
+ else
110
+ raise ArgumentError, "Invalid argument (#{groupings.class}) supplied to groupings="
111
+ end
112
+ end
113
+ alias :g= :groupings=
114
+
115
+ def method_missing(method_id, *args)
116
+ method_name = method_id.to_s
117
+ writer = method_name.sub!(/\=$/, '')
118
+ if attribute_method?(method_name)
119
+ writer ? write_attribute(method_name, *args) : read_attribute(method_name)
120
+ else
121
+ super
122
+ end
123
+ end
124
+
125
+ def attribute_method?(name)
126
+ name = strip_predicate_and_index(name)
127
+ case name
128
+ when /^(g|c|m|groupings|conditions|combinator)=?$/
129
+ true
130
+ else
131
+ name.split(/_and_|_or_/).select {|n| !@context.attribute_method?(n)}.empty?
132
+ end
133
+ end
134
+
135
+ def build_grouping(params = {})
136
+ params ||= {}
137
+ new_grouping(params).tap do |new_grouping|
138
+ self.groupings << new_grouping
139
+ end
140
+ end
141
+
142
+ def new_grouping(params = {})
143
+ Grouping.new(@context).build(params)
144
+ end
145
+
146
+ def build(params)
147
+ params.with_indifferent_access.each do |key, value|
148
+ case key
149
+ when /^(g|c|m)$/
150
+ self.send("#{key}=", value)
151
+ else
152
+ write_attribute(key.to_s, value)
153
+ end
154
+ end
155
+ self
156
+ end
157
+
158
+ private
159
+
160
+ def write_attribute(name, val)
161
+ # TODO: Methods
162
+ if condition = Condition.extract(@context, name, val)
163
+ self[name] = condition
164
+ end
165
+ end
166
+
167
+ def read_attribute(name)
168
+ if self[name].respond_to?(:value)
169
+ self[name].value
170
+ else
171
+ self[name]
172
+ end
173
+ end
174
+
175
+ def strip_predicate_and_index(str)
176
+ string = str.split(/\(/).first
177
+ Predicate.detect_and_strip_from_string!(string)
178
+ string
179
+ end
180
+
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,34 @@
1
+ module Ransack
2
+ module Nodes
3
+ class Node
4
+ attr_reader :context
5
+ delegate :contextualize, :to => :context
6
+ class_attribute :i18n_words
7
+ class_attribute :i18n_aliases
8
+ self.i18n_words = []
9
+ self.i18n_aliases = {}
10
+
11
+ class << self
12
+ def i18n_word(*args)
13
+ self.i18n_words += args.map(&:to_s)
14
+ end
15
+
16
+ def i18n_alias(opts = {})
17
+ self.i18n_aliases.merge! Hash[opts.map {|k, v| [k.to_s, v.to_s]}]
18
+ end
19
+ end
20
+
21
+ def initialize(context)
22
+ @context = context
23
+ end
24
+
25
+ def translate(key, options = {})
26
+ key = i18n_aliases[key.to_s] if i18n_aliases.has_key?(key.to_s)
27
+ if i18n_words.include?(key.to_s)
28
+ Translate.word(key)
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end