acts_as_word_cloud 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,223 +5,143 @@ module ActsAsWordCloud
5
5
  end
6
6
 
7
7
  module ClassMethods
8
- # args:
9
- # using, takes an array of Symbols that refer to the methods we'll use on the model calling word_cloud
10
- # excluded models, takes in Class names of models we want to ignore that are associated to model calling word_cloud
11
- # skipped attributes, takes in Symbols referring to attributes that won't be pulled from model calling word_cloud
12
- # depth level, takes in an Integer referring to how deep of a recursive search we'll make
13
- # no_mixin_fields, is a default pre-set array of methods to use on models that do not include the mixin
14
- #
15
- def acts_as_word_cloud(args)
8
+ # Sets up the word_cloud method and takes arguments that control what it returns
9
+ #
10
+ # @param [Hash] args
11
+ # @param args [Array] :included_methods An array of method symbols used to create this model's word cloud
12
+ # @param args [Array] :excluded_methods An array of method symbols used to remove data from the word cloud. This should be used to remove database fields from the word cloud.
13
+ # @param args [Array] :excluded_models An array of models whose data should not be included in the word cloud
14
+ # @param args [Integer] :depth_level How many levels of associations to include
15
+ # @param args [Symbol] :object_name_method How to name the object when included in the word cloud as an association
16
+ def acts_as_word_cloud(args = {})
16
17
 
17
18
  # getter/setter methods for the module
18
- mattr_accessor :word_cloud_using unless respond_to? :word_cloud_using
19
- mattr_accessor :word_cloud_excluded unless respond_to? :word_cloud_excluded
20
- mattr_accessor :word_cloud_skipped unless respond_to? :word_cloud_skipped
21
- mattr_accessor :word_cloud_depth unless respond_to? :word_cloud_depth
22
- mattr_accessor :word_cloud_no_mixin_fields unless respond_to? :word_cloud_no_mixin_fields
23
-
24
- # set empty arrays to allow |= on values
25
- unless self.word_cloud_using.is_a?(Array)
26
- self.word_cloud_using = []
27
- end
28
- unless self.word_cloud_excluded.is_a?(Array)
29
- self.word_cloud_excluded = []
30
- end
31
- unless self.word_cloud_skipped.is_a?(Array)
32
- self.word_cloud_skipped = []
33
- end
34
- unless self.word_cloud_no_mixin_fields.is_a?(Array)
35
- self.word_cloud_no_mixin_fields = []
36
- end
37
-
38
- # default values if none set in the mixin call on the model
39
- self.word_cloud_using |= args[:methods_to_use].present? ? args[:methods_to_use] : []
40
- self.word_cloud_excluded |= args[:excluded_models].present? ? args[:excluded_models] : []
41
- self.word_cloud_skipped |= args[:skipped_attributes].present? ? args[:skipped_attributes] : []
42
- self.word_cloud_depth = args[:depth].present? ? args[:depth] : ActsAsWordCloud.config.min_depth
43
- self.word_cloud_no_mixin_fields = [:name, :title, :label] #ActsAsWordCloud.config.no_mixin_fields
19
+ mattr_accessor :word_cloud_attributes unless respond_to? :word_cloud_attributes
20
+ allowed_options = [:included_methods, :excluded_methods, :excluded_models, :depth, :object_name_methods]
21
+
22
+ # set defaults
23
+ args[:included_methods] ||= []
24
+ args[:excluded_methods] ||= []
25
+ args[:excluded_models] ||= []
26
+ args[:depth] ||= ::ActsAsWordCloud.config.default_search_depth
27
+ # note that the user passes in object_name_method and it is turned into the array object_name_methods
28
+ args[:object_name_methods] = args[:object_name_method] ? [args[:object_name_method]] : ::ActsAsWordCloud.config.object_name_methods
29
+
30
+ self.word_cloud_attributes = args.keep_if { |key| allowed_options.include?(key) }
44
31
 
45
32
  include ActsAsWordCloud::WordCloud::InstanceMethods
46
33
  end
47
34
  end
48
35
 
49
36
  module InstanceMethods
50
-
51
- # collects associations on a model as long as they're not nil
52
- #
53
- # @param [Symbol] the association type to look in
54
- # @returns [Array <Symbol>] association names under the passed in type
55
- #
56
- def word_cloud_get_associated(type)
57
- self.class.reflect_on_all_associations(type).select {|r| self.send(r.name).present? }.collect { |r| r.name }
58
- end
59
-
60
- # goes through array of objects or arrays (in the case of has_many association)
61
- #
62
- # @param [Symbol] the association type to fetch objects from
63
- # @returns [Array <Object>] that under association passed in
64
- #
65
- def word_cloud_associated_objects(type)
66
- objects = []
67
- associated = word_cloud_get_associated(type)
68
- associated.each do |o|
69
- if o.class == Array
70
- nested_array = self.send(o)
71
- nested_array.each do |a|
72
- objects << a
73
- end
74
- else
75
- objects << self.send(o)
76
- end
77
- end
78
- return objects
79
- end
80
-
81
- # removes objects that are in the list of objects to exclude
82
- #
83
- # @returns [Array <Object>] that are not in the excluded list
84
- #
85
- def word_cloud_exclude_words(objects)
86
- if objects.nil?
87
- return []
88
- else
89
- result = objects.flatten.reject { |x| self.word_cloud_excluded.include?(x.class) }
90
- return result.present? ? result : []
91
- end
92
- end
93
-
94
- # removes objects that include word_cloud mixin
95
- #
96
- # @returns [Array <Object>] that don't include the mixin
97
- #
98
- def word_cloud_no_mixin(objects)
99
- if objects.nil?
100
- return []
37
+ # Uses recursive word cloud to find text attributes, associations and included methods on the model
38
+ #
39
+ # @param [Symbol] return_type Whether to return an array or string, defaulting to :string
40
+ # @return [Array<String> or String] All processed values
41
+ def word_cloud(return_type = :string)
42
+ output = recursive_word_cloud(self.word_cloud_attributes[:depth]).uniq.flatten
43
+ if return_type == :string
44
+ return output.join(' ')
101
45
  else
102
- result = objects.flatten.reject { |n| n.respond_to?(:word_cloud) }
103
- result.present? ? result : []
46
+ return output
104
47
  end
105
48
  end
106
-
107
- # goes through each object passed in trying the included methods for each of those objects
108
- # keeps the first one to work and returns the value of that method called on the object
109
- #
110
- # @param [Array <Objects>] to look through for values
111
- # @returns [Array <String>] values returned by method
49
+
50
+ protected
51
+
52
+ # Finds all text attributes, associated objects, and included methods down to a specified depth
112
53
  #
113
- def word_cloud_process_words(objects)
54
+ # @param [Integer] depth How many layers of associations to search
55
+ # @return [Array] The word cloud for the specified object and depth
56
+ def recursive_word_cloud(depth)
57
+ # prepare an array of strings to be used as an output
114
58
  output = []
59
+
60
+ # list of database attributes and selected methods minus a list of excluded methods
61
+ output |= word_cloud_get_valid_strings
62
+
63
+ # array of objects for every association minus a list of excluded objects
64
+ objects = word_cloud_associated_objects
115
65
  objects.each do |obj|
116
- obj_calls = obj.word_cloud_using.select { |f| obj.respond_to?(f) }
117
- if obj_calls.first.present?
118
- output << obj.send(obj_calls.first)
119
- end
120
- end
121
- return output
122
- end
123
-
124
- # goes through each of the default fields to call on models that don't include mixin
125
- # and attempts to return the value of the first one to work, if none do return model class name
126
- #
127
- # @returns [String] value for object passed in
128
- #
129
- def word_cloud_apply_fields_to(no_mixin_model)
130
- # fields that should be tried on models that don't have the mixin, set with other attributes
131
- fields = self.word_cloud_no_mixin_fields
132
- fields.each do |f|
133
- if no_mixin_model.respond_to?(f)
134
- return no_mixin_model.send(f)
66
+ if obj.respond_to?(:recursive_word_cloud) && depth > 1
67
+ # if the object has a word cloud mixin and we can recurse
68
+ output |= obj.recursive_word_cloud(depth - 1)
69
+ else
70
+ # otherwise get the default name for the object
71
+ output |= [self.word_cloud_object_name(obj)]
135
72
  end
136
73
  end
137
- return no_mixin_model.class.name
138
- end
139
-
140
- # goes through models that don't include mixin trying to find a relevant value for each
141
- #
142
- # @returns [Array <String>] of values for objects passed in
143
- #
144
- def word_cloud_find_field(no_mixin)
145
- output = []
146
- flag = 0
147
- no_mixin.each do |n|
148
- output << word_cloud_apply_fields_to(n)
149
- end
150
74
  return output
151
- end
152
-
153
-
154
- # returns values from methods specified in using option
155
- # ignores string attributes that are listed in the skipped option
75
+ end
76
+
77
+ # returns values from methods specified in included_methods option
78
+ # ignores string attributes that are listed in the excluded_methods option
156
79
  #
157
- # @returns [Array <String>]
80
+ # Note that his uses content_columns to read the database data.
81
+ # At some point, we may want to increase the types that are allowed or use a blacklist instead of a whitelist
158
82
  #
83
+ # @return [Array <String>] The database fields and included methods with excluded methods removed
159
84
  def word_cloud_get_valid_strings
160
- output = []
161
- ignore = []
162
-
163
- self.word_cloud_using.each do |m|
164
- output << self.send(m)
165
- end
166
-
167
- self.word_cloud_skipped.each do |s|
168
- ignore << self.send(s)
169
- end
85
+ # database fields
86
+ string_attributes = self.class.content_columns.collect { |c| c.name.to_sym if c.type == :string || c.type == :text }.compact
87
+ # included methods
88
+ methods = self.word_cloud_attributes[:included_methods] | string_attributes
89
+ # excluded methods
90
+ methods -= self.word_cloud_attributes[:excluded_methods]
170
91
 
171
- # current model's string attributes
172
- output += self.attributes.select {|k,v| v.class == String}.values
173
- output -= ignore
174
-
175
- return output
92
+ return methods.map { |m| self.send(m) }
176
93
  end
177
94
 
178
- # finds string attributes on model
179
- # processes associations on model, either fetching word_cloud results if they include mixin or default information if they don't
180
- # if depth is said to something higher than one the word_cloud results on each associated model then makes more recursive calls (BFS)
181
- #
182
- # @params Integer, recursive depth for method; Symbol for the type of result (array or string)
183
- # @returns [Array <String>] all processed values
95
+ # Gets a list of objects associated to the calling object
96
+ # Uses rails reflect_on_all_associations methods. Only pulls from belongs_to, has_one, and has_many for now.
97
+ # Each association should return nil, a single object, or an array of objects
184
98
  #
185
- def word_cloud( depth = self.word_cloud_depth, type = :string )
186
-
187
- output = []
99
+ # @return [Array] List of models associated to the calling object
100
+ def word_cloud_associated_objects
188
101
  objects = []
189
- no_mixin = []
190
-
191
- # string attributes in current model
192
- output = word_cloud_get_valid_strings
102
+ # get associations
103
+ [:belongs_to, :has_one, :has_many].each do |association_type|
104
+ word_cloud_association_names_by_type(association_type).each do |association|
105
+ objects << self.send(association)
106
+ end
107
+ end
108
+ objects.flatten!
109
+ # remove excluded associations
110
+ objects = objects.delete_if { |o| self.word_cloud_attributes[:excluded_models].include?(o.class) }
111
+ return objects
112
+ end
113
+
114
+ # @return [Array <Symbol>] Collect the names of associations of a specific typeI
115
+ def word_cloud_association_names_by_type(type)
116
+ self.class.reflect_on_all_associations(type).collect(&:name)
117
+ end
193
118
 
194
- # objects on all associations collected
195
- objects += self.word_cloud_associated_objects(:belongs_to)
196
- objects += self.word_cloud_associated_objects(:has_one)
197
- objects += self.word_cloud_associated_objects(:has_many)
198
-
199
- # remove objects that have been explicitly excluded on current model
200
- objects = word_cloud_exclude_words(objects)
201
- # find associated models that do not include mixin
202
- no_mixin = word_cloud_no_mixin(objects)
203
- objects -= no_mixin
204
-
205
- # process object using set methods for each model with mixin, or try preset general methods for models without
206
- output += self.word_cloud_process_words(objects) + word_cloud_find_field(no_mixin)
207
-
208
- # recursive step, if depth > 1, calls word cloud on objects array (which already excludes objects that don't have the mixin)
209
- if depth < 1
210
- return "depth for word cloud can't be less than 0"
211
- elsif depth == 1
212
- # recursive steps got a bit more complicated with :array/:string addition, that last line gets rid of duplicate results in the 'long' strings being returned
213
- return ( type == :array ? output.uniq : output.uniq.join(' ') )
119
+ # Name an object that is included in the word cloud
120
+ #
121
+ # When the depth level is low or an included gem does not use the word cloud mixin
122
+ # use a special rule to determine the name of the object. The word cloud attribute
123
+ # object_name_methods is used for objects that use the word cloud. Otherwise, the
124
+ # default config option is used. If none of the methods are found on the object, an
125
+ # empty string is returned.
126
+ #
127
+ # @param [ActiveRecord::Base] object The object to name
128
+ # @param [String] The name of the object or a blank string
129
+ def word_cloud_object_name(object)
130
+ output = ""
131
+ method_list = []
132
+ if object.respond_to?(:word_cloud_attributes)
133
+ method_list = object.word_cloud_attributes[:object_name_methods]
214
134
  else
215
- if type == :array
216
- output |= objects.collect { |o| o.word_cloud(depth-1, :array) }
217
- return output.flatten.uniq
218
- else
219
- output |= objects.collect { |o| o.word_cloud(depth-1) }
220
- return output.flatten.join(' ').split(' ').uniq.join(' ')
135
+ method_list = ::ActsAsWordCloud.config.object_name_methods
136
+ end
137
+ method_list.each do |method|
138
+ if object.respond_to?(method)
139
+ output = object.send(method)
140
+ break
221
141
  end
222
142
  end
223
- end
143
+ return output
144
+ end
224
145
  end
225
146
  end
226
147
  end
227
-
@@ -1,7 +1,12 @@
1
1
  ActsAsWordCloud.configure do |config|
2
- #the default setting for smallest recursive step for calling wordcloud on models
3
- config.min_depth = 1
2
+ # The default recursion depth when looking at data in associations.
3
+ # A min_depth of 1 will look at all fields on the current model and the name of each association.
4
+ # A min_depth of 2 will look at all fields on the current model, all fields on the associations, and all names of the association's associations
5
+ # config.default_search_depth = 1
4
6
 
5
- #methods whose values we'll attempt to get for models that don't include the mixin
6
- config.no_mixin_fields = [:name, :title, :label]
7
+ # This option is useful when associations do not use the acts_as_word_cloud mixin.
8
+ # When word_cloud is called, the system tries to guess a reasonable name for the associated models.
9
+ # It calls the methods in order meaning it will look for name first.
10
+ # Make sure that the final method in this list is on every model
11
+ # config.default_object_names = [:name, :title, :label, :to_s]
7
12
  end
@@ -1,109 +1,172 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
3
  describe "ActsAsWordCloud" do
4
- before(:all) do
5
- @lone_article = Article.make
6
- @publisher = Publisher.make(:name => "publisher", :location => "location")
7
- @site = Site.make( :name => "site name", :domain => "uff.us", :genre => "site genre", :publisher => @publisher)
8
- @author = Author.make(:name => "author", :genre => "author genre", :publisher => @publisher, :site => @site)
9
- @article = Article.make(:title => "article title", :genre => "genre", :content => "article text", :author => @author, :publisher => @publisher, :site => @site)
10
- @reader = Reader.make(:username => "reader", :site => @site)
11
- @following = Following.make(:article => @article, :reader => @reader, :special_name => "following1")
4
+ before(:each) do
5
+ @article = Article.new
12
6
  end
13
-
7
+
14
8
  describe "word_cloud" do
15
- describe "class method use_word_cloud" do
16
- it "should contain set or default values in attributes" do
17
- @article.word_cloud_using.should == [:title]
18
- @article.word_cloud_excluded.should == []
19
- @article.word_cloud_depth.should == 1
20
- @article.word_cloud_no_mixin_fields.should == [:name, :title, :label]
21
- end
22
- end
23
-
24
- it "should return the right association models" do
25
- @lone_article.word_cloud.should == [@lone_article.title, @lone_article.genre, @lone_article.content, @lone_article.author.name, @lone_article.publisher.name, @lone_article.site.domain ].join(' ')
26
- @article.word_cloud.should == [@article.title, @article.genre, @article.content, @article.author.name, @article.publisher.name, @article.site.domain, @article.readers.first.username, "Following" ].join(' ')
27
-
28
- @publisher2 = Publisher.make(:name => "publisher2", :location => "location2")
29
- @site2 = Site.make( :name => "site2 name", :domain => "uff.us2", :genre => "site2 genre", :publisher => @publisher2)
30
- @author2 = Author.make(:name => "author2", :genre => "author2 genre", :publisher => @publisher2, :site => @site2)
31
- @article2 = Article.make(:title => "article2 title", :genre => "genre2", :content => "article2 text", :author => @author2, :publisher => @publisher2, :site => @site2)
32
- @following2 = Following.make(:article => @article2, :reader => @reader , :special_name => "following2")
33
-
34
- @reader.word_cloud(2, :array).should == [@reader.username, @reader.site.domain, @article.title, @article2.title, "Following", @site.name, @site.genre, @site.publisher.name, @site.authors.first.name, @article.genre, @article.content, @article2.genre, @article2.content, @article2.author.name, @article2.publisher.name, @article2.site.domain ]
9
+ before(:each) do
10
+ @article.stub(:recursive_word_cloud).and_return([])
11
+ end
12
+
13
+ it "should call recursive_word_cloud" do
14
+ @article.should_receive(:recursive_word_cloud).and_return([])
15
+ @article.word_cloud
16
+ end
17
+
18
+ it "should return a string by default" do
19
+ @article.word_cloud.should be_a_kind_of String
20
+ end
21
+
22
+ it "should return a string if :string is passed as an argument" do
23
+ @article.word_cloud(:string).should be_a_kind_of String
24
+ end
25
+
26
+ it "should return an array if :array is passed as an argument" do
27
+ @article.word_cloud(:array).should be_a_kind_of Array
35
28
  end
36
29
  end
37
-
38
- describe "instance methods:" do
39
- describe "word_cloud_associated_objects" do
40
- it "should return correct models when called with belongs_to" do
41
- @article.word_cloud_associated_objects(:belongs_to).should == [@article.author, @article.publisher, @article.site]
42
- end
43
-
44
- it "should return correct models when called with has_many" do
45
- @article.word_cloud_associated_objects(:has_many).should == [@article.followings, @article.readers]
46
- end
30
+
31
+ describe "recursive_word_cloud" do
32
+ before(:each) do
33
+ @article.stub(:word_cloud_get_valid_strings).and_return([])
34
+ @article.stub(:word_cloud_associated_objects).and_return([])
35
+ end
36
+ it "should call word_cloud_get_valid_strings" do
37
+ @article.should_receive(:word_cloud_get_valid_strings).and_return([])
38
+ @article.send(:recursive_word_cloud, 1)
39
+ end
40
+
41
+ it "should call word_cloud_associated_objects" do
42
+ @article.should_receive(:word_cloud_associated_objects).and_return([])
43
+ @article.send(:recursive_word_cloud, 1)
44
+ end
45
+
46
+ it "should call rescursive_word_cloud on an associated object if the depth is more than 1" do
47
+ object_with_word_cloud = mock(Author)
48
+ object_with_word_cloud.should_receive(:recursive_word_cloud).with(1).and_return([])
49
+
50
+ @article.stub(:word_cloud_associated_objects).and_return([object_with_word_cloud])
51
+ @article.send(:recursive_word_cloud, 2)
52
+ end
53
+
54
+ it "should call word_cloud_object_name on an associated object if the depth is 1 or less" do
55
+ object_with_word_cloud = mock(Author)
56
+ object_with_word_cloud.stub(:recursive_word_cloud).and_return([])
57
+ @article.stub(:word_cloud_associated_objects).and_return([object_with_word_cloud])
58
+
59
+ @article.should_receive(:word_cloud_object_name).with(object_with_word_cloud)
60
+ @article.send(:recursive_word_cloud, 1)
61
+ end
62
+
63
+ it "should call word_cloud_object_name on an associated object if the object does not have the word cloud mixin" do
64
+ object_without_word_cloud = mock(Author)
65
+ @article.stub(:word_cloud_associated_objects).and_return([object_without_word_cloud])
66
+
67
+ @article.should_receive(:word_cloud_object_name).with(object_without_word_cloud)
68
+ @article.send(:recursive_word_cloud, 2)
69
+ end
70
+
71
+ it "should return an array of strings" do
72
+ @article.stub(:word_cloud_get_valid_strings).and_return(["hello"])
73
+ results = @article.send(:recursive_word_cloud, 1)
74
+ results.should be_a_kind_of Array
75
+ results.each { |r| r.should be_a_kind_of String }
76
+ end
77
+ end
78
+
79
+ describe "word_cloud_get_valid_strings" do
80
+ before(:each) do
81
+ @article.word_cloud_attributes[:included_methods] = [:sample_method]
82
+ @article.word_cloud_attributes[:excluded_methods] = [:sample_method]
83
+ end
84
+
85
+ it "should read the database structure and return text and string fields only" do
86
+ @article.should_receive(:title)
87
+ @article.should_receive(:content)
88
+ @article.should_not_receive(:created_at)
89
+ @article.send(:word_cloud_get_valid_strings)
47
90
  end
48
-
49
- describe "word_cloud_get_associated" do
50
- it "should return collectoin of non-nil associated models" do
51
- @article.word_cloud_get_associated(:belongs_to).should == [:author, :publisher, :site]
52
- @lone_article.word_cloud_get_associated(:has_many).should == []
53
- @article.word_cloud_get_associated(:has_many).should == [:followings, :readers]
54
- end
55
- end
56
-
57
- describe "word_cloud_exclude_words" do
58
- it "should not keep excluded models" do
59
- @article.stub!(:word_cloud_excluded).and_return([Publisher])
60
- objects = @article.word_cloud_associated_objects(:belongs_to)
61
- @article.word_cloud_exclude_words(objects).should == [@article.author, @article.site]
62
- end
63
- end
64
-
65
- describe "word_cloud_no_mixin" do
66
- it "should only have models that don't have the mixin" do
67
- objects = @article.word_cloud_associated_objects(:has_many)
68
- @article.word_cloud_no_mixin(objects).should == @article.followings
69
- end
70
- end
71
-
72
- describe "word_cloud_process_words" do
73
- it "should return result of first included method to work on objects with mixin" do
74
- objects = []
75
- objects += @article.word_cloud_associated_objects(:belongs_to)
76
- objects += @article.word_cloud_associated_objects(:has_many)
77
- objects = @article.word_cloud_exclude_words(objects)
78
- no_mixin = @article.word_cloud_no_mixin(objects)
79
- objects -= no_mixin
80
- @article.word_cloud_process_words(objects).should == [@article.author.name, @article.publisher.name, @article.site.domain, @article.readers.first.username]
81
- end
82
- end
83
-
84
- describe "word_cloud_apply_fields_to" do
85
- it "return the value found in the first method to work on object passed in, if there's none return class name" do
86
- @article.word_cloud_apply_fields_to(@article.followings.first).should == "Following"
87
- @article.stub!(:word_cloud_no_mixin_fields).and_return([:special_name])
88
- @article.word_cloud_apply_fields_to(@article.followings.first).should == @article.followings.first.special_name
89
- end
90
- end
91
-
92
- describe "word_cloud_find_field" do
93
- it "should return result of first included method to work on objects with mixin or their class names" do
94
- @article.word_cloud_find_field(@article.followings).should == ["Following"]
95
- end
96
- end
97
-
98
- describe "word_cloud_get_valid_strings" do
99
- it "should return used methods on model and string attributes not in skipped list" do
100
- @article.stub!(:word_cloud_skipped).and_return([:title, :content])
101
- @article.word_cloud_get_valid_strings.should_not include "article title"
102
- @article.word_cloud_get_valid_strings.should_not include "article text"
103
- @article.word_cloud_get_valid_strings.should include "genre"
104
- end
105
- end
106
-
91
+
92
+ it "should add included methods to the output and call them" do
93
+ @article.word_cloud_attributes[:excluded_methods] = []
94
+ @article.should_receive(:sample_method)
95
+ @article.send(:word_cloud_get_valid_strings)
96
+ end
97
+
98
+ it "should remove excluded methods from the output and not call them" do
99
+ @article.should_not_receive(:sample_method)
100
+ @article.send(:word_cloud_get_valid_strings)
101
+ end
107
102
  end
108
- end
109
103
 
104
+ describe "word_cloud_associated_objects" do
105
+ before(:each) do
106
+ @article.stub(:word_cloud_association_names_by_type).and_return([])
107
+ end
108
+ it "should get objects in belongs to, has ony, and has many relationships" do
109
+ @article.should_receive(:word_cloud_association_names_by_type).with(:belongs_to).and_return([])
110
+ @article.should_receive(:word_cloud_association_names_by_type).with(:has_one).and_return([])
111
+ @article.should_receive(:word_cloud_association_names_by_type).with(:has_many).and_return([])
112
+ @article.send(:word_cloud_associated_objects)
113
+ end
114
+
115
+ it "should remove objects that are included in excluded_models" do
116
+ author = Author.new
117
+ @article.stub(:author).and_return(author)
118
+ @article.stub(:word_cloud_association_names_by_type).with(:belongs_to).and_return([:author])
119
+ @article.word_cloud_attributes[:excluded_models] = [Author]
120
+ @article.send(:word_cloud_associated_objects).should be_empty
121
+ end
122
+
123
+ it "should return an array of rails models" do
124
+ author = Author.new
125
+ @article.stub(:author).and_return(author)
126
+ @article.stub(:word_cloud_association_names_by_type).with(:belongs_to).and_return([:author])
127
+ @article.word_cloud_attributes[:excluded_models] = []
128
+ @article.send(:word_cloud_associated_objects).should == [author]
129
+ end
130
+ end
131
+
132
+ describe "word_cloud_association_names_by_type" do
133
+ # this one is tough to test because we are just calling a single rails method
134
+ it "should return an array of symbols" do
135
+ associations = @article.send(:word_cloud_association_names_by_type, :belongs_to)
136
+ associations.should be_a_kind_of Array
137
+ associations.each { |a| a.should be_a_kind_of Symbol }
138
+ end
139
+ end
140
+
141
+ describe "word_cloud_object_name" do
142
+ it "should use the object's word cloud attribute for object name if set" do
143
+ @article.word_cloud_attributes[:object_name_methods] = [:hello]
144
+ @article.should_receive(:hello).and_return("hello")
145
+ @article.send(:word_cloud_object_name, @article).should == "hello"
146
+ end
147
+
148
+ it "should call each member of the method list until it finds one that is runnable on the object" do
149
+ publisher = Publisher.new
150
+
151
+ ActsAsWordCloud.config.should_receive(:object_name_methods).and_return([:hello, :world])
152
+ publisher.stub(:respond_to?).with(:word_cloud_attributes).and_return(false)
153
+
154
+ publisher.stub(:respond_to?).with(:hello).and_return(false)
155
+ publisher.should_not_receive(:hello)
156
+
157
+ publisher.stub(:respond_to?).with(:world).and_return(true)
158
+ publisher.should_receive(:world).and_return("world")
159
+
160
+ @article.send(:word_cloud_object_name, publisher).should == "world"
161
+ end
162
+
163
+ it "should return an empty string if no methods are found" do
164
+ publisher = Publisher.new
165
+
166
+ ActsAsWordCloud.config.should_receive(:object_name_methods).and_return([])
167
+ publisher.stub(:respond_to?).with(:word_cloud_attributes).and_return(false)
168
+
169
+ @article.send(:word_cloud_object_name, publisher).should == ""
170
+ end
171
+ end
172
+ end
@@ -1,9 +1,11 @@
1
1
  class Article < ActiveRecord::Base
2
- acts_as_word_cloud :methods_to_use => [:title], :excluded_models => [], :skipped_attributes => [], :depth => 1
2
+ acts_as_word_cloud :included_methods => [:truncated_title], :excluded_methods => [:genre], :excluded_models => [ArticleReader, Reader], :depth => 2, :object_name_method => :title
3
3
 
4
4
  belongs_to :author
5
- belongs_to :publisher
6
- belongs_to :site
7
- has_many :followings
8
- has_many :readers, :through => :followings
5
+ has_many :article_readers
6
+ has_many :readers, :through => :article_readers
7
+
8
+ def truncated_title
9
+ title[0..5] if title
10
+ end
9
11
  end