oahu-dragonfly 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. data/.rspec +1 -0
  2. data/.yardopts +24 -0
  3. data/Gemfile +30 -0
  4. data/History.md +323 -0
  5. data/LICENSE +20 -0
  6. data/README.md +88 -0
  7. data/Rakefile +50 -0
  8. data/VERSION +1 -0
  9. data/config.ru +14 -0
  10. data/docs.watchr +1 -0
  11. data/dragonfly.gemspec +297 -0
  12. data/extra_docs/Analysers.md +66 -0
  13. data/extra_docs/Caching.md +23 -0
  14. data/extra_docs/Configuration.md +124 -0
  15. data/extra_docs/Couch.md +49 -0
  16. data/extra_docs/DataStorage.md +153 -0
  17. data/extra_docs/Encoding.md +67 -0
  18. data/extra_docs/GeneralUsage.md +121 -0
  19. data/extra_docs/Generators.md +60 -0
  20. data/extra_docs/Heroku.md +50 -0
  21. data/extra_docs/ImageMagick.md +125 -0
  22. data/extra_docs/Index.md +33 -0
  23. data/extra_docs/MimeTypes.md +40 -0
  24. data/extra_docs/Models.md +272 -0
  25. data/extra_docs/Mongo.md +45 -0
  26. data/extra_docs/Processing.md +77 -0
  27. data/extra_docs/Rack.md +52 -0
  28. data/extra_docs/Rails2.md +57 -0
  29. data/extra_docs/Rails3.md +62 -0
  30. data/extra_docs/Sinatra.md +25 -0
  31. data/extra_docs/URLs.md +169 -0
  32. data/features/images.feature +47 -0
  33. data/features/no_processing.feature +14 -0
  34. data/features/rails_3.0.5.feature +8 -0
  35. data/features/steps/common_steps.rb +8 -0
  36. data/features/steps/dragonfly_steps.rb +66 -0
  37. data/features/steps/rails_steps.rb +28 -0
  38. data/features/support/env.rb +13 -0
  39. data/features/support/setup.rb +32 -0
  40. data/fixtures/rails_3.0.5/files/app/models/album.rb +7 -0
  41. data/fixtures/rails_3.0.5/files/app/views/albums/new.html.erb +7 -0
  42. data/fixtures/rails_3.0.5/files/app/views/albums/show.html.erb +6 -0
  43. data/fixtures/rails_3.0.5/files/config/initializers/dragonfly.rb +4 -0
  44. data/fixtures/rails_3.0.5/files/features/manage_album_images.feature +38 -0
  45. data/fixtures/rails_3.0.5/files/features/step_definitions/helper_steps.rb +7 -0
  46. data/fixtures/rails_3.0.5/files/features/step_definitions/image_steps.rb +25 -0
  47. data/fixtures/rails_3.0.5/files/features/support/paths.rb +17 -0
  48. data/fixtures/rails_3.0.5/files/features/text_images.feature +7 -0
  49. data/fixtures/rails_3.0.5/template.rb +20 -0
  50. data/irbrc.rb +18 -0
  51. data/lib/dragonfly.rb +55 -0
  52. data/lib/dragonfly/active_model_extensions.rb +13 -0
  53. data/lib/dragonfly/active_model_extensions/attachment.rb +250 -0
  54. data/lib/dragonfly/active_model_extensions/attachment_class_methods.rb +148 -0
  55. data/lib/dragonfly/active_model_extensions/class_methods.rb +95 -0
  56. data/lib/dragonfly/active_model_extensions/instance_methods.rb +28 -0
  57. data/lib/dragonfly/active_model_extensions/validations.rb +41 -0
  58. data/lib/dragonfly/analyser.rb +58 -0
  59. data/lib/dragonfly/analysis/file_command_analyser.rb +32 -0
  60. data/lib/dragonfly/analysis/image_magick_analyser.rb +6 -0
  61. data/lib/dragonfly/app.rb +172 -0
  62. data/lib/dragonfly/config/heroku.rb +19 -0
  63. data/lib/dragonfly/config/image_magick.rb +6 -0
  64. data/lib/dragonfly/config/rails.rb +20 -0
  65. data/lib/dragonfly/configurable.rb +207 -0
  66. data/lib/dragonfly/core_ext/array.rb +7 -0
  67. data/lib/dragonfly/core_ext/hash.rb +7 -0
  68. data/lib/dragonfly/core_ext/object.rb +12 -0
  69. data/lib/dragonfly/core_ext/string.rb +9 -0
  70. data/lib/dragonfly/core_ext/symbol.rb +9 -0
  71. data/lib/dragonfly/data_storage.rb +9 -0
  72. data/lib/dragonfly/data_storage/couch_data_store.rb +64 -0
  73. data/lib/dragonfly/data_storage/file_data_store.rb +141 -0
  74. data/lib/dragonfly/data_storage/mongo_data_store.rb +86 -0
  75. data/lib/dragonfly/data_storage/s3data_store.rb +145 -0
  76. data/lib/dragonfly/encoder.rb +13 -0
  77. data/lib/dragonfly/encoding/image_magick_encoder.rb +6 -0
  78. data/lib/dragonfly/function_manager.rb +71 -0
  79. data/lib/dragonfly/generation/image_magick_generator.rb +6 -0
  80. data/lib/dragonfly/generator.rb +9 -0
  81. data/lib/dragonfly/hash_with_css_style_keys.rb +21 -0
  82. data/lib/dragonfly/image_magick/analyser.rb +51 -0
  83. data/lib/dragonfly/image_magick/config.rb +41 -0
  84. data/lib/dragonfly/image_magick/encoder.rb +57 -0
  85. data/lib/dragonfly/image_magick/generator.rb +145 -0
  86. data/lib/dragonfly/image_magick/processor.rb +99 -0
  87. data/lib/dragonfly/image_magick/utils.rb +72 -0
  88. data/lib/dragonfly/image_magick_utils.rb +4 -0
  89. data/lib/dragonfly/job.rb +451 -0
  90. data/lib/dragonfly/job_builder.rb +39 -0
  91. data/lib/dragonfly/job_definitions.rb +26 -0
  92. data/lib/dragonfly/job_endpoint.rb +15 -0
  93. data/lib/dragonfly/loggable.rb +28 -0
  94. data/lib/dragonfly/middleware.rb +20 -0
  95. data/lib/dragonfly/processing/image_magick_processor.rb +6 -0
  96. data/lib/dragonfly/processor.rb +9 -0
  97. data/lib/dragonfly/rails/images.rb +27 -0
  98. data/lib/dragonfly/response.rb +97 -0
  99. data/lib/dragonfly/routed_endpoint.rb +40 -0
  100. data/lib/dragonfly/serializer.rb +32 -0
  101. data/lib/dragonfly/server.rb +113 -0
  102. data/lib/dragonfly/simple_cache.rb +23 -0
  103. data/lib/dragonfly/temp_object.rb +175 -0
  104. data/lib/dragonfly/url_mapper.rb +78 -0
  105. data/samples/beach.png +0 -0
  106. data/samples/egg.png +0 -0
  107. data/samples/round.gif +0 -0
  108. data/samples/sample.docx +0 -0
  109. data/samples/taj.jpg +0 -0
  110. data/spec/dragonfly/active_model_extensions/model_spec.rb +1426 -0
  111. data/spec/dragonfly/active_model_extensions/spec_helper.rb +91 -0
  112. data/spec/dragonfly/analyser_spec.rb +123 -0
  113. data/spec/dragonfly/analysis/file_command_analyser_spec.rb +48 -0
  114. data/spec/dragonfly/app_spec.rb +135 -0
  115. data/spec/dragonfly/configurable_spec.rb +461 -0
  116. data/spec/dragonfly/core_ext/array_spec.rb +19 -0
  117. data/spec/dragonfly/core_ext/hash_spec.rb +19 -0
  118. data/spec/dragonfly/core_ext/string_spec.rb +17 -0
  119. data/spec/dragonfly/core_ext/symbol_spec.rb +17 -0
  120. data/spec/dragonfly/data_storage/couch_data_store_spec.rb +76 -0
  121. data/spec/dragonfly/data_storage/file_data_store_spec.rb +296 -0
  122. data/spec/dragonfly/data_storage/mongo_data_store_spec.rb +57 -0
  123. data/spec/dragonfly/data_storage/s3_data_store_spec.rb +258 -0
  124. data/spec/dragonfly/data_storage/shared_data_store_examples.rb +77 -0
  125. data/spec/dragonfly/function_manager_spec.rb +154 -0
  126. data/spec/dragonfly/hash_with_css_style_keys_spec.rb +24 -0
  127. data/spec/dragonfly/image_magick/analyser_spec.rb +64 -0
  128. data/spec/dragonfly/image_magick/encoder_spec.rb +41 -0
  129. data/spec/dragonfly/image_magick/generator_spec.rb +172 -0
  130. data/spec/dragonfly/image_magick/processor_spec.rb +233 -0
  131. data/spec/dragonfly/image_magick/utils_spec.rb +18 -0
  132. data/spec/dragonfly/job_builder_spec.rb +37 -0
  133. data/spec/dragonfly/job_definitions_spec.rb +35 -0
  134. data/spec/dragonfly/job_endpoint_spec.rb +173 -0
  135. data/spec/dragonfly/job_spec.rb +1046 -0
  136. data/spec/dragonfly/loggable_spec.rb +80 -0
  137. data/spec/dragonfly/middleware_spec.rb +47 -0
  138. data/spec/dragonfly/routed_endpoint_spec.rb +48 -0
  139. data/spec/dragonfly/serializer_spec.rb +61 -0
  140. data/spec/dragonfly/server_spec.rb +278 -0
  141. data/spec/dragonfly/simple_cache_spec.rb +27 -0
  142. data/spec/dragonfly/temp_object_spec.rb +306 -0
  143. data/spec/dragonfly/url_mapper_spec.rb +126 -0
  144. data/spec/functional/deprecations_spec.rb +51 -0
  145. data/spec/functional/image_magick_app_spec.rb +27 -0
  146. data/spec/functional/model_urls_spec.rb +85 -0
  147. data/spec/functional/remote_on_the_fly_spec.rb +51 -0
  148. data/spec/functional/to_response_spec.rb +31 -0
  149. data/spec/spec_helper.rb +51 -0
  150. data/spec/support/argument_matchers.rb +19 -0
  151. data/spec/support/image_matchers.rb +47 -0
  152. data/spec/support/simple_matchers.rb +53 -0
  153. data/yard/handlers/configurable_attr_handler.rb +38 -0
  154. data/yard/setup.rb +15 -0
  155. data/yard/templates/default/fulldoc/html/css/common.css +107 -0
  156. data/yard/templates/default/layout/html/layout.erb +89 -0
  157. data/yard/templates/default/module/html/configuration_summary.erb +31 -0
  158. data/yard/templates/default/module/setup.rb +17 -0
  159. metadata +544 -0
@@ -0,0 +1,91 @@
1
+ require 'spec_helper'
2
+ require 'active_model'
3
+
4
+ # --------------------------------------------------------------- #
5
+ # MODELS
6
+ # --------------------------------------------------------------- #
7
+ class MyModel
8
+
9
+ # Callbacks
10
+ extend ActiveModel::Callbacks
11
+ define_model_callbacks :save, :destroy
12
+
13
+ include ActiveModel::Validations
14
+
15
+ class << self
16
+ def create!(attrs={})
17
+ new(attrs).save!
18
+ end
19
+
20
+ def find(id)
21
+ new(instances[id])
22
+ end
23
+
24
+ def instances
25
+ @instances ||= {}
26
+ end
27
+ end
28
+
29
+ def initialize(attrs={})
30
+ attrs.each do |key, value|
31
+ send("#{key}=", value)
32
+ end
33
+ end
34
+
35
+ attr_accessor :id
36
+
37
+ def to_hash
38
+ self.class::ATTRIBUTES.inject({}) do |hash, attr|
39
+ hash[attr] = send(attr)
40
+ hash
41
+ end
42
+ end
43
+
44
+ def save
45
+ _run_save_callbacks {
46
+ self.id ||= rand(1000)
47
+ self.class.instances[id] = self.to_hash
48
+ }
49
+ end
50
+ def save!
51
+ save
52
+ self
53
+ end
54
+
55
+ def destroy
56
+ _run_destroy_callbacks {}
57
+ end
58
+ end
59
+
60
+ class Item < MyModel
61
+
62
+ ATTRIBUTES = [
63
+ :title,
64
+ :preview_image_uid,
65
+ :preview_image_some_analyser_method,
66
+ :preview_image_size,
67
+ :preview_image_name,
68
+ :preview_image_blah_blah,
69
+ :other_image_uid,
70
+ :yet_another_image_uid,
71
+ :otra_imagen_uid,
72
+ :trailer_video_uid,
73
+ :created_at,
74
+ :updated_at
75
+ ]
76
+ attr_accessor *ATTRIBUTES
77
+ end
78
+
79
+ class Car < MyModel
80
+ ATTRIBUTES = [
81
+ :image_uid,
82
+ :reliant_image_uid,
83
+ :type
84
+ ]
85
+ attr_accessor *ATTRIBUTES
86
+ end
87
+
88
+ class Photo < MyModel
89
+ ATTRIBUTES = [:image_uid]
90
+ attr_accessor *ATTRIBUTES
91
+ end
@@ -0,0 +1,123 @@
1
+ require 'spec_helper'
2
+
3
+ describe Dragonfly::Analyser do
4
+
5
+ before(:each) do
6
+ @analyser = Dragonfly::Analyser.new
7
+ @analyser.log = Logger.new(LOG_FILE)
8
+ end
9
+
10
+ describe "analysis_methods module" do
11
+
12
+ before(:each) do
13
+ @analyser.add(:num_letters){|temp_object, letter| temp_object.data.count(letter) }
14
+ @obj = Object.new
15
+ @obj.extend @analyser.analysis_methods
16
+ end
17
+
18
+ it "should return a module" do
19
+ @analyser.analysis_methods.should be_a(Module)
20
+ end
21
+
22
+ it "should provide the object with the analyser method" do
23
+ @obj.analyser.should == @analyser
24
+ end
25
+
26
+ it "should provide the object with the direct analysis method, provided that analyse method exists" do
27
+ def @obj.analyse(meth, *args)
28
+ analyser.analyse Dragonfly::TempObject.new('HELLO'), meth, *args
29
+ end
30
+ @obj.num_letters('L').should == 2
31
+ end
32
+
33
+ end
34
+
35
+ describe "analyse" do
36
+ it "should return nil if the function isn't defined" do
37
+ @analyser.analyse(Dragonfly::TempObject.new("Hello"), :width).should be_nil
38
+ end
39
+ it "should return nil if the function can't be handled" do
40
+ @analyser.add(:width){ throw :unable_to_handle }
41
+ @analyser.analyse(Dragonfly::TempObject.new("Hello"), :width).should be_nil
42
+ end
43
+ end
44
+
45
+ describe "analysis_method_names" do
46
+ it "should return the analysis methods" do
47
+ @analyser.add(:width){}
48
+ @analyser.add(:height){}
49
+ @analyser.analysis_method_names.should == [:width, :height]
50
+ end
51
+ end
52
+
53
+ describe "cache" do
54
+ before(:each) do
55
+ @temp_object = Dragonfly::TempObject.new('HELLO')
56
+ end
57
+
58
+ def it_should_analyse_using(meth, temp_object, *args)
59
+ result = mock('result')
60
+ @analyser.should_receive(:call_last).with(meth, temp_object, *args).exactly(:once).and_return result
61
+ @analyser.analyse(temp_object, meth, *args).should == result
62
+ result
63
+ end
64
+
65
+ it "should do the analysis the first time" do
66
+ it_should_analyse_using(:blah, @temp_object, :arg1)
67
+ end
68
+
69
+ describe "when already called" do
70
+ before(:each) do
71
+ @result = it_should_analyse_using(:blah, @temp_object, :arg1)
72
+ end
73
+
74
+ it "should not do it subsequent times but still return the result" do
75
+ @analyser.should_not_receive(:call_last)
76
+ @analyser.analyse(@temp_object, :blah, :arg1).should == @result
77
+ @analyser.analyse(@temp_object, :blah, :arg1).should == @result
78
+ end
79
+
80
+ it "should not use the cache if the temp_object is different" do
81
+ temp_object = Dragonfly::TempObject.new('aaa')
82
+ it_should_analyse_using(:blah, temp_object, :arg1)
83
+ end
84
+
85
+ it "should not use the cache if the method name is different" do
86
+ it_should_analyse_using(:egghead, @temp_object, :arg1)
87
+ end
88
+
89
+ it "should not use the cache if the args are different" do
90
+ it_should_analyse_using(:blah, @temp_object, :arg2)
91
+ end
92
+
93
+ it "should do it again if the cache has been cleared" do
94
+ @analyser.clear_cache!
95
+ it_should_analyse_using(:blah, @temp_object, :arg1)
96
+ end
97
+
98
+ it "should not use the cache if it has been turned off" do
99
+ @analyser.enable_cache = false
100
+ it_should_analyse_using(:blah, @temp_object, :arg1)
101
+ end
102
+
103
+ end
104
+
105
+ describe "cache size" do
106
+ it "should not exceed the cache size" do
107
+ @analyser.cache_size = 2
108
+
109
+ res1 = it_should_analyse_using(:blah, @temp_object, :arg1)
110
+ res2 = it_should_analyse_using(:blah, @temp_object, :arg2)
111
+ res3 = it_should_analyse_using(:blah, @temp_object, :arg3) # Should kick out first one
112
+
113
+ it_should_analyse_using(:blah, @temp_object, :arg1)
114
+
115
+ # Third analysis should still be cached
116
+ @analyser.should_not_receive(:call_last)
117
+ @analyser.analyse(@temp_object, :blah, :arg3).should == res3
118
+ end
119
+ end
120
+
121
+ end
122
+
123
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ png_path = File.dirname(__FILE__) + '/../../../samples/egg.png'
4
+
5
+ describe Dragonfly::Analysis::FileCommandAnalyser do
6
+
7
+ before(:each) do
8
+ @analyser = Dragonfly::Analysis::FileCommandAnalyser.new
9
+ end
10
+
11
+ describe "mime_type" do
12
+
13
+ describe "when using the filesystem" do
14
+ before(:each) do
15
+ @analyser.use_filesystem = true
16
+ @temp_object = Dragonfly::TempObject.new(File.new(png_path))
17
+ end
18
+ it "should give the mime-type" do
19
+ @analyser.mime_type(@temp_object).should == 'image/png'
20
+ end
21
+ it "should not have initialized the data string" do
22
+ @analyser.mime_type(@temp_object)
23
+ @temp_object.instance_eval{@data}.should be_nil
24
+ end
25
+ end
26
+
27
+ describe "when not using the filesystem" do
28
+ before(:each) do
29
+ @analyser.use_filesystem = false
30
+ @temp_object = Dragonfly::TempObject.new(File.read(png_path))
31
+ end
32
+ it "should give the mime-type" do
33
+ @analyser.mime_type(@temp_object).should == 'image/png'
34
+ end
35
+ it "should not have initialized the file" do
36
+ @analyser.mime_type(@temp_object)
37
+ @temp_object.instance_eval{@tempfile}.should be_nil
38
+ end
39
+ it "should work properly (without a broken pipe error) for big files of format jpg" do
40
+ data = Dragonfly::ImageMagick::Generator.new.plasma(1000, 1000, :jpg).first
41
+ temp_object = Dragonfly::TempObject.new(data)
42
+ @analyser.mime_type(temp_object).should == "image/jpeg"
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,135 @@
1
+ require 'spec_helper'
2
+ require 'rack/mock'
3
+
4
+ def request(app, path)
5
+ Rack::MockRequest.new(app).get(path)
6
+ end
7
+
8
+ describe Dragonfly::App do
9
+
10
+ describe ".instance" do
11
+
12
+ it "should create a new instance if it didn't already exist" do
13
+ app = Dragonfly::App.instance(:images)
14
+ app.should be_a(Dragonfly::App)
15
+ end
16
+
17
+ it "should return an existing instance if called by name" do
18
+ app = Dragonfly::App.instance(:images)
19
+ Dragonfly::App.instance(:images).should == app
20
+ end
21
+
22
+ it "should also work using square brackets" do
23
+ Dragonfly[:images].should == Dragonfly::App.instance(:images)
24
+ end
25
+
26
+ end
27
+
28
+ describe ".new" do
29
+ it "should not be callable" do
30
+ lambda{
31
+ Dragonfly::App.new
32
+ }.should raise_error(NoMethodError)
33
+ end
34
+ end
35
+
36
+ describe "mime types" do
37
+ describe "#mime_type_for" do
38
+ before(:each) do
39
+ @app = test_app
40
+ end
41
+ it "should return the correct mime type for a symbol" do
42
+ @app.mime_type_for(:png).should == 'image/png'
43
+ end
44
+ it "should work for strings" do
45
+ @app.mime_type_for('png').should == 'image/png'
46
+ end
47
+ it "should work with uppercase strings" do
48
+ @app.mime_type_for('PNG').should == 'image/png'
49
+ end
50
+ it "should work with a dot" do
51
+ @app.mime_type_for('.png').should == 'image/png'
52
+ end
53
+ it "should return nil if not known" do
54
+ @app.mime_type_for(:mark).should be_nil
55
+ end
56
+ it "should allow for configuring extra mime types" do
57
+ @app.register_mime_type 'mark', 'application/mark'
58
+ @app.mime_type_for(:mark).should == 'application/mark'
59
+ end
60
+ it "should override existing mime types when registered" do
61
+ @app.register_mime_type :png, 'ping/pong'
62
+ @app.mime_type_for(:png).should == 'ping/pong'
63
+ end
64
+ it "should have a per-app mime-type configuration" do
65
+ other_app = Dragonfly[:other_app]
66
+ @app.register_mime_type(:mark, 'first/one')
67
+ other_app.register_mime_type(:mark, 'second/one')
68
+ @app.mime_type_for(:mark).should == 'first/one'
69
+ other_app.mime_type_for(:mark).should == 'second/one'
70
+ end
71
+ end
72
+ end
73
+
74
+ describe "remote_url_for" do
75
+ before(:each) do
76
+ @app = test_app
77
+ @app.datastore = Object.new
78
+ end
79
+ it "should raise an error if the datastore doesn't provide it" do
80
+ lambda{
81
+ @app.remote_url_for('some_uid')
82
+ }.should raise_error(NotImplementedError)
83
+ end
84
+ it "should correctly call it if the datastore provides it" do
85
+ @app.datastore.should_receive(:url_for).with('some_uid', :some => :opts).and_return 'http://egg.head'
86
+ @app.remote_url_for('some_uid', :some => :opts).should == 'http://egg.head'
87
+ end
88
+ end
89
+
90
+ describe "#store" do
91
+ before(:each) do
92
+ @app = test_app
93
+ end
94
+ it "should allow just storing content" do
95
+ @app.datastore.should_receive(:store).with(a_temp_object_with_data("HELLO"), {})
96
+ @app.store("HELLO")
97
+ end
98
+ it "should allow storing using a TempObject" do
99
+ temp_object = Dragonfly::TempObject.new("HELLO")
100
+ @app.datastore.should_receive(:store).with(temp_object, {})
101
+ @app.store(temp_object)
102
+ end
103
+ it "should allow storing with extra stuff" do
104
+ @app.datastore.should_receive(:store).with(
105
+ a_temp_object_with_data("HELLO"), :meta => {:egg => :head}, :option => :blarney
106
+ )
107
+ @app.store("HELLO", :meta => {:egg => :head}, :option => :blarney)
108
+ end
109
+ end
110
+
111
+ describe "url_for" do
112
+ before(:each) do
113
+ @app = test_app
114
+ @job = @app.fetch('eggs')
115
+ end
116
+ it "should give the server url by default" do
117
+ @app.url_for(@job).should =~ %r{^/\w+$}
118
+ end
119
+ it "should allow configuring" do
120
+ @app.configure do |c|
121
+ c.define_url do |app, job, opts|
122
+ "doogies"
123
+ end
124
+ end
125
+ @app.url_for(@job).should == 'doogies'
126
+ end
127
+ it "should yield the correct dooberries" do
128
+ @app.define_url do |app, job, opts|
129
+ [app, job, opts]
130
+ end
131
+ @app.url_for(@job, {'chuddies' => 'margate'}).should == [@app, @job, {'chuddies' => 'margate'}]
132
+ end
133
+ end
134
+
135
+ end
@@ -0,0 +1,461 @@
1
+ require 'spec_helper'
2
+
3
+ describe Dragonfly::Configurable do
4
+
5
+ before(:each) do
6
+ class Car
7
+ include Dragonfly::Configurable
8
+ configurable_attr :colour
9
+ configurable_attr :top_speed, 216
10
+ def self.other_thing=(thing); end
11
+ end
12
+ @car = Car.new
13
+ end
14
+
15
+ describe "setup" do
16
+ it "should provide attr_readers for configurable attributes" do
17
+ @car.should respond_to(:colour)
18
+ end
19
+
20
+ it "should provide attr_writers for configurable attributes" do
21
+ @car.colour = 'verde'
22
+ @car.colour.should == 'verde'
23
+ end
24
+
25
+ it "should set default values for configurable attributes" do
26
+ @car.top_speed.should == 216
27
+ end
28
+
29
+ it "should set the default as nil if not specified" do
30
+ @car.colour.should be_nil
31
+ end
32
+
33
+ it "should allow setting to nil" do
34
+ @car.top_speed = nil
35
+ @car.top_speed.should be_nil
36
+ end
37
+
38
+ it "should allow specifying configurable attrs as strings" do
39
+ class Bike
40
+ include Dragonfly::Configurable
41
+ configurable_attr 'colour', 'rude'
42
+ end
43
+ Bike.new.colour.should == 'rude'
44
+ end
45
+ end
46
+
47
+ describe "configuring" do
48
+ it "should allow you to change values" do
49
+ @car.configure do |c|
50
+ c.colour = 'red'
51
+ end
52
+ @car.colour.should == 'red'
53
+ end
54
+
55
+ it "should not allow you to call other methods on the object via the configuration" do
56
+ lambda{
57
+ @car.configure do |c|
58
+ c.other_thing = 5
59
+ end
60
+ }.should raise_error(Dragonfly::Configurable::BadConfigAttribute)
61
+ end
62
+
63
+ it "should return itself" do
64
+ @car.configure{|c|}.should == @car
65
+ end
66
+ end
67
+
68
+ describe "getting configuration" do
69
+ it "should return the configuration when nothing is set" do
70
+ @car.configuration.should == {}
71
+ end
72
+ it "should return the configuration when something is set" do
73
+ @car.top_speed = 10
74
+ @car.configuration.should == {:top_speed => 10}
75
+ end
76
+ end
77
+
78
+ describe "multiple objects" do
79
+ it "should return the default configuration" do
80
+ Car.default_configuration.should == {:colour => nil, :top_speed => 216}
81
+ end
82
+ it "should allow instances to be configured differently" do
83
+ car1 = Car.new
84
+ car1.configure{|c| c.colour = 'green'}
85
+ car2 = Car.new
86
+ car2.configure{|c| c.colour = 'yellow'}
87
+ car1.configuration.should == {:colour => 'green'}
88
+ car2.configuration.should == {:colour => 'yellow'}
89
+ end
90
+ end
91
+
92
+ describe "lazy attributes" do
93
+ before(:each) do
94
+ cow = @cow = mock('cow')
95
+ class Lazy; end
96
+ Lazy.class_eval do
97
+ include Dragonfly::Configurable
98
+ configurable_attr(:sound){ cow.moo }
99
+ end
100
+ @lazy = Lazy.new
101
+ end
102
+ it "should not call the block if the configurable attribute is set to something else" do
103
+ @cow.should_not_receive(:moo)
104
+ @lazy.configure{|c| c.sound = 'baa' }
105
+ @lazy.sound.should == 'baa'
106
+ end
107
+ it "should call the block if it's not been changed, once it's accessed" do
108
+ @cow.should_receive(:moo).and_return('mooo!')
109
+ @lazy.sound.should == 'mooo!'
110
+ end
111
+ it "should not call the block when accessed again" do
112
+ @cow.should_receive(:moo).exactly(:once).and_return('mooo!')
113
+ @lazy.sound.should == 'mooo!'
114
+ @lazy.sound.should == 'mooo!'
115
+ end
116
+ it "should not call an explicitly passed in proc" do
117
+ @lazy.configure{|c| c.sound = lambda{ @cow.fart }}
118
+ @lazy.sound.should be_a(Proc)
119
+ end
120
+ end
121
+
122
+ describe "using in the singleton class" do
123
+ it "should work" do
124
+ class OneOff
125
+ class << self
126
+ include Dragonfly::Configurable
127
+ configurable_attr :food, 'bread'
128
+ end
129
+ end
130
+ OneOff.food.should == 'bread'
131
+ end
132
+ end
133
+
134
+ describe "configuration method" do
135
+
136
+ before(:each) do
137
+ class ClassWithMethod
138
+ include Dragonfly::Configurable
139
+ def add_thing(thing)
140
+ 'poo'
141
+ end
142
+ def remove_thing(thing)
143
+ 'bum'
144
+ end
145
+ configuration_method :add_thing, :remove_thing
146
+ end
147
+ @thing = ClassWithMethod.new
148
+ end
149
+
150
+ it "should allow calling the method through 'configure'" do
151
+ @thing.configure do |c|
152
+ c.add_thing('duck')
153
+ c.remove_thing('dog')
154
+ end
155
+ end
156
+
157
+ end
158
+
159
+ describe "nested configurable objects" do
160
+
161
+ before(:each) do
162
+ class NestedThing
163
+ include Dragonfly::Configurable
164
+ configurable_attr :age, 29
165
+ def some_method(val)
166
+ @some_thing = val
167
+ end
168
+ configuration_method :some_method
169
+ attr_reader :some_thing
170
+ end
171
+
172
+ class Car
173
+ def nested_thing
174
+ @nested_thing ||= NestedThing.new
175
+ end
176
+ nested_configurable :nested_thing
177
+ end
178
+
179
+ @car.configure do |c|
180
+ c.nested_thing.configure do |nt|
181
+ nt.age = 50
182
+ nt.some_method('yo')
183
+ end
184
+ end
185
+ end
186
+
187
+ it "should allow configuring nested configurable accessors" do
188
+ @car.nested_thing.age.should == 50
189
+ end
190
+
191
+ it "should allow configuring nested configurable normal methods" do
192
+ @car.nested_thing.some_thing.should == 'yo'
193
+ end
194
+
195
+ it "should not allow configuring directly on the config object" do
196
+ expect{
197
+ @car.configure do |c|
198
+ c.some_method('other')
199
+ end
200
+ }.to raise_error(Dragonfly::Configurable::BadConfigAttribute)
201
+ end
202
+ end
203
+
204
+ describe "configuring with a saved config" do
205
+ before(:each) do
206
+ @cool_configuration = Object.new
207
+ def @cool_configuration.apply_configuration(car, colour=nil)
208
+ car.configure do |c|
209
+ c.colour = (colour || 'vermelho')
210
+ end
211
+ end
212
+ end
213
+
214
+ it "should allow configuration by a saved config" do
215
+ @car.configure_with(@cool_configuration)
216
+ @car.colour.should == 'vermelho'
217
+ @car.top_speed.should == 216
218
+ end
219
+
220
+ it "should pass any args through to the saved config" do
221
+ @car.configure_with(@cool_configuration, 'preto')
222
+ @car.colour.should == 'preto'
223
+ end
224
+
225
+ it "should yield a block for any extra configuration" do
226
+ @car.configure_with(@cool_configuration) do |c|
227
+ c.colour = 'branco'
228
+ end
229
+ @car.colour.should == 'branco'
230
+ end
231
+
232
+ it "should return itself" do
233
+ @car.configure_with(@cool_configuration).should == @car
234
+ end
235
+
236
+ describe "using a symbol to specify the config" do
237
+
238
+ before(:all) do
239
+ @rally_config = Object.new
240
+ Car.register_configuration(:rally, @rally_config)
241
+ @long_journey_config = Object.new
242
+ Car.register_configuration(:long_journey){ @long_journey_config }
243
+ Car.register_configuration(:some_library){ SomeLibrary }
244
+ end
245
+
246
+ it "should map the symbol to the correct configuration" do
247
+ @rally_config.should_receive(:apply_configuration).with(@car)
248
+ @car.configure_with(:rally)
249
+ end
250
+
251
+ it "should map the symbol to the correct configuration lazily" do
252
+ @long_journey_config.should_receive(:apply_configuration).with(@car)
253
+ @car.configure_with(:long_journey)
254
+ end
255
+
256
+ it "should throw an error if an unknown symbol is passed in" do
257
+ lambda {
258
+ @car.configure_with(:eggs)
259
+ }.should raise_error(ArgumentError)
260
+ end
261
+
262
+ it "should only try to load the library when asked to" do
263
+ lambda{
264
+ @car.configure_with(:some_library)
265
+ }.should raise_error(NameError, /uninitialized constant.*SomeLibrary/)
266
+ end
267
+ end
268
+
269
+ end
270
+
271
+ describe "falling back to another config" do
272
+ before(:each) do
273
+ @garage = Object.new
274
+ class << @garage
275
+ include Dragonfly::Configurable
276
+ configurable_attr :top_speed, 100
277
+ end
278
+ @car.use_as_fallback_config(@garage)
279
+ end
280
+
281
+ describe "when nothing set" do
282
+ it "should use its default" do
283
+ @car.top_speed.should == 216
284
+ end
285
+ it "shouldn't affect the fallback config object" do
286
+ @garage.top_speed.should == 100
287
+ end
288
+ end
289
+
290
+ describe "if set" do
291
+ before(:each) do
292
+ @car.top_speed = 444
293
+ end
294
+ it "should work normally" do
295
+ @car.top_speed.should == 444
296
+ end
297
+ it "shouldn't affect the fallback config object" do
298
+ @garage.top_speed.should == 100
299
+ end
300
+ end
301
+
302
+ describe "both set" do
303
+ before(:each) do
304
+ @car.top_speed = 444
305
+ @garage.top_speed = 3000
306
+ end
307
+ it "should prefer its own setting" do
308
+ @car.top_speed.should == 444
309
+ end
310
+ it "shouldn't affect the fallback config object" do
311
+ @garage.top_speed.should == 3000
312
+ end
313
+ end
314
+
315
+ describe "the fallback config is set" do
316
+ before(:each) do
317
+ @garage.top_speed = 3000
318
+ end
319
+ it "should use the fallback config" do
320
+ @car.top_speed.should == 3000
321
+ end
322
+ it "shouldn't affect the fallback config object" do
323
+ @garage.top_speed.should == 3000
324
+ end
325
+ end
326
+
327
+ describe "falling back multiple levels" do
328
+ before(:each) do
329
+ @klass = Class.new
330
+ @klass.class_eval do
331
+ include Dragonfly::Configurable
332
+ configurable_attr :veg, 'carrot'
333
+ end
334
+ @a = @klass.new
335
+ @b = @klass.new
336
+ @b.use_as_fallback_config(@a)
337
+ @c = @klass.new
338
+ @c.use_as_fallback_config(@b)
339
+ end
340
+
341
+ it "should be the default if nothing set" do
342
+ @c.veg.should == 'carrot'
343
+ end
344
+
345
+ it "should fall all the way back to the top one if necessary" do
346
+ @a.veg = 'turnip'
347
+ @c.veg.should == 'turnip'
348
+ end
349
+
350
+ it "should prefer the closer one over the further away one" do
351
+ @b.veg = 'tatty'
352
+ @a.veg = 'turnip'
353
+ @c.veg.should == 'tatty'
354
+ end
355
+
356
+ it "should work properly with nils" do
357
+ @a.veg = nil
358
+ @c.veg = 'broc'
359
+ @a.veg.should be_nil
360
+ @b.veg.should be_nil
361
+ @c.veg.should == 'broc'
362
+ end
363
+ end
364
+
365
+ describe "objects with different methods" do
366
+ before(:each) do
367
+ @dad = Object.new
368
+ class << @dad
369
+ include Dragonfly::Configurable
370
+ end
371
+ @kid = Object.new
372
+ class << @kid
373
+ include Dragonfly::Configurable
374
+ configurable_attr :lug, 'default-lug'
375
+ end
376
+ @kid.use_as_fallback_config(@dad)
377
+ end
378
+
379
+ it "should not allow setting on the fallback obj directly" do
380
+ lambda{
381
+ @dad.lug = 'leg'
382
+ }.should raise_error(NoMethodError)
383
+ end
384
+
385
+ it "should not have the fallback obj respond to the method" do
386
+ @dad.should_not respond_to(:lug=)
387
+ end
388
+
389
+ it "should allow configuring through the fallback object even if it doesn't have that method" do
390
+ @dad.configure do |c|
391
+ c.lug = 'leg'
392
+ end
393
+ @kid.lug.should == 'leg'
394
+ end
395
+
396
+ it "should work when a grandchild config is added later" do
397
+ grandkid = Object.new
398
+ class << grandkid
399
+ include Dragonfly::Configurable
400
+ configurable_attr :oogie, 'boogie'
401
+ end
402
+ grandkid.use_as_fallback_config(@kid)
403
+ @dad.configure{|c| c.oogie = 'duggen' }
404
+ grandkid.oogie.should == 'duggen'
405
+ end
406
+
407
+ it "should allow configuring twice through the fallback object" do
408
+ @dad.configure{|c| c.lug = 'leg' }
409
+ @dad.configure{|c| c.lug = 'blug' }
410
+ @kid.lug.should == 'blug'
411
+ end
412
+ end
413
+
414
+ describe "clashing with configurable modules" do
415
+ before(:each) do
416
+ @mod = mod = Module.new
417
+ @mod.module_eval do
418
+ include Dragonfly::Configurable
419
+ configurable_attr :team, 'spurs'
420
+ end
421
+ @class = Class.new
422
+ @class.class_eval do
423
+ include mod
424
+ include Dragonfly::Configurable
425
+ configurable_attr :tree, 'elm'
426
+ end
427
+ end
428
+
429
+ it "should not override the defaults from the module" do
430
+ obj = @class.new
431
+ obj.team.should == 'spurs'
432
+ end
433
+
434
+ it "should still use its own defaults" do
435
+ obj = @class.new
436
+ obj.tree.should == 'elm'
437
+ end
438
+
439
+ describe "when the configurable_attr is specified in a subclass that doesn't include Configurable" do
440
+ before(:each) do
441
+ @subclass = Class.new(@class)
442
+ @subclass.class_eval do
443
+ configurable_attr :car, 'mazda'
444
+ configurable_attr :tree, 'oak'
445
+ end
446
+ @obj = @subclass.new
447
+ end
448
+
449
+ it "should still work with default values" do
450
+ @obj.car.should == 'mazda'
451
+ end
452
+
453
+ it "should override the default from the parent" do
454
+ @obj.tree.should == 'oak'
455
+ end
456
+ end
457
+
458
+ end
459
+
460
+ end
461
+ end