oahu-dragonfly 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
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