meta_search 0.3.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.
@@ -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