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,41 @@
1
+ module Ransack
2
+ module Nodes
3
+ class Sort < Node
4
+ include Bindable
5
+
6
+ attr_reader :name, :dir
7
+ i18n_word :asc, :desc
8
+
9
+ class << self
10
+ def extract(context, str)
11
+ attr, direction = str.split(/\s+/,2)
12
+ self.new(context).build(:name => attr, :dir => direction)
13
+ end
14
+ end
15
+
16
+ def build(params)
17
+ params.with_indifferent_access.each do |key, value|
18
+ if key.match(/^(name|dir)$/)
19
+ self.send("#{key}=", value)
20
+ end
21
+ end
22
+
23
+ self
24
+ end
25
+
26
+ def valid?
27
+ bound? && attr
28
+ end
29
+
30
+ def name=(name)
31
+ @name = name
32
+ context.bind(self, name) unless name.blank?
33
+ end
34
+
35
+ def dir=(dir)
36
+ @dir = %w(asc desc).include?(dir) ? dir : 'asc'
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,108 @@
1
+ module Ransack
2
+ module Nodes
3
+ class Value < Node
4
+ attr_accessor :value
5
+ delegate :present?, :blank?, :to => :value
6
+
7
+ def initialize(context, value = nil)
8
+ super(context)
9
+ @value = value
10
+ end
11
+
12
+ def persisted?
13
+ false
14
+ end
15
+
16
+ def eql?(other)
17
+ self.class == other.class &&
18
+ self.value == other.value
19
+ end
20
+ alias :== :eql?
21
+
22
+ def hash
23
+ value.hash
24
+ end
25
+
26
+ def cast(type)
27
+ case type
28
+ when :date
29
+ cast_to_date(value)
30
+ when :datetime, :timestamp, :time
31
+ cast_to_time(value)
32
+ when :boolean
33
+ cast_to_boolean(value)
34
+ when :integer
35
+ cast_to_integer(value)
36
+ when :float
37
+ cast_to_float(value)
38
+ when :decimal
39
+ cast_to_decimal(value)
40
+ else
41
+ cast_to_string(value)
42
+ end
43
+ end
44
+
45
+ def cast_to_date(val)
46
+ if val.is_a?(String)
47
+ Chronic.parse(val).in_time_zone.to_date rescue nil
48
+ elsif val.respond_to?(:to_date)
49
+ val.to_date rescue nil
50
+ else
51
+ y, m, d = *[val].flatten
52
+ m ||= 1
53
+ d ||= 1
54
+ Date.new(y,m,d) rescue nil
55
+ end
56
+ end
57
+
58
+ def cast_to_time(val)
59
+ if val.is_a?(Array)
60
+ Time.zone.local(*val) rescue nil
61
+ else
62
+ unless val.acts_like?(:time)
63
+ val = val.is_a?(String) ? Chronic.parse(val) : val.to_time rescue nil
64
+ end
65
+ val.in_time_zone rescue nil
66
+ end
67
+ end
68
+
69
+ def cast_to_boolean(val)
70
+ if Constants::TRUE_VALUES.include?(val)
71
+ true
72
+ elsif Constants::FALSE_VALUES.include?(val)
73
+ false
74
+ else
75
+ nil
76
+ end
77
+ end
78
+
79
+ def cast_to_string(val)
80
+ val.respond_to?(:to_s) ? val.to_s : String.new(val)
81
+ end
82
+
83
+ def cast_to_integer(val)
84
+ val.blank? ? nil : val.to_i
85
+ end
86
+
87
+ def cast_to_float(val)
88
+ val.blank? ? nil : val.to_f
89
+ end
90
+
91
+ def cast_to_decimal(val)
92
+ if val.blank?
93
+ nil
94
+ elsif val.class == BigDecimal
95
+ val
96
+ elsif val.respond_to?(:to_d)
97
+ val.to_d
98
+ else
99
+ val.to_s.to_d
100
+ end
101
+ end
102
+
103
+ def array_of_arrays?(val)
104
+ Array === val && Array === val.first
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,7 @@
1
+ require 'ransack/nodes/bindable'
2
+ require 'ransack/nodes/node'
3
+ require 'ransack/nodes/attribute'
4
+ require 'ransack/nodes/value'
5
+ require 'ransack/nodes/condition'
6
+ require 'ransack/nodes/sort'
7
+ require 'ransack/nodes/grouping'
@@ -0,0 +1,70 @@
1
+ module Ransack
2
+ class Predicate
3
+ attr_reader :name, :arel_predicate, :type, :formatter, :validator, :compound, :wants_array
4
+
5
+ class << self
6
+
7
+ def names
8
+ Ransack.predicates.keys
9
+ end
10
+
11
+ def names_by_decreasing_length
12
+ names.sort {|a,b| b.length <=> a.length}
13
+ end
14
+
15
+ def named(name)
16
+ Ransack.predicates[name.to_s]
17
+ end
18
+
19
+ def detect_and_strip_from_string!(str)
20
+ names_by_decreasing_length.detect {|p| str.sub!(/_#{p}$/, '')}
21
+ end
22
+
23
+ def detect_from_string(str)
24
+ names_by_decreasing_length.detect {|p| str.match(/_#{p}$/)}
25
+ end
26
+
27
+ def name_from_attribute_name(attribute_name)
28
+ names_by_decreasing_length.detect {|p| attribute_name.to_s.match(/_#{p}$/)}
29
+ end
30
+
31
+ def for_attribute_name(attribute_name)
32
+ self.named(detect_from_string(attribute_name.to_s))
33
+ end
34
+
35
+ end
36
+
37
+ def initialize(opts = {})
38
+ @name = opts[:name]
39
+ @arel_predicate = opts[:arel_predicate]
40
+ @type = opts[:type]
41
+ @formatter = opts[:formatter]
42
+ @validator = opts[:validator] || lambda { |v| v.respond_to?(:empty?) ? !v.empty? : !v.nil? }
43
+ @compound = opts[:compound]
44
+ @wants_array = @compound || ['in', 'not_in'].include?(@arel_predicate)
45
+ end
46
+
47
+ def eql?(other)
48
+ self.class == other.class &&
49
+ self.name == other.name
50
+ end
51
+ alias :== :eql?
52
+
53
+ def hash
54
+ name.hash
55
+ end
56
+
57
+ def format(val)
58
+ if formatter
59
+ formatter.call(val)
60
+ else
61
+ val
62
+ end
63
+ end
64
+
65
+ def validate(vals, type = @type)
66
+ vals.select {|v| validator.call(type ? v.cast(type) : v.value)}.any?
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,24 @@
1
+ module Ransack
2
+ class Ransacker
3
+
4
+ attr_reader :name, :type, :formatter, :args
5
+
6
+ delegate :call, :to => :@callable
7
+
8
+ def initialize(klass, name, opts = {}, &block)
9
+ @klass, @name = klass, name
10
+
11
+ @type = opts[:type] || :string
12
+ @args = opts[:args] || [:parent]
13
+ @formatter = opts[:formatter]
14
+ @callable = opts[:callable] || block ||
15
+ (@klass.method(name) if @klass.respond_to?(name)) ||
16
+ proc {|parent| parent.table[name]}
17
+ end
18
+
19
+ def attr_from(bindable)
20
+ call(*args.map {|arg| bindable.send(arg)})
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,123 @@
1
+ require 'ransack/nodes'
2
+ require 'ransack/context'
3
+ require 'ransack/naming'
4
+
5
+ module Ransack
6
+ class Search
7
+ include Naming
8
+
9
+ attr_reader :base, :context
10
+
11
+ delegate :object, :klass, :to => :context
12
+ delegate :new_grouping, :new_condition,
13
+ :build_grouping, :build_condition,
14
+ :translate, :to => :base
15
+
16
+ def initialize(object, params = {}, options = {})
17
+ params ||= {}
18
+ @context = Context.for(object, options)
19
+ @context.auth_object = options[:auth_object]
20
+ @base = Nodes::Grouping.new(@context, 'and')
21
+ build(params.with_indifferent_access)
22
+ end
23
+
24
+ def result(opts = {})
25
+ @context.evaluate(self, opts)
26
+ end
27
+
28
+ def inspect
29
+ # Replace the inspect method on object's singleton class to call to_s() instead of to_a().
30
+ object.class_eval { alias :inspect :to_s }
31
+ super
32
+ end
33
+
34
+ def build(params)
35
+ collapse_multiparameter_attributes!(params).each do |key, value|
36
+ case key
37
+ when 's', 'sorts'
38
+ send("#{key}=", value)
39
+ else
40
+ base.send("#{key}=", value) if base.attribute_method?(key)
41
+ end
42
+ end
43
+ self
44
+ end
45
+
46
+ def sorts=(args)
47
+ case args
48
+ when Array
49
+ args.each do |sort|
50
+ sort = Nodes::Sort.extract(@context, sort)
51
+ self.sorts << sort
52
+ end
53
+ when Hash
54
+ args.each do |index, attrs|
55
+ sort = Nodes::Sort.new(@context).build(attrs)
56
+ self.sorts << sort
57
+ end
58
+ when String
59
+ self.sorts = [args]
60
+ else
61
+ raise ArgumentError, "Invalid argument (#{args.class}) supplied to sorts="
62
+ end
63
+ end
64
+ alias :s= :sorts=
65
+
66
+ def sorts
67
+ @sorts ||= []
68
+ end
69
+ alias :s :sorts
70
+
71
+ def build_sort(opts = {})
72
+ new_sort(opts).tap do |sort|
73
+ self.sorts << sort
74
+ end
75
+ end
76
+
77
+ def new_sort(opts = {})
78
+ Nodes::Sort.new(@context).build(opts)
79
+ end
80
+
81
+ def respond_to?(method_id)
82
+ super or begin
83
+ method_name = method_id.to_s
84
+ writer = method_name.sub!(/\=$/, '')
85
+ base.attribute_method?(method_name) ? true : false
86
+ end
87
+ end
88
+
89
+ def method_missing(method_id, *args)
90
+ method_name = method_id.to_s
91
+ writer = method_name.sub!(/\=$/, '')
92
+ if base.attribute_method?(method_name)
93
+ base.send(method_id, *args)
94
+ else
95
+ super
96
+ end
97
+ end
98
+
99
+ private
100
+
101
+ def collapse_multiparameter_attributes!(attrs)
102
+ attrs.keys.each do |k|
103
+ if k.include?("(")
104
+ real_attribute, position = k.split(/\(|\)/)
105
+ cast = %w(a s i).include?(position.last) ? position.last : nil
106
+ position = position.to_i - 1
107
+ value = attrs.delete(k)
108
+ attrs[real_attribute] ||= []
109
+ attrs[real_attribute][position] = if cast
110
+ (value.blank? && cast == 'i') ? nil : value.send("to_#{cast}")
111
+ else
112
+ value
113
+ end
114
+ elsif Hash === attrs[k]
115
+ collapse_multiparameter_attributes!(attrs[k])
116
+ end
117
+ end
118
+
119
+ attrs
120
+ end
121
+
122
+ end
123
+ end
@@ -0,0 +1,92 @@
1
+ I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'locale', '*.yml')]
2
+
3
+ module Ransack
4
+ module Translate
5
+ def self.word(key, options = {})
6
+ I18n.translate(:"ransack.#{key}", :default => key.to_s)
7
+ end
8
+
9
+ def self.predicate(key, options = {})
10
+ I18n.translate(:"ransack.predicates.#{key}", :default => key.to_s)
11
+ end
12
+
13
+ def self.attribute(key, options = {})
14
+ unless context = options.delete(:context)
15
+ raise ArgumentError, "A context is required to translate attributes"
16
+ end
17
+
18
+ original_name = key.to_s
19
+ base_class = context.klass
20
+ base_ancestors = base_class.ancestors.select { |x| x.respond_to?(:model_name) }
21
+ predicate = Predicate.detect_from_string(original_name)
22
+ attributes_str = original_name.sub(/_#{predicate}$/, '')
23
+ attribute_names = attributes_str.split(/_and_|_or_/)
24
+ combinator = attributes_str.match(/_and_/) ? :and : :or
25
+ defaults = base_ancestors.map do |klass|
26
+ :"ransack.attributes.#{klass.model_name.underscore}.#{original_name}"
27
+ end
28
+
29
+ translated_names = attribute_names.map do |attr|
30
+ attribute_name(context, attr, options[:include_associations])
31
+ end
32
+
33
+ interpolations = {}
34
+ interpolations[:attributes] = translated_names.join(" #{Translate.word(combinator)} ")
35
+
36
+ if predicate
37
+ defaults << "%{attributes} %{predicate}"
38
+ interpolations[:predicate] = Translate.predicate(predicate)
39
+ else
40
+ defaults << "%{attributes}"
41
+ end
42
+
43
+ defaults << options.delete(:default) if options[:default]
44
+ options.reverse_merge! :count => 1, :default => defaults
45
+ I18n.translate(defaults.shift, options.merge(interpolations))
46
+ end
47
+
48
+ def self.association(key, options = {})
49
+ unless context = options.delete(:context)
50
+ raise ArgumentError, "A context is required to translate associations"
51
+ end
52
+
53
+ defaults = key.blank? ? [:"#{context.klass.i18n_scope}.models.#{context.klass.model_name.underscore}"] : [:"ransack.associations.#{context.klass.model_name.underscore}.#{key}"]
54
+ defaults << context.traverse(key).model_name.human
55
+ options = {:count => 1, :default => defaults}
56
+ I18n.translate(defaults.shift, options)
57
+ end
58
+
59
+ private
60
+
61
+ def self.attribute_name(context, name, include_associations = nil)
62
+ assoc_path = context.association_path(name)
63
+ associated_class = context.traverse(assoc_path) if assoc_path.present?
64
+ attr_name = name.sub(/^#{assoc_path}_/, '')
65
+ interpolations = {}
66
+ interpolations[:attr_fallback_name] = I18n.translate(
67
+ (associated_class ?
68
+ :"ransack.attributes.#{associated_class.model_name.underscore}.#{attr_name}" :
69
+ :"ransack.attributes.#{context.klass.model_name.underscore}.#{attr_name}"
70
+ ),
71
+ :default => [
72
+ (associated_class ?
73
+ :"#{associated_class.i18n_scope}.attributes.#{associated_class.model_name.underscore}.#{attr_name}" :
74
+ :"#{context.klass.i18n_scope}.attributes.#{context.klass.model_name.underscore}.#{attr_name}"
75
+ ),
76
+ attr_name.humanize
77
+ ]
78
+ )
79
+ defaults = [
80
+ :"ransack.attributes.#{context.klass.model_name.underscore}.#{name}"
81
+ ]
82
+ if include_associations && associated_class
83
+ defaults << '%{association_name} %{attr_fallback_name}'
84
+ interpolations[:association_name] = association(assoc_path, :context => context)
85
+ else
86
+ defaults << '%{attr_fallback_name}'
87
+ end
88
+ options = {:count => 1, :default => defaults}
89
+ I18n.translate(defaults.shift, options.merge(interpolations))
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,3 @@
1
+ module Ransack
2
+ VERSION = "0.6.0"
3
+ end
@@ -0,0 +1,68 @@
1
+ module Ransack
2
+ class Visitor
3
+
4
+ def accept(object)
5
+ visit(object)
6
+ end
7
+
8
+ def can_accept?(object)
9
+ respond_to? DISPATCH[object.class]
10
+ end
11
+
12
+ def visit_Array(object)
13
+ object.map {|o| accept(o)}.compact
14
+ end
15
+
16
+ def visit_Ransack_Nodes_Condition(object)
17
+ object.arel_predicate if object.valid?
18
+ end
19
+
20
+ def visit_Ransack_Nodes_Grouping(object)
21
+ object.combinator == 'or' ? visit_or(object) : visit_and(object)
22
+ end
23
+
24
+ def visit_and(object)
25
+ nodes = object.values.map {|o| accept(o)}.compact
26
+ return nil unless nodes.size > 0
27
+
28
+ if nodes.size > 1
29
+ Arel::Nodes::Grouping.new(Arel::Nodes::And.new(nodes))
30
+ else
31
+ nodes.first
32
+ end
33
+ end
34
+
35
+ def visit_or(object)
36
+ nodes = object.values.map {|o| accept(o)}.compact
37
+ return nil unless nodes.size > 0
38
+
39
+ if nodes.size > 1
40
+ nodes.inject(&:or)
41
+ else
42
+ nodes.first
43
+ end
44
+ end
45
+
46
+ def visit_Ransack_Nodes_Sort(object)
47
+ object.attr.send(object.dir) if object.valid?
48
+ end
49
+
50
+ def quoted?(object)
51
+ case object
52
+ when Arel::Nodes::SqlLiteral, Bignum, Fixnum
53
+ false
54
+ else
55
+ true
56
+ end
57
+ end
58
+
59
+ def visit(object)
60
+ send(DISPATCH[object.class], object)
61
+ end
62
+
63
+ DISPATCH = Hash.new do |hash, klass|
64
+ hash[klass] = "visit_#{klass.name.gsub('::', '_')}"
65
+ end
66
+
67
+ end
68
+ end
data/lib/ransack.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'ransack/configuration'
2
+
3
+ module Ransack
4
+ extend Configuration
5
+
6
+ class UntraversableAssociationError < StandardError; end;
7
+ end
8
+
9
+ Ransack.configure do |config|
10
+ Ransack::Constants::AREL_PREDICATES.each do |name|
11
+ config.add_predicate name, :arel_predicate => name
12
+ end
13
+
14
+ Ransack::Constants::DERIVED_PREDICATES.each do |args|
15
+ config.add_predicate *args
16
+ end
17
+ end
18
+
19
+ require 'ransack/translate'
20
+ require 'ransack/search'
21
+ require 'ransack/ransacker'
22
+ require 'ransack/adapters/active_record'
23
+ require 'ransack/helpers'
24
+ require 'action_controller'
25
+ require 'chronic'
26
+
27
+ ActionController::Base.helper Ransack::Helpers::FormHelper
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "ransack/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "ransack_ffcrm"
7
+ s.version = Ransack::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Ernie Miller"]
10
+ s.email = ["ernie@metautonomo.us"]
11
+ s.homepage = "http://metautonomo.us/projects/ransack"
12
+ s.summary = %q{Object-based searching for ActiveRecord (currently).}
13
+ s.description = %q{Ransack is the successor to the MetaSearch gem. It improves and expands upon MetaSearch's functionality, but does not have a 100%-compatible API.}
14
+
15
+ s.rubyforge_project = "ransack"
16
+
17
+ s.add_dependency 'activerecord', '~> 3.0'
18
+ s.add_dependency 'actionpack', '~> 3.0'
19
+ s.add_dependency 'polyamorous', '~> 0.5.0'
20
+ s.add_dependency 'chronic', '~> 0.6.7'
21
+ s.add_development_dependency 'rspec', '~> 2.8.0'
22
+ s.add_development_dependency 'machinist', '~> 1.0.6'
23
+ s.add_development_dependency 'faker', '~> 0.9.5'
24
+ s.add_development_dependency 'sqlite3', '~> 1.3.3'
25
+
26
+ s.files = `git ls-files`.split("\n")
27
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
28
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
29
+ s.require_paths = ["lib"]
30
+ end
@@ -0,0 +1,5 @@
1
+ Article.blueprint do
2
+ person
3
+ title
4
+ body
5
+ end
@@ -0,0 +1,5 @@
1
+ Comment.blueprint do
2
+ article
3
+ person
4
+ body
5
+ end
@@ -0,0 +1,3 @@
1
+ Note.blueprint do
2
+ note
3
+ end
@@ -0,0 +1,4 @@
1
+ Person.blueprint do
2
+ name
3
+ salary
4
+ end
@@ -0,0 +1,3 @@
1
+ Tag.blueprint do
2
+ name { Sham.tag_name }
3
+ end
data/spec/console.rb ADDED
@@ -0,0 +1,21 @@
1
+ Bundler.setup
2
+ require 'machinist/active_record'
3
+ require 'sham'
4
+ require 'faker'
5
+ require 'ransack'
6
+
7
+ Dir[File.expand_path('../../spec/{helpers,support,blueprints}/*.rb', __FILE__)].each do |f|
8
+ require f
9
+ end
10
+
11
+ Sham.define do
12
+ name { Faker::Name.name }
13
+ title { Faker::Lorem.sentence }
14
+ body { Faker::Lorem.paragraph }
15
+ salary {|index| 30000 + (index * 1000)}
16
+ tag_name { Faker::Lorem.words(3).join(' ') }
17
+ note { Faker::Lorem.words(7).join(' ') }
18
+ end
19
+
20
+ Schema.create
21
+
@@ -0,0 +1,2 @@
1
+ module RansackHelper
2
+ end