meta_search 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,46 @@
1
+ require 'meta_search/builder'
2
+
3
+ module MetaSearch
4
+ module Searches
5
+ module Base
6
+ # Prepares the search to run against your model. Returns an instance of
7
+ # MetaSearch::Builder, which behaves pretty much like an ActiveRecord::Relation,
8
+ # in that it doesn't actually query the database until you do something that
9
+ # requires it to do so.
10
+ def search(opts = {})
11
+ opts ||= {} # to catch nil params
12
+ search_options = opts.delete(:search_options) || {}
13
+ builder = MetaSearch::Builder.new(self, search_options)
14
+ builder.build(opts)
15
+ end
16
+
17
+ private
18
+
19
+ # Excludes model attributes from searchability. This means that searches can't be created against
20
+ # these columns, whether the search is based on this model, or the model's attributes are being
21
+ # searched by association from another model. If a Comment <tt>belongs_to :article</tt> but declares
22
+ # <tt>metasearch_exclude_attr :user_id</tt> then <tt>Comment.search</tt> won't accept parameters
23
+ # like <tt>:user_id_equals</tt>, nor will an Article.search accept the parameter
24
+ # <tt>:comments_user_id_equals</tt>.
25
+ def metasearch_exclude_attr(*args)
26
+ args.each do |attr|
27
+ attr = attr.to_s
28
+ raise(ArgumentError, "No persisted attribute (column) named #{attr} in #{self}") unless self.columns_hash.has_key?(attr)
29
+ self._metasearch_exclude_attributes = (self._metasearch_exclude_attributes + [attr]).uniq
30
+ end
31
+ end
32
+
33
+ # Excludes model associations from searchability. This mean that searches can't be created against
34
+ # these associations. An article that <tt>has_many :comments</tt> but excludes comments from
35
+ # searching by declaring <tt>metasearch_exclude_assoc :comments</tt> won't make any of the
36
+ # <tt>comments_*</tt> methods available.
37
+ def metasearch_exclude_assoc(*args)
38
+ args.each do |assoc|
39
+ assoc = assoc.to_s
40
+ raise(ArgumentError, "No such association #{assoc} in #{self}") unless self.reflect_on_all_associations.map {|a| a.name.to_s}.include?(assoc)
41
+ self._metasearch_exclude_associations = (self._metasearch_exclude_associations + [assoc]).uniq
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,85 @@
1
+ require 'meta_search/exceptions'
2
+
3
+ module MetaSearch
4
+ module Utility #:nodoc:
5
+ private
6
+
7
+ def array_of_arrays?(vals)
8
+ vals.is_a?(Array) && vals.first.is_a?(Array)
9
+ end
10
+
11
+ def array_of_dates?(vals)
12
+ vals.is_a?(Array) && vals.first.respond_to?(:to_time)
13
+ end
14
+
15
+ def cast_attributes(type, vals)
16
+ if array_of_arrays?(vals)
17
+ vals.map! {|v| cast_attributes(type, v)}
18
+ # Need to make sure not to kill multiparam dates/times
19
+ elsif vals.is_a?(Array) && (array_of_dates?(vals) || !(DATES+TIMES).include?(type))
20
+ vals.map! {|v| cast_attribute(type, v)}
21
+ else
22
+ cast_attribute(type, vals)
23
+ end
24
+ end
25
+
26
+ def cast_attribute(type, val)
27
+ case type
28
+ when *STRINGS
29
+ val.respond_to?(:to_s) ? val.to_s : String.new(val)
30
+ when *DATES
31
+ if val.respond_to?(:to_date)
32
+ val.to_date
33
+ else
34
+ y, m, d = *[val].flatten
35
+ m ||= 1
36
+ d ||= 1
37
+ Date.new(y,m,d) rescue nil
38
+ end
39
+ when *TIMES
40
+ if val.respond_to?(:to_time)
41
+ val.to_time
42
+ else
43
+ y, m, d, hh, mm, ss = *[val].flatten
44
+ Time.zone.local(y, m, d, hh, mm, ss) rescue nil
45
+ end
46
+ when *BOOLEANS
47
+ ActiveRecord::ConnectionAdapters::Column.value_to_boolean(val)
48
+ when :integer
49
+ val.blank? ? nil : val.to_i
50
+ when :float
51
+ val.blank? ? nil : val.to_f
52
+ when :decimal
53
+ val.blank? ? nil : ActiveRecord::ConnectionAdapters::Column.value_to_decimal(val)
54
+ else
55
+ raise TypeCastError, "Unable to cast columns of type #{type}"
56
+ end
57
+ end
58
+
59
+ def collapse_multiparameter_options(opts)
60
+ opts.each_key do |k|
61
+ if k.include?("(")
62
+ real_attribute, position = k.split(/\(|\)/)
63
+ cast = %w(a s i).include?(position.last) ? position.last : nil
64
+ position = position.to_i - 1
65
+ value = opts.delete(k)
66
+ opts[real_attribute] ||= []
67
+ opts[real_attribute][position] = if cast
68
+ (value.blank? && cast == 'i') ? nil : value.send("to_#{cast}")
69
+ else
70
+ value
71
+ end
72
+ end
73
+ end
74
+ opts
75
+ end
76
+
77
+ def quote_table_name(name)
78
+ ActiveRecord::Base.connection.quote_table_name(name)
79
+ end
80
+
81
+ def quote_column_name(name)
82
+ ActiveRecord::Base.connection.quote_column_name(name)
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,174 @@
1
+ require 'meta_search/exceptions'
2
+
3
+ module MetaSearch
4
+ # Wheres are how MetaSearch does its magic. Wheres have a name (and possible aliases) which are
5
+ # appended to your model and association attributes. When you instantiate a MetaSearch::Builder
6
+ # against a model (manually or by calling your model's +search+ method) the builder responds to
7
+ # methods named for your model's attributes and associations, suffixed by the name of the Where.
8
+ #
9
+ # These are the default Wheres, broken down by the types of ActiveRecord columns they can search
10
+ # against:
11
+ #
12
+ # === All data types
13
+ #
14
+ # * _equals_ (alias: _eq_) - Just as it sounds.
15
+ # * _does_not_equal_ (alias: _ne_) - The opposite of equals, oddly enough.
16
+ # * _in_ - Takes an array, matches on equality with any of the items in the array.
17
+ # * _not_in_ (alias: _ni_) - Like above, but negated.
18
+ #
19
+ # === Strings
20
+ #
21
+ # * _contains_ (alias: _like_) - Substring match.
22
+ # * _does_not_contain_ (alias: _nlike_) - Negative substring match.
23
+ # * _starts_with_ (alias: _sw_) - Match strings beginning with the entered term.
24
+ # * _does_not_start_with_ (alias: _dnsw_) - The opposite of above.
25
+ # * _ends_with_ (alias: _ew_) - Match strings ending with the entered term.
26
+ # * _does_not_end_with_ (alias: _dnew_) - Negative of above.
27
+ #
28
+ # === Numbers, dates, and times
29
+ #
30
+ # * _greater_than_ (alias: _gt_) - Greater than.
31
+ # * _greater_than_or_equal_to_ (alias: _gte_) - Greater than or equal to.
32
+ # * _less_than_ (alias: _lt_) - Less than.
33
+ # * _less_than_or_equal_to_ (alias: _lte_) - Less than or equal to.
34
+ #
35
+ # So, given a model like this...
36
+ #
37
+ # class Article < ActiveRecord::Base
38
+ # belongs_to :author
39
+ # has_many :comments
40
+ # has_many :moderations, :through => :comments
41
+ # end
42
+ #
43
+ # ...you might end up with attributes like <tt>title_contains</tt>,
44
+ # <tt>comments_title_starts_with</tt>, <tt>moderations_value_less_than</tt>,
45
+ # <tt>author_name_equals</tt>, and so on.
46
+ class Where
47
+ attr_reader :name, :aliases, :types, :condition, :substitutions, :formatter
48
+ def initialize(where)
49
+ if [String,Symbol].include?(where.class)
50
+ where = Where.get(where) or raise ArgumentError("A where could not be instantiated for the argument #{where}")
51
+ end
52
+ @name = where[:name]
53
+ @aliases = where[:aliases]
54
+ @types = where[:types]
55
+ @condition = where[:condition]
56
+ @substitutions = where[:substitutions]
57
+ @formatter = where[:formatter]
58
+ @keep_arrays = where[:keep_arrays]
59
+ end
60
+
61
+ def keep_arrays?
62
+ @keep_arrays
63
+ end
64
+
65
+ # Checks that the given +value+ is valid to use for the substitutions of this where.
66
+ # Requires that there are the same number of parameters as substitutions, and none of'
67
+ # them is blank.
68
+ def valid_substitutions?(*values)
69
+ values.flatten! unless values.size > 1 || self.keep_arrays?
70
+ self.substitutions.count('?') == values.select {|v| !v.blank?}.size
71
+ end
72
+
73
+ class << self
74
+ # At application initialization, you can add additional custom Wheres to the mix.
75
+ # in your application's <tt>config/initializers/meta_search.rb</tt>, place lines
76
+ # like this:
77
+ #
78
+ # MetaSearch::Where.add :between, :btw, {
79
+ # :types => [:integer, :float, :decimal, :date, :datetime, :timestamp, :time],
80
+ # :condition => 'BETWEEN',
81
+ # :substitutions => '? AND ?',
82
+ # :formatter => Proc.new {|param| param}
83
+ # }
84
+ #
85
+ # The first options are all names for the where. Well, the first is a name, the rest
86
+ # are aliases, really. They will determine the suffix you will use to access your Where.
87
+ #
88
+ # <tt>types</tt> is an array of types the comparison is valid for. The where will not
89
+ # be available against columns that are not one of these types. Default is +ALL_TYPES+,
90
+ # Which is one of several MetaSearch constants available for type assignment (the others
91
+ # being +DATES+, +TIIMES+, +STRINGS+, and +NUMBERS+).
92
+ #
93
+ # <tt>condition</tt> is the condition placed between the column and the substitutions, such as
94
+ # BETWEEN, IN, or =. Default is =.
95
+ #
96
+ # <tt>substitutions</tt> is the text that comes next. It's normally going to have some
97
+ # question marks in it (for variable substitution) if it's going to be of much use. The
98
+ # default is ?. Keep in mind if you use more than one ? MetaSearch will require an array
99
+ # be passed to the attribute for substitution.
100
+ #
101
+ # <tt>keep_arrays</tt> tells MetaSearch that if any arrays are received as parameters, they
102
+ # should be used as-is in the substitution, rather than flattened and passed as a list.
103
+ # For example, this is the definition of the "in" Where:
104
+ #
105
+ # ['in', {:types => ALL_TYPES, :condition => 'IN', :substitutions => '(?)',
106
+ # :keep_arrays => true}]
107
+ #
108
+ # <tt>formatter</tt> is the Proc that will do any formatting to the variables to be substituted.
109
+ # The default proc is <tt>{|param| param}</tt>, which doesn't really do anything. If you pass a
110
+ # string, it will be +eval+ed in the context of this Proc.
111
+ #
112
+ # For example, this is the definition of the "contains" Where:
113
+ #
114
+ # ['contains', 'like', {:types => STRINGS, :condition => 'LIKE', :formatter => '"%#{param}%"'}]
115
+ #
116
+ # Be sure to single-quote the string, so that variables aren't interpolated until later. If in doubt,
117
+ # just use a Proc.
118
+ def add(*args)
119
+ opts = args.last.is_a?(Hash) ? args.pop : {}
120
+ args = args.compact.flatten.map {|a| a.to_s }
121
+ raise ArgumentError, "Name parameter required" if args.blank?
122
+ opts[:name] ||= args.first
123
+ opts[:types] ||= ALL_TYPES
124
+ opts[:types] = [opts[:types]].flatten
125
+ opts[:condition] ||= '='
126
+ opts[:substitutions] ||= '?'
127
+ opts[:keep_arrays] ||= false
128
+ opts[:formatter] ||= Proc.new {|param| param}
129
+ if opts[:formatter].is_a?(String)
130
+ formatter = opts[:formatter]
131
+ opts[:formatter] = Proc.new {|param| eval formatter}
132
+ end
133
+ opts[:aliases] ||= [args - [opts[:name]]].flatten
134
+ @@wheres ||= {}
135
+ if @@wheres.has_key?(opts[:name])
136
+ raise ArgumentError, "\"#{opts[:name]}\" is not available for use as a where name."
137
+ end
138
+ @@wheres[opts[:name]] = opts
139
+ opts[:aliases].each do |a|
140
+ if @@wheres.has_key?(a)
141
+ opts[:aliases].delete(a)
142
+ else
143
+ @@wheres[a] = opts[:name]
144
+ end
145
+ end
146
+ end
147
+
148
+ # Returns the complete array of Wheres
149
+ def all
150
+ @@wheres
151
+ end
152
+
153
+ # Get the where matching a method or condition.
154
+ def get(method_id_or_condition)
155
+ return nil unless where_key = @@wheres.keys.
156
+ sort {|a,b| b.length <=> a.length}.
157
+ detect {|n| method_id_or_condition.to_s.match(/#{n}=?$/)}
158
+ where = @@wheres[where_key]
159
+ where = @@wheres[where] if where.is_a?(String)
160
+ where
161
+ end
162
+
163
+ # Set the wheres to their default values, removing any customized settings.
164
+ def initialize_wheres
165
+ @@wheres = {}
166
+ DEFAULT_WHERES.each do |where|
167
+ add(*where)
168
+ end
169
+ end
170
+ end
171
+ end
172
+
173
+ Where.initialize_wheres
174
+ end
@@ -0,0 +1,31 @@
1
+ module MetaSearch
2
+ NUMBERS = [:integer, :float, :decimal]
3
+ STRINGS = [:string, :text, :binary]
4
+ DATES = [:date]
5
+ TIMES = [:datetime, :timestamp, :time]
6
+ BOOLEANS = [:boolean]
7
+ ALL_TYPES = NUMBERS + STRINGS + DATES + TIMES + BOOLEANS
8
+
9
+ DEFAULT_WHERES = [
10
+ ['equals', 'eq'],
11
+ ['does_not_equal', 'ne', {:types => ALL_TYPES, :condition => '!='}],
12
+ ['contains', 'like', {:types => STRINGS, :condition => 'LIKE', :formatter => '"%#{param}%"'}],
13
+ ['does_not_contain', 'nlike', {:types => STRINGS, :condition => 'NOT LIKE', :formatter => '"%#{param}%"'}],
14
+ ['starts_with', 'sw', {:types => STRINGS, :condition => 'LIKE', :formatter => '"#{param}%"'}],
15
+ ['does_not_start_with', 'dnsw', {:types => STRINGS, :condition => 'NOT LIKE', :formatter => '"%#{param}%"'}],
16
+ ['ends_with', 'ew', {:types => STRINGS, :condition => 'LIKE', :formatter => '"%#{param}"'}],
17
+ ['does_not_end_with', 'dnew', {:types => STRINGS, :condition => 'NOT LIKE', :formatter => '"%#{param}"'}],
18
+ ['greater_than', 'gt', {:types => (NUMBERS + DATES + TIMES), :condition => '>'}],
19
+ ['less_than', 'lt', {:types => (NUMBERS + DATES + TIMES), :condition => '<'}],
20
+ ['greater_than_or_equal_to', 'gte', {:types => (NUMBERS + DATES + TIMES), :condition => '>='}],
21
+ ['less_than_or_equal_to', 'lte', {:types => (NUMBERS + DATES + TIMES), :condition => '<='}],
22
+ ['in', {:types => ALL_TYPES, :condition => 'IN', :substitutions => '(?)', :keep_arrays => true}],
23
+ ['not_in', 'ni', {:types => ALL_TYPES, :condition => 'NOT IN', :substitutions => '(?)', :keep_arrays => true}]
24
+ ]
25
+
26
+ RELATION_METHODS = [:joins, :includes, :all, :count, :to_sql, :paginate, :find_each, :first, :last, :each]
27
+ end
28
+
29
+ if defined?(::Rails::Railtie)
30
+ require 'meta_search/railtie'
31
+ end
@@ -0,0 +1,83 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{meta_search}
8
+ s.version = "0.3.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Ernie Miller"]
12
+ s.date = %q{2010-03-16}
13
+ s.description = %q{Adds a search method to your ActiveRecord models which returns an object to be used in form_for while constructing a search. Works with Rails 3 only.}
14
+ s.email = %q{ernie@metautonomo.us}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "lib/meta_search.rb",
27
+ "lib/meta_search/builder.rb",
28
+ "lib/meta_search/exceptions.rb",
29
+ "lib/meta_search/helpers/action_view.rb",
30
+ "lib/meta_search/model_compatibility.rb",
31
+ "lib/meta_search/railtie.rb",
32
+ "lib/meta_search/searches/active_record.rb",
33
+ "lib/meta_search/searches/base.rb",
34
+ "lib/meta_search/utility.rb",
35
+ "lib/meta_search/where.rb",
36
+ "meta_search.gemspec",
37
+ "test/fixtures/companies.yml",
38
+ "test/fixtures/company.rb",
39
+ "test/fixtures/data_type.rb",
40
+ "test/fixtures/data_types.yml",
41
+ "test/fixtures/developer.rb",
42
+ "test/fixtures/developers.yml",
43
+ "test/fixtures/developers_projects.yml",
44
+ "test/fixtures/note.rb",
45
+ "test/fixtures/notes.yml",
46
+ "test/fixtures/project.rb",
47
+ "test/fixtures/projects.yml",
48
+ "test/fixtures/schema.rb",
49
+ "test/helper.rb",
50
+ "test/test_search.rb",
51
+ "test/test_view_helpers.rb"
52
+ ]
53
+ s.homepage = %q{http://metautonomo.us}
54
+ s.rdoc_options = ["--charset=UTF-8"]
55
+ s.require_paths = ["lib"]
56
+ s.rubygems_version = %q{1.3.6}
57
+ s.summary = %q{ActiveRecord 3 object-based searching.}
58
+ s.test_files = [
59
+ "test/fixtures/company.rb",
60
+ "test/fixtures/data_type.rb",
61
+ "test/fixtures/developer.rb",
62
+ "test/fixtures/note.rb",
63
+ "test/fixtures/project.rb",
64
+ "test/fixtures/schema.rb",
65
+ "test/helper.rb",
66
+ "test/test_search.rb",
67
+ "test/test_view_helpers.rb"
68
+ ]
69
+
70
+ if s.respond_to? :specification_version then
71
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
72
+ s.specification_version = 3
73
+
74
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
75
+ s.add_development_dependency(%q<activerecord>, [">= 3.0.0.beta"])
76
+ else
77
+ s.add_dependency(%q<activerecord>, [">= 3.0.0.beta"])
78
+ end
79
+ else
80
+ s.add_dependency(%q<activerecord>, [">= 3.0.0.beta"])
81
+ end
82
+ end
83
+
@@ -0,0 +1,17 @@
1
+ initech:
2
+ name : Initech
3
+ id : 1
4
+ created_at: 1999-02-19 08:00
5
+ updated_at: 1999-02-19 08:00
6
+
7
+ aos:
8
+ name: Advanced Optical Solutions
9
+ id : 2
10
+ created_at: 2004-02-01 08:00
11
+ updated_at: 2004-02-01 08:00
12
+
13
+ mission_data:
14
+ name: Mission Data
15
+ id : 3
16
+ created_at: 1996-09-21 08:00
17
+ updated_at: 1996-09-21 08:00
@@ -0,0 +1,9 @@
1
+ class Company < ActiveRecord::Base
2
+ has_many :developers
3
+ has_many :developer_notes, :through => :developers, :source => :notes
4
+ has_many :slackers, :class_name => "Developer", :conditions => {:slacker => true}
5
+ has_many :notes, :as => :notable
6
+ has_many :data_types
7
+ metasearch_exclude_attr :updated_at
8
+ metasearch_exclude_assoc :notes
9
+ end
@@ -0,0 +1,4 @@
1
+ class DataType < ActiveRecord::Base
2
+ belongs_to :company
3
+ metasearch_exclude_attr :str
4
+ end
@@ -0,0 +1,15 @@
1
+ <% 1.upto(9) do |n| %>
2
+ dt_<%= n %>:
3
+ company_id: <%= n % 3 + 1 %>
4
+ str : This string has <%= n %> exclamation points<%= '!' * n %>
5
+ txt : <%= 'This is some text that may or may not repeat based on the value of n.' * n %>
6
+ int : <%= n ** 3 %>
7
+ flt : <%= n.to_f / 2.0 %>
8
+ dec : <%= n.to_f ** (n + 0.1) %>
9
+ dtm : <%= Time.utc(2009, 12, 24) + 86400 * n %>
10
+ tms : <%= Time.utc(2009, 12, 24) + 86400 * n %>
11
+ tim : <%= Time.utc(2000, 01, 01, n+8, n) %>
12
+ dat : <%= (Date.new(2009, 12, 24) + n).strftime("%Y-%m-%d") %>
13
+ bin : <%= "BLOB#{n}" * n %>
14
+ bln : <%= n % 2 > 0 ? true : false %>
15
+ <% end %>
@@ -0,0 +1,5 @@
1
+ class Developer < ActiveRecord::Base
2
+ belongs_to :company
3
+ has_and_belongs_to_many :projects
4
+ has_many :notes, :as => :notable
5
+ end
@@ -0,0 +1,55 @@
1
+ peter:
2
+ id : 1
3
+ company_id: 1
4
+ name : Peter Gibbons
5
+ salary : 100000
6
+ slacker : true
7
+
8
+ michael:
9
+ id : 2
10
+ company_id: 1
11
+ name : Michael Bolton
12
+ salary : 70000
13
+ slacker : false
14
+
15
+ samir:
16
+ id : 3
17
+ company_id: 1
18
+ name : Samir Nagheenanajar
19
+ salary : 65000
20
+ slacker : false
21
+
22
+ herb:
23
+ id : 4
24
+ company_id: 2
25
+ name : Herb Myers
26
+ salary : 50000
27
+ slacker : false
28
+
29
+ dude:
30
+ id : 5
31
+ company_id: 2
32
+ name : Some Dude
33
+ salary : 84000
34
+ slacker : true
35
+
36
+ ernie:
37
+ id : 6
38
+ company_id: 3
39
+ name : Ernie Miller
40
+ salary : 45000
41
+ slacker : true
42
+
43
+ someone:
44
+ id : 7
45
+ company_id: 3
46
+ name : Someone Else
47
+ salary : 70000
48
+ slacker : true
49
+
50
+ another:
51
+ id : 8
52
+ company_id: 3
53
+ name : Another Guy
54
+ salary : 80000
55
+ slacker : false
@@ -0,0 +1,25 @@
1
+ <% 1.upto(3) do |d| %>
2
+ y2k_<%= d %>:
3
+ developer_id: <%= d %>
4
+ project_id : 1
5
+ <% end %>
6
+
7
+ virus:
8
+ developer_id: 2
9
+ project_id : 2
10
+
11
+ <% 1.upto(8) do |d| %>
12
+ awesome_<%= d %>:
13
+ developer_id: <%= d %>
14
+ project_id : 3
15
+ <% end %>
16
+
17
+ metasearch:
18
+ developer_id: 6
19
+ project_id : 4
20
+
21
+ <% 4.upto(8) do |d| %>
22
+ another_<%= d %>:
23
+ developer_id: <%= d %>
24
+ project_id : 5
25
+ <% end %>
@@ -0,0 +1,3 @@
1
+ class Note < ActiveRecord::Base
2
+ belongs_to :notable, :polymorphic => true
3
+ end
@@ -0,0 +1,79 @@
1
+ peter:
2
+ notable_type: Developer
3
+ notable_id : 1
4
+ note : A straight shooter with upper management written all over him.
5
+
6
+ michael:
7
+ notable_type: Developer
8
+ notable_id : 2
9
+ note : Doesn't like the singer of the same name. The nerve!
10
+
11
+ samir:
12
+ notable_type: Developer
13
+ notable_id : 3
14
+ note : Naga.... Naga..... Not gonna work here anymore anyway.
15
+
16
+ herb:
17
+ notable_type: Developer
18
+ notable_id : 4
19
+ note : Will show you what he's doing.
20
+
21
+ dude:
22
+ notable_type: Developer
23
+ notable_id : 5
24
+ note : Nothing of note.
25
+
26
+ ernie:
27
+ notable_type: Developer
28
+ notable_id : 6
29
+ note : Complete slacker. Should probably be fired.
30
+
31
+ someone:
32
+ notable_type: Developer
33
+ notable_id : 7
34
+ note : Just another developer.
35
+
36
+ another:
37
+ notable_type: Developer
38
+ notable_id : 8
39
+ note : Placing a note in this guy's file for insubordination.
40
+
41
+ initech:
42
+ notable_type: Company
43
+ notable_id : 1
44
+ note : Innovation + Technology!
45
+
46
+ aos:
47
+ notable_type: Company
48
+ notable_id : 2
49
+ note : Advanced solutions of an optical nature.
50
+
51
+ mission_data:
52
+ notable_type: Company
53
+ notable_id : 3
54
+ note : Best design + development shop in the 'ville.
55
+
56
+ y2k:
57
+ notable_type: Project
58
+ notable_id : 1
59
+ note : It may have already passed but that's no excuse to be unprepared!
60
+
61
+ virus:
62
+ notable_type: Project
63
+ notable_id : 2
64
+ note : It could bring the company to its knees.
65
+
66
+ awesome:
67
+ notable_type: Project
68
+ notable_id : 3
69
+ note : This note is AWESOME!!!
70
+
71
+ metasearch:
72
+ notable_type: Project
73
+ notable_id : 4
74
+ note : A complete waste of the developer's time.
75
+
76
+ another:
77
+ notable_type: Project
78
+ notable_id : 5
79
+ note : This is another project note.
@@ -0,0 +1,4 @@
1
+ class Project < ActiveRecord::Base
2
+ has_and_belongs_to_many :developers
3
+ has_many :notes, :as => :notable
4
+ end
@@ -0,0 +1,24 @@
1
+ y2k:
2
+ estimated_hours: 1000
3
+ name : Y2K Software Updates
4
+ id : 1
5
+
6
+ virus:
7
+ estimated_hours: 80
8
+ name : Virus
9
+ id : 2
10
+
11
+ awesome:
12
+ estimated_hours: 100
13
+ name : Do something awesome
14
+ id : 3
15
+
16
+ metasearch:
17
+ estimated_hours: 100
18
+ name : MetaSearch Development
19
+ id : 4
20
+
21
+ another:
22
+ estimated_hours: 120
23
+ name : Another Project
24
+ id : 5