datashift 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/README.markdown +13 -12
  2. data/Rakefile +8 -8
  3. data/VERSION +1 -5
  4. data/datashift.gemspec +12 -38
  5. data/lib/applications/jruby/jexcel_file.rb +23 -11
  6. data/lib/datashift/method_detail.rb +44 -5
  7. data/lib/datashift/method_dictionary.rb +210 -0
  8. data/lib/datashift/method_mapper.rb +25 -191
  9. data/lib/generators/excel_generator.rb +12 -11
  10. data/lib/helpers/spree_helper.rb +36 -12
  11. data/lib/loaders/excel_loader.rb +2 -1
  12. data/lib/loaders/loader_base.rb +37 -20
  13. data/lib/loaders/spree/image_loader.rb +35 -16
  14. data/lib/loaders/spree/product_loader.rb +27 -1
  15. data/spec/csv_loader_spec.rb +3 -3
  16. data/spec/excel_exporter_spec.rb +79 -0
  17. data/spec/excel_generator_spec.rb +3 -3
  18. data/spec/excel_loader_spec.rb +35 -16
  19. data/spec/fixtures/ProjectsMultiCategoriesHeaderLookup.xls +0 -0
  20. data/spec/fixtures/images/DEMO_001_ror_bag.jpeg +0 -0
  21. data/spec/fixtures/images/DEMO_002_Powerstation.jpg +0 -0
  22. data/spec/fixtures/images/DEMO_003_ror_mug.jpeg +0 -0
  23. data/spec/fixtures/images/DEMO_004_ror_ringer.jpeg +0 -0
  24. data/spec/fixtures/interact_models_db.sqlite +0 -0
  25. data/spec/fixtures/interact_spree_db.sqlite +0 -0
  26. data/spec/fixtures/spree/SpreeProductsWithImages.xls +0 -0
  27. data/spec/loader_spec.rb +4 -4
  28. data/spec/method_dictionary_spec.rb +243 -0
  29. data/spec/method_mapper_spec.rb +17 -213
  30. data/spec/spec_helper.rb +1 -0
  31. data/spec/spree_loader_spec.rb +18 -1
  32. data/spec/spree_method_mapping_spec.rb +4 -4
  33. metadata +14 -130
  34. data/Gemfile +0 -28
  35. data/spec/fixtures/.~lock.ProjectsSingleCategories.xls# +0 -1
@@ -7,39 +7,58 @@ require 'loader_base'
7
7
 
8
8
  module DataShift
9
9
 
10
- class ImageLoader < LoaderBase
11
-
12
- def initialize(image = nil)
13
- super( Image, image )
14
- raise "Failed to create Image for loading" unless @load_object
15
- end
10
+ module ImageLoading
16
11
 
12
+
17
13
  # Note the Spree Image model sets default storage path to
18
14
  # => :path => ":rails_root/public/assets/products/:id/:style/:basename.:extension"
19
15
 
20
- def process( image_path, record = nil)
16
+ def create(image_path, viewable_record = nil, options = {})
21
17
 
18
+ image = Image.new
19
+
22
20
  unless File.exists?(image_path)
23
21
  puts "ERROR : Invalid Path"
24
- return
22
+ return image
25
23
  end
26
24
 
27
- alt = (record and record.respond_to? :name) ? record.name : ""
28
-
29
- @load_object.alt = alt
25
+ alt = if(options[:alt])
26
+ options[:alt]
27
+ else
28
+ (viewable_record and viewable_record.respond_to? :name) ? viewable_record.name : ""
29
+ end
30
+
31
+ image.alt = alt
30
32
 
31
33
  begin
32
- @load_object.attachment = File.new(image_path, "r")
34
+ image.attachment = File.new(image_path, "r")
33
35
  rescue => e
34
36
  puts e.inspect
35
37
  puts "ERROR : Failed to read image #{image_path}"
36
- return
38
+ return image
37
39
  end
38
40
 
39
- @load_object.attachment.reprocess!
40
- @load_object.viewable = record if record
41
+ image.attachment.reprocess!
42
+ image.viewable = viewable_record if viewable_record
43
+
44
+ puts image.save ? "Success: Crteated Image: #{image.inspect}" : "ERROR : Problem saving to DB Image: #{image.inspect}"
45
+ end
46
+ end
47
+
48
+ class ImageLoader < LoaderBase
49
+
50
+ include DataShift::ImageLoading
51
+
52
+ def initialize(image = nil)
53
+ super( Image, image )
54
+ raise "Failed to create Image for loading" unless @load_object
55
+ end
41
56
 
42
- puts @load_object.save ? "Success: Uploaded Image: #{@load_object.inspect}" : "ERROR : Problem saving to DB Image: #{@load_object}"
57
+ # Note the Spree Image model sets default storage path to
58
+ # => :path => ":rails_root/public/assets/products/:id/:style/:basename.:extension"
59
+
60
+ def process( image_path, record = nil)
61
+ @load_object = create_image(path, record)
43
62
  end
44
63
  end
45
64
 
@@ -6,8 +6,9 @@
6
6
  # Details:: Specific over-rides/additions to support Spree Products
7
7
  #
8
8
  require 'loader_base'
9
- require 'excel_loader'
10
9
  require 'csv_loader'
10
+ require 'excel_loader'
11
+ require 'image_loader'
11
12
 
12
13
  module DataShift
13
14
 
@@ -17,6 +18,7 @@ module DataShift
17
18
 
18
19
  include DataShift::CsvLoading
19
20
  include DataShift::ExcelLoading
21
+ include DataShift::ImageLoading
20
22
 
21
23
  def initialize(product = nil)
22
24
  super( Product, product, :instance_methods => true )
@@ -63,6 +65,10 @@ module DataShift
63
65
  elsif(@current_method_detail.operator?('product_properties') && current_value)
64
66
 
65
67
  add_properties
68
+
69
+ elsif(@current_method_detail.operator?('images') && current_value)
70
+
71
+ add_images
66
72
 
67
73
  elsif(@current_method_detail.operator?('count_on_hand') || @current_method_detail.operator?('on_hand') )
68
74
 
@@ -150,7 +156,27 @@ module DataShift
150
156
 
151
157
  end
152
158
 
159
+
160
+ # Special case for Images
161
+ # A list of paths to Images with a optional 'alt' value - supplied in form :
162
+ # path:alt|path2:alt2|path_3:alt3 etc
163
+ #
164
+ def add_images
165
+ # TODO smart column ordering to ensure always valid by time we get to associations
166
+ save_if_new
167
+
168
+ images = current_value.split(LoaderBase::multi_assoc_delim)
153
169
 
170
+ images.each do |image|
171
+
172
+ img_path, alt_text = image.split(LoaderBase::name_value_delim)
173
+
174
+ image = create_image(img_path, @load_object, :alt => alt_text)
175
+ end
176
+
177
+ end
178
+
179
+
154
180
  # Special case for ProductProperties since it can have additional value applied.
155
181
  # A list of Properties with a optional Value - supplied in form :
156
182
  # property.name:value|property.name|property.name:value
@@ -23,9 +23,9 @@ describe 'CSV Loader' do
23
23
  end
24
24
 
25
25
  before(:each) do
26
- MethodMapper.clear
27
- MethodMapper.find_operators( @klazz )
28
- MethodMapper.find_operators( @assoc_klazz )
26
+ MethodDictionary.clear
27
+ MethodDictionary.find_operators( @klazz )
28
+ MethodDictionary.find_operators( @assoc_klazz )
29
29
  end
30
30
 
31
31
  end
@@ -0,0 +1,79 @@
1
+ # Copyright:: (c) Autotelik Media Ltd 2011
2
+ # Author :: Tom Statter
3
+ # Date :: Aug 2011
4
+ # License:: MIT
5
+ #
6
+ # Details:: Specs for Excel aspect of Active Record Loader
7
+ #
8
+ require File.dirname(__FILE__) + '/spec_helper'
9
+
10
+ if(Guards::jruby?)
11
+ require 'erb'
12
+ require 'excel_generator'
13
+
14
+ include DataShift
15
+
16
+ describe 'Excel Generator' do
17
+
18
+ before(:all) do
19
+ db_connect( 'test_file' ) # , test_memory, test_mysql
20
+
21
+ # load our test model definitions - Project etc
22
+ require File.join($DataShiftFixturePath, 'test_model_defs')
23
+
24
+ # handle migration changes or reset of test DB
25
+ migrate_up
26
+
27
+ db_clear() # todo read up about proper transactional fixtures
28
+ results_clear()
29
+
30
+ @klazz = Project
31
+ @assoc_klazz = Category
32
+ end
33
+
34
+ before(:each) do
35
+ MethodDictionary.clear
36
+ MethodDictionary.find_operators( @klazz )
37
+ MethodDictionary.find_operators( @assoc_klazz )
38
+ end
39
+
40
+ it "should be able to create a new excel generator" do
41
+ generator = ExcelGenerator.new( 'dummy.xls' )
42
+
43
+ generator.should_not be_nil
44
+ end
45
+
46
+ it "should generate template .xls file from model" do
47
+
48
+ expect = result_file('project_template_spec.xls')
49
+
50
+ gen = ExcelGenerator.new( expect )
51
+
52
+ gen.generate(Project)
53
+
54
+ File.exists?(expect).should be_true
55
+
56
+ puts "Can manually check file @ #{expect}"
57
+ end
58
+
59
+ it "should export a simple model to .xls spreedsheet" do
60
+
61
+ Project.create( :value_as_string => 'Value as Text', :value_as_boolean => true, :value_as_double => 75.672)
62
+ #001 Demo string blah blah 2011-02-14 1.00 320.00
63
+
64
+ expect= result_file('simple_export_spec.xls')
65
+
66
+ gen = ExcelGenerator.new(expect)
67
+
68
+ items = Project.all
69
+
70
+ gen.export(items)
71
+
72
+ File.exists?(expect).should be_true
73
+
74
+ end
75
+
76
+ end
77
+ else
78
+ puts "WARNING: skipped excel_generator_spec : Requires JRUBY - JExcelFile requires JAVA"
79
+ end # jruby
@@ -32,9 +32,9 @@ if(Guards::jruby?)
32
32
  end
33
33
 
34
34
  before(:each) do
35
- MethodMapper.clear
36
- MethodMapper.find_operators( @klazz )
37
- MethodMapper.find_operators( @assoc_klazz )
35
+ MethodDictionary.clear
36
+ MethodDictionary.find_operators( @klazz )
37
+ MethodDictionary.find_operators( @assoc_klazz )
38
38
  end
39
39
 
40
40
  it "should be able to create a new excel generator" do
@@ -28,7 +28,7 @@ if(Guards::jruby?)
28
28
  db_clear() # todo read up about proper transactional fixtures
29
29
 
30
30
 
31
- @klazz = Project
31
+ Project = Project
32
32
  @assoc_klazz = Category
33
33
  end
34
34
 
@@ -36,49 +36,49 @@ if(Guards::jruby?)
36
36
 
37
37
  Project.delete_all
38
38
 
39
- %w{category_001 category_002 category_003}.each do |cat|
39
+ %w{category_001 category_002 category_003 category_004 category_005}.each do |cat|
40
40
  @assoc_klazz.find_or_create_by_reference(cat)
41
41
  end
42
42
 
43
43
 
44
- MethodMapper.clear
45
- MethodMapper.find_operators( @klazz )
46
- MethodMapper.find_operators( @assoc_klazz )
44
+ MethodDictionary.clear
45
+ MethodDictionary.find_operators( Project )
46
+ MethodDictionary.find_operators( @assoc_klazz )
47
47
  end
48
48
 
49
49
  it "should be able to create a new excel loader and load object" do
50
- loader = ExcelLoader.new( @klazz)
50
+ loader = ExcelLoader.new( Project)
51
51
 
52
52
  loader.load_object.should_not be_nil
53
- loader.load_object.should be_is_a(@klazz)
53
+ loader.load_object.should be_is_a(Project)
54
54
  loader.load_object.new_record?.should be_true
55
55
  end
56
56
 
57
57
  it "should process a simple .xls spreedsheet" do
58
58
 
59
- loader = ExcelLoader.new(@klazz)
59
+ loader = ExcelLoader.new(Project)
60
60
 
61
- count = @klazz.count
61
+ count = Project.count
62
62
  loader.perform_load( $DataShiftFixturePath + '/SimpleProjects.xls')
63
63
 
64
- loader.loaded_count.should == (@klazz.count - count)
64
+ loader.loaded_count.should == (Project.count - count)
65
65
 
66
66
  end
67
67
 
68
68
  it "should process multiple associationss from single column" do
69
69
 
70
- @klazz.find_by_title('001').should be_nil
71
- count = @klazz.count
70
+ Project.find_by_title('001').should be_nil
71
+ count = Project.count
72
72
 
73
- loader = ExcelLoader.new(@klazz)
73
+ loader = ExcelLoader.new(Project)
74
74
 
75
75
  loader.perform_load( $DataShiftFixturePath + '/ProjectsSingleCategories.xls')
76
76
 
77
77
  loader.loaded_count.should be > 3
78
- loader.loaded_count.should == (@klazz.count - count)
78
+ loader.loaded_count.should == (Project.count - count)
79
79
 
80
80
  {'001' => 2, '002' => 1, '003' => 3, '099' => 0 }.each do|title, expected|
81
- project = @klazz.find_by_title(title)
81
+ project = Project.find_by_title(title)
82
82
 
83
83
  project.should_not be_nil
84
84
  puts "#{project.inspect} [#{project.categories.size}]"
@@ -97,7 +97,7 @@ if(Guards::jruby?)
97
97
  loader.loaded_count.should == (Project.count - count)
98
98
 
99
99
  {'004' => 3, '005' => 1, '006' => 0, '007' => 1 }.each do|title, expected|
100
- project = @klazz.find_by_title(title)
100
+ project = Project.find_by_title(title)
101
101
 
102
102
  project.should_not be_nil
103
103
 
@@ -106,6 +106,25 @@ if(Guards::jruby?)
106
106
 
107
107
  end
108
108
 
109
+ it "should process multiple associations with lookup specified in column from excel spreedsheet", :fail => true do
110
+
111
+ loader = ExcelLoader.new(Project)
112
+
113
+ count = Project.count
114
+ loader.perform_load( $DataShiftFixturePath + '/ProjectsMultiCategoriesHeaderLookup.xls')
115
+
116
+ loader.loaded_count.should == (Project.count - count)
117
+
118
+ {'004' => 4, '005' => 1, '006' => 0, '007' => 1 }.each do|title, expected|
119
+ project = Project.find_by_title(title)
120
+
121
+ project.should_not be_nil
122
+
123
+ project.should have(expected).categories
124
+ end
125
+
126
+ end
127
+
109
128
  it "should process excel spreedsheet with extra undefined columns" do
110
129
  loader = ExcelLoader.new(Project)
111
130
  lambda {loader.perform_load( ifixture_file('BadAssociationName.xls') ) }.should_not raise_error
Binary file
data/spec/loader_spec.rb CHANGED
@@ -15,15 +15,15 @@ describe 'Basic Loader' do
15
15
  db_connect( 'test_file' ) # , test_memory, test_mysql
16
16
 
17
17
  # load our test model definitions - Project etc
18
- require File.join($DataShiftFixturePath, 'test_model_defs')
19
-
18
+ require File.join($DataShiftFixturePath, 'test_model_defs')
19
+
20
20
  migrate_up
21
21
  @klazz = Project
22
22
  end
23
23
 
24
24
  before(:each) do
25
- MethodMapper.clear
26
- MethodMapper.find_operators( @klazz )
25
+ MethodDictionary.clear
26
+ MethodDictionary.find_operators( @klazz )
27
27
  end
28
28
 
29
29
  it "should be able to create a new loader and load object" do
@@ -0,0 +1,243 @@
1
+ # Copyright:: (c) Autotelik Media Ltd 2011
2
+ # Author :: Tom Statter
3
+ # Date :: Aug 2011
4
+ # License:: MIT
5
+ #
6
+ # Details:: Specs for MethodMapper aspect of Active Record Loader
7
+ # MethodMapper provides the bridge between 'strings' e.g column headings
8
+ # and a classes different types of assignment operators
9
+ #
10
+ require File.dirname(__FILE__) + '/spec_helper'
11
+
12
+ describe 'Method Mapping' do
13
+
14
+ before(:all) do
15
+ db_connect( 'test_file' ) # , test_memory, test_mysql
16
+
17
+ # load our test model definitions - Project etc
18
+ require ifixture_file('test_model_defs')
19
+
20
+ migrate_up
21
+ end
22
+
23
+ before(:each) do
24
+ MethodDictionary.clear
25
+ MethodDictionary.find_operators( Project )
26
+ MethodDictionary.find_operators( Milestone )
27
+ end
28
+
29
+ it "should store dictionary for multiple AR models" do
30
+ MethodDictionary.assignments.size.should == 2
31
+ MethodDictionary.has_many.size.should == 2
32
+ end
33
+
34
+ it "should populate method dictionary for a given AR model" do
35
+
36
+ MethodDictionary.has_many.should_not be_empty
37
+ MethodDictionary.has_many[Project].should include('milestones')
38
+
39
+ MethodDictionary.assignments.should_not be_empty
40
+ MethodDictionary.assignments[Project].should include('id')
41
+ MethodDictionary.assignments[Project].should include('value_as_string')
42
+ MethodDictionary.assignments[Project].should include('value_as_text')
43
+
44
+ MethodDictionary.belongs_to.should_not be_empty
45
+ MethodDictionary.belongs_to[Project].should be_empty
46
+
47
+
48
+ MethodDictionary.column_types.should be_is_a(Hash)
49
+ MethodDictionary.column_types.should_not be_empty
50
+ MethodDictionary.column_types[Project].size.should == Project.columns.size
51
+
52
+
53
+ end
54
+
55
+ it "should populate assigment members without the equivalent association names" do
56
+
57
+ # we should remove has-many & belongs_to from basic assignment set as they require a DB lookup
58
+ # or a Model.create call, not a simple assignment
59
+
60
+ MethodDictionary.assignments_for(Project).should_not include( MethodDictionary.belongs_to_for(Project) )
61
+ MethodDictionary.assignments_for(Project).should_not include( MethodDictionary.has_many_for(Project) )
62
+ end
63
+
64
+
65
+ it "should populate assignment operators for method details for different forms of a column name" do
66
+
67
+ MethodDictionary.build_method_details( Project )
68
+
69
+ [:value_as_string, 'value_as_string', "VALUE as_STRING", "value as string"].each do |format|
70
+
71
+ method_details = MethodDictionary.find_method_detail( Project, format )
72
+
73
+ method_details.class.should == MethodDetail
74
+
75
+ method_details.operator.should == 'value_as_string'
76
+ method_details.operator_for(:assignment).should == 'value_as_string'
77
+
78
+ method_details.operator?('value_as_string').should be_true
79
+ method_details.operator?('blah_as_string').should be_false
80
+
81
+ method_details.operator_for(:belongs_to).should be_nil
82
+ method_details.operator_for(:has_many).should be_nil
83
+ end
84
+ end
85
+
86
+
87
+ # Note : Not all assignments will currently have a column type, for example
88
+ # those that are derived from a delegate_belongs_to
89
+
90
+ it "should populate column types for assignment operators in method details" do
91
+
92
+ MethodDictionary.build_method_details( Project )
93
+
94
+ [:value_as_string, 'value_as_string', "VALUE as_STRING", "value as string"].each do |format|
95
+
96
+ method_details = MethodDictionary.find_method_detail( Project, format )
97
+
98
+ method_details.class.should == MethodDetail
99
+
100
+ method_details.col_type.should_not be_nil
101
+ method_details.col_type.name.should == 'value_as_string'
102
+ method_details.col_type.default.should == nil
103
+ method_details.col_type.sql_type.should include 'varchar(255)' # db specific, sqlite
104
+ method_details.col_type.type.should == :string
105
+ end
106
+ end
107
+
108
+ it "should populate required Class for assignment operators based on column type" do
109
+
110
+ MethodDictionary.build_method_details( Project )
111
+
112
+ [:value_as_string, 'value_as_string', "VALUE as_STRING", "value as string"].each do |format|
113
+
114
+ method_details = MethodDictionary.find_method_detail( Project, format )
115
+
116
+ method_details.operator_class_name.should == 'String'
117
+ method_details.operator_class.should be_is_a(Class)
118
+ method_details.operator_class.should == String
119
+ end
120
+
121
+ end
122
+
123
+ it "should populate belongs_to operator for method details for different forms of a column name" do
124
+
125
+ MethodDictionary.build_method_details( Project )
126
+ MethodDictionary.build_method_details( Milestone )
127
+
128
+ # milestone.project = project.id
129
+ [:project, 'project', "PROJECT", "prOJECt"].each do |format|
130
+
131
+ method_details = MethodDictionary.find_method_detail( Milestone, format )
132
+
133
+ method_details.should_not be_nil
134
+
135
+ method_details.operator.should == 'project'
136
+ method_details.operator_for(:belongs_to).should == 'project'
137
+
138
+ method_details.operator_for(:assignment).should be_nil
139
+ method_details.operator_for(:has_many).should be_nil
140
+ end
141
+
142
+ end
143
+
144
+ it "should populate required Class for belongs_to operator method details" do
145
+
146
+ MethodDictionary.find_operators( LoaderRelease )
147
+ MethodDictionary.find_operators( LongAndComplexTableLinkedToVersion )
148
+
149
+ MethodDictionary.build_method_details( LoaderRelease )
150
+ MethodDictionary.build_method_details( LongAndComplexTableLinkedToVersion )
151
+
152
+
153
+ # release.project = project.id
154
+ [:project, 'project', "PROJECT", "prOJECt"].each do |format|
155
+
156
+ method_details = MethodDictionary.find_method_detail( LoaderRelease, format )
157
+
158
+ method_details.operator_class_name.should == 'Project'
159
+ method_details.operator_class.should == Project
160
+ end
161
+
162
+
163
+ [:version, "Version", "verSION"].each do |format|
164
+ method_details = MethodDictionary.find_method_detail( LongAndComplexTableLinkedToVersion, format )
165
+
166
+ method_details.operator_type.should == :belongs_to
167
+
168
+ method_details.operator_class_name.should == 'Version'
169
+ method_details.operator_class.should == Version
170
+ end
171
+ end
172
+
173
+ it "should populate required Class for has_one operator method details" do
174
+
175
+ MethodDictionary.find_operators( Version )
176
+ MethodDictionary.build_method_details( Version )
177
+
178
+ # version.long_and_complex_table_linked_to_version = LongAndComplexTableLinkedToVersion.create()
179
+
180
+ [:long_and_complex_table_linked_to_version, 'LongAndComplexTableLinkedToVersion', "Long And Complex_Table_Linked To Version", "Long_And_Complex_Table_Linked_To_Version"].each do |format|
181
+ method_details = MethodDictionary.find_method_detail( Version, format )
182
+
183
+ method_details.should_not be_nil
184
+
185
+ method_details.operator.should == 'long_and_complex_table_linked_to_version'
186
+
187
+ method_details.operator_type.should == :has_one
188
+
189
+ method_details.operator_class_name.should == 'LongAndComplexTableLinkedToVersion'
190
+ method_details.operator_class.should == LongAndComplexTableLinkedToVersion
191
+ end
192
+ end
193
+
194
+
195
+ it "should find has_many operator for method details" do
196
+
197
+ MethodDictionary.build_method_details( Project )
198
+
199
+ [:milestones, "Mile Stones", 'mileSTONES', 'MileStones'].each do |format|
200
+
201
+ method_details = MethodDictionary.find_method_detail( Project, format )
202
+
203
+ method_details.class.should == MethodDetail
204
+
205
+ result = 'milestones'
206
+ method_details.operator.should == result
207
+ method_details.operator_for(:has_many).should == result
208
+
209
+ method_details.operator_for(:belongs_to).should be_nil
210
+ method_details.operator_for(:assignments).should be_nil
211
+ end
212
+
213
+ end
214
+
215
+
216
+ it "should return nil when non existent column name" do
217
+ ["On sale", 'on_sale'].each do |format|
218
+ detail = MethodDictionary.find_method_detail( Project, format )
219
+
220
+ detail.should be_nil
221
+ end
222
+ end
223
+
224
+
225
+ it "should find a set of methods based on a list of column names" do
226
+ pending("key API - map column headers to set of methods")
227
+ end
228
+
229
+ it "should not by default map setter methods", :fail => true do
230
+ MethodDictionary.assignments[Milestone].should_not include('title')
231
+ end
232
+
233
+ it "should support reload and inclusion of setter methods", :fail => true do
234
+
235
+ MethodDictionary.assignments[Milestone].should_not include('title')
236
+
237
+ MethodDictionary.find_operators( Milestone, :reload => true, :instance_methods => true )
238
+
239
+ # Milestone delegates :title to Project
240
+ MethodDictionary.assignments[Milestone].should include('title')
241
+ end
242
+
243
+ end