├── .gitignore ├── CHANGELOG.rdoc ├── LICENSE ├── Manifest ├── NOTES.rdoc ├── README.rdoc ├── Rakefile ├── VERSION.yml ├── WISHLIST ├── enumerated_attribute.gemspec ├── init.rb ├── lib ├── enumerated_attribute.rb └── enumerated_attribute │ ├── attribute.rb │ ├── attribute │ ├── arguments.rb │ ├── attribute_descriptor.rb │ ├── class_methods.rb │ └── instance_methods.rb │ ├── integrations.rb │ ├── integrations │ ├── active_record.rb │ ├── datamapper.rb │ ├── default.rb │ └── object.rb │ ├── method_definition_dsl.rb │ └── rails_helpers.rb └── spec ├── active_record ├── active_record_spec.rb ├── association_test_classes.rb ├── associations_spec.rb ├── cfg.rb ├── df.rb ├── inheritance_classes.rb ├── inheritance_spec.rb ├── race_car.rb ├── single_table_inheritance_spec.rb ├── sti_classes.rb ├── sti_spec.rb └── test_in_memory.rb ├── car.rb ├── cfg.rb ├── inheritance_classes.rb ├── inheritance_spec.rb ├── new_and_method_missing_spec.rb ├── plural.rb ├── poro_spec.rb ├── rails ├── .gitignore ├── README ├── Rakefile ├── app │ ├── controllers │ │ ├── application_controller.rb │ │ └── form_test_controller.rb │ ├── helpers │ │ ├── application_helper.rb │ │ └── form_test_helper.rb │ ├── models │ │ └── user.rb │ └── views │ │ ├── form_test │ │ ├── form.html.erb │ │ ├── form_for.html.erb │ │ ├── form_tag.html.erb │ │ └── index.html.erb │ │ └── layouts │ │ └── application.html.erb ├── config │ ├── boot.rb │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── backtrace_silencers.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── new_rails_defaults.rb │ │ └── session_store.rb │ ├── locales │ │ └── en.yml │ └── routes.rb ├── db │ ├── development.sqlite3 │ ├── migrate │ │ ├── 20090804230221_create_sessions.rb │ │ └── 20090804230546_create_users.rb │ ├── schema.rb │ └── test.sqlite3 ├── public │ ├── 404.html │ ├── 422.html │ ├── 500.html │ ├── favicon.ico │ ├── images │ │ └── rails.png │ ├── index.html │ ├── javascripts │ │ ├── application.js │ │ ├── controls.js │ │ ├── dragdrop.js │ │ ├── effects.js │ │ └── prototype.js │ ├── robots.txt │ └── stylesheets │ │ └── scaffold.css ├── script │ ├── about │ ├── autospec │ ├── console │ ├── dbconsole │ ├── destroy │ ├── generate │ ├── performance │ │ ├── benchmarker │ │ └── profiler │ ├── plugin │ ├── runner │ ├── server │ ├── spec │ └── spec_server ├── spec │ ├── controllers │ │ └── form_test_controller_spec.rb │ ├── integrations │ │ └── enum_select_spec.rb │ ├── matchers.rb │ ├── rcov.opts │ ├── spec.opts │ ├── spec_helper.rb │ └── views │ │ └── form_test │ │ ├── form.html.erb_spec.rb │ │ ├── form_for.html.erb_spec.rb │ │ └── form_tag.html.erb_spec.rb └── test │ ├── fixtures │ └── users.yml │ ├── functional │ └── form_test_controller_test.rb │ ├── performance │ └── browsing_test.rb │ ├── test_helper.rb │ └── unit │ ├── helpers │ └── form_test_helper_test.rb │ └── user_test.rb ├── rcov.opts ├── spec.opts └── tractor.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | **/*.log 3 | spec/**/*.log 4 | *.gem 5 | pkg 6 | rdoc 7 | coverage 8 | doc 9 | log 10 | nbproject 11 | tmp 12 | vendor 13 | lib/tasks/* 14 | TODO.rdoc 15 | webrat-*.html 16 | -------------------------------------------------------------------------------- /CHANGELOG.rdoc: -------------------------------------------------------------------------------- 1 | == master 2 | 3 | == 0.1.7 / 2009-07-17 4 | 5 | * Added abbreviated predicate methods 6 | * Added and cleaned documentation 7 | 8 | == 0.1.6 / 2009-07-15 9 | 10 | * Refactored 11 | 12 | == 0.1.5 / 2009-07-14 13 | 14 | * Refactored to remove unnecessary class methods from class 15 | * Dynamically load class methods into implementing class object 16 | 17 | == 0.1.4 / 2009-07-14 18 | 19 | * Removed log files from gem build 20 | 21 | == 0.1.3 / 2009-07-14 22 | 23 | * Adds Ingration module supporting Object and ActiveRecord 24 | * Adds extensibility to Integration module 25 | * Adds ActiveRecord Integration 26 | * Improves pluralization -- supports /(sh|ch|x|s)$/, /y$/, /[aeiou]y$/ and +'s' pluralization 27 | 28 | == 0.1.2 / 2009-07-11 29 | 30 | * Added 'is_not' for negated custom method definitions 31 | * Added dynamic creation of 'nil' predicate methods 32 | * Refactored MethodDefinitionDSL 33 | * Added EnumeratedAttribute::MethodDefinition 34 | 35 | == 0.1.1 / 2009-07-09 36 | 37 | * Added enum_attr_reader and enum_attr_writer 38 | * Added option :nil=>true to allow attributes set to nil 39 | * Added #{attr_name}_nil? method for testing nil case 40 | 41 | == 0.1.0 / 2009-07-08 42 | 43 | * Added dynamic predicate method generation 44 | * Added options handling 45 | * Added incrementor and decrementor 46 | * Added enumeration accessor 47 | * Added accessor and enumeration value definition 48 | * Added simple attribute initialization 49 | * Added DSL for short-hand method definition 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006-2009 Jeff Patmon 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Manifest: -------------------------------------------------------------------------------- 1 | enumerated_attribute.gemspec 2 | lib/enumerated_attribute.rb 3 | Manifest 4 | Rakefile 5 | README.rdoc 6 | spec/car.rb 7 | spec/car_spec.rb 8 | spec/tractor.rb 9 | spec/tractor_spec.rb 10 | -------------------------------------------------------------------------------- /NOTES.rdoc: -------------------------------------------------------------------------------- 1 | == Notes 2 | 3 | === Sqlite3 In-memory testing 4 | 5 | * http://www.mathewabonyi.com/articles/2006/11/26/superfast-testing-how-to-in-memory-sqlite3/ 6 | * use gem install sqlite3-ruby --source http://code.whytheluckystiff.net 7 | 8 | 9 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = enumerated_attribute 2 | 3 | Easily code enumerations for your models and expose them as 4 | drop-down lists with the +enum_select+ helper, or use any of +enumerated_attribute+ 5 | features to simplify coding enumerations in any Ruby object. 6 | 7 | == Resources 8 | 9 | Development 10 | 11 | * http://github.com/jeffp/enumerated_attribute 12 | 13 | Source 14 | 15 | * git://github.com/jeffp/enumerated_attribute.git 16 | 17 | Install 18 | 19 | * sudo gem install enumerated_attribute 20 | 21 | == Notice 22 | 23 | * Rails 3 ... dynamic finders working... should be rails3 ready. We are completing form tests. 24 | 25 | * Rails 2.3.8 breaks find_or_create_by_... and find_or_initialize_by_... methods. Write me if you gotta have it... otherwise happy Rails3. 26 | 27 | * Write cleaner code ... implement state patterns for your enumerated attributes (enumerated_state[http://github.com/jeffp/enumerated_state]) 28 | 29 | == How to submit an Issue 30 | 31 | If something needs fixed, please submit issues to this Github project in the Issues tab and 32 | provide a series of FAILING RSPEC tests that I can drop into the current RSpec 33 | test framework and run with little to no coersing. Thanks. 34 | 35 | == Contributors 36 | 37 | * Lailson Bandeira - Rails 3 updates 38 | 39 | == Description 40 | 41 | Typically, in Ruby, enumerated attributes are implemented with strings, symbols or constants. Often the 42 | developer is burdened with repeatedly defining common methods in support of each 43 | attribute. +enumerated_attribute+ provides a DRY implementation for enumerations in Rails. 44 | Repetitive code such as initializers, accessors, predicate and enumeration methods are automatically generated 45 | along with the following features: 46 | 47 | * ActiveRecord integration 48 | * ActionView form helpers 49 | * Scaffold generator integration 50 | * Definable enumeration labels 51 | * Enum helper methods 52 | * Dynamic predicate methods 53 | * Initialization 54 | * State pattern support (enumerated_state[http://github.com/jeffp/enumerated_state]) 55 | 56 | == Setup 57 | 58 | For a Ruby application, install the gem and require it 59 | 60 | require 'enumerated_attribute' 61 | 62 | or for a rails application configure the gem in the config block of the 63 | config/environment.rb file 64 | 65 | config.gem "enumerated_attribute" 66 | 67 | and run the gem install rake task 68 | 69 | rake gems:install 70 | 71 | 72 | == Rails Example 73 | 74 | Here's an example of +enumerated_attribute+ features in a Rails application: 75 | 76 | In the migration, declare your enumeration attributes with +enum+ 77 | 78 | create_table :users, :force=>true do |t| 79 | t.string :first_name 80 | t.enum :gender 81 | t.enum :degree 82 | ... 83 | end 84 | 85 | Define the enumerations in your models with +enum_attr+ 86 | 87 | class User < ActiveRecord::Base 88 | enum_attr :gender, %w(male female) 89 | enum_attr :degree, %w(^none high_school college graduate) 90 | end 91 | 92 | Expose the enumeration in your forms with +enum_select+ 93 | 94 | <% form_for :user do |f| %> 95 | <%= f.label :user %> <%= f.text_field :first_name %>
96 | <%= f.label :gender %> <%= f.enum_select :gender %>
97 | <%= f.label :degree %> <%= f.enum_select :degree %>
98 | <%= submit_tag 'save' %> 99 | <% end %> 100 | 101 | or generate a scaffold with one of your favorite scaffold generators. Currently 102 | supports most scaffold generators including scaffold, wizardly_scaffold, nifty_scaffold, rspec_scaffold, and haml_scaffold. 103 | See the section 'Generating Scaffolds' below. 104 | 105 | The select options text can be customized. See 'Customizing Labels' in the Integration section. 106 | 107 | == Ruby Example 108 | 109 | Here's an example of +enumerated_attribute+ features in a Ruby application: 110 | 111 | require 'enumerated_attribute' 112 | 113 | class Tractor 114 | enum_attr :gear, %w(reverse ^neutral first second over_drive) 115 | 116 | end 117 | 118 | t = Tractor.new 119 | t.gear # => :neutral 120 | t.neutral? # => true 121 | t.gear_next # => :first 122 | t.not_neutral? # => true 123 | t.gear_previous # => :neutral 124 | t.gear = :second # => :second 125 | t.gear_is_not_in_first? # => true 126 | 127 | An explanation of the above features and their usage follows. 128 | 129 | == Usage 130 | 131 | === Defining the Attribute 132 | 133 | Defining an enumerated attribute is as simple as this: 134 | 135 | require 'enumerated_attribute' 136 | 137 | class Tractor 138 | enumerated_attribute :gear, %w(reverse neutral first second over_drive) 139 | 140 | def initialize 141 | @gear = :neutral 142 | end 143 | end 144 | 145 | Notice the plugin +enumerated_attribute+ is required at the top of the code. 146 | The +require+ line must be added at least once at some point in the code. 147 | It is not included in subsequent examples. 148 | 149 | The above code uses +enumerated_attribute+ to define an attribute named 'gear' with five enumeration values. 150 | In general, +enumerated_attribute+ takes three parameters: the name of the attribute, an array of 151 | enumeration values (either symbols or strings), and an optional hash of options (not shown above). The complete 152 | form of +enumerated_attribute+ looks like this: 153 | 154 | enumerated_attribute :name, array_of_enumerations, hash_of_options 155 | 156 | Defining the attribute :gear has done a number things. 157 | It has generated an instance variable '@gear', read/write accessors for the 158 | attribute and support methods 159 | for the enumeration, such as incrementor and decrementor methods. These methods are 160 | demonstrated below using the Tractor class above: 161 | 162 | Tractor.instance_methods(false) 163 | # =>["gear", "gear=", "gears", "gear_next", "gear_previous", ... 164 | 165 | t = Tractor.new 166 | t.gear # => :neutral 167 | t.gear = :reverse # => :reverse 168 | t.gear # => :reverse 169 | t.gear = :third 170 | # => ArgumentError: 'third' is not an enumerated value for gear attribute 171 | 172 | t.gears # => [:reverse, :neutral, :first, :second, :over_drive] 173 | t.gear_next # => :neutral 174 | t.gear_previous # => :reverse 175 | t.gear_previous # => :over_drive 176 | 177 | The plugin has defined +gear+ and gear= accessors for the attribute. They can be used 178 | to set the attribute to one of the defined enumeration values. Attempting to set the 179 | attribute to something besides a defined enumeration value raises an ArgumentError. 180 | 181 | +gear_next+ and +gear_previous+ are incrementors and decrementors of the attribute. 182 | The increment order is based on the order of the enumeration values in the attribute definition. 183 | Both the incrementor and decrementor will wrap when reaching the boundary elements 184 | of the enumeration array. For example: 185 | 186 | t.gear = :second 187 | t.gear_next # => :over_drive 188 | t.gear_next # => :reverse 189 | 190 | 191 | ==== Dynamically-Generating Predicates Methods 192 | 193 | Predicate methods are methods that query the state of the attribute, 194 | for instance, gear_is_neutral? is a predicate method that returns 'true' if 195 | the gear attribute is in the :neutral state. 196 | By default, predicate methods are not predefined, instead, they are dynamically generated. 197 | The plugin will evaluate and respond to methods adhering to a format that it 198 | can associate with an attribute name and one of the attribute's enumeration values. 199 | +enumerated_attribute+ recognizes predicate methods of the following format: 200 | 201 | {attribute name}_{anything}_{enumeration value}? 202 | 203 | The predicate method must satisfy three requirements: it must begin with the name 204 | of the attribute, 205 | it must end with a question mark, and the question mark must be preceded with 206 | a valid enumeration value (all connected by underscores without colons). 207 | So we can write the following two predicate methods without any prior definition and 208 | the plugin will recognize, define and respond to them as demonstrated here: 209 | 210 | t.gear= :neutral 211 | t.gear_is_in_neutral? # => true 212 | t.gear_is_in_reverse? # => false 213 | 214 | The '_is_in_' part of the methods above is merely semantic but enhances 215 | readability. The contents of the {anything} portion is completely 216 | at the discretion of the developer. However, there is one twist. 217 | The evaluation of a predicate method can be negated 218 | by including 'not' in the the middle {anything} section, such as here: 219 | 220 | t.gear_is_not_in_neutral? # => false 221 | t.gear_is_not_in_reverse? # => true 222 | 223 | Basically, the shortest acceptable form of a predicate method is: 224 | 225 | t.gear_neutral? # => true 226 | t.gear_not_neutral? # => false 227 | 228 | 229 | ==== Abbreviating Predicate Methods 230 | 231 | In the case that an enumeration value is associated with only one 232 | attribute, the attribute name can be left out of the predicate method name. 233 | The plugin will infer the attribute from the enum value in the method name. 234 | The abbreviate format can be written like this: 235 | 236 | {anything}{_}{enumeration value}? 237 | 238 | And results in the following possibilities: 239 | 240 | t.gear = :neutral 241 | t.neutral? # => true 242 | t.is_neutral? # => true 243 | t.not_neutral? # => false 244 | t.is_not_neutral? # => false 245 | 246 | Calling the abbreviated form of the method containing an enumeration value 247 | belonging to two or more attributes throws an AmbiguousMethod error. 248 | 249 | 250 | ==== Initializing Attributes 251 | 252 | The plugin provides a few ways to eliminate setting the initial value of the attribute in 253 | the +initialize+ method. Two ways are demonstrated here: 254 | 255 | class Tractor 256 | enum_attr :gear, %w(reverse ^neutral first second third) 257 | enum_attr :front_light, %w(off low high), :init=>:off 258 | end 259 | 260 | t = Tractor.new 261 | t.gear # => :neutral 262 | t.front_light # => :off 263 | 264 | *Note* +enumerated_attribute+ can be abbreviated to +enum_attr+. The abbreviated 265 | form will be used in subsequent examples. 266 | 267 | The first and simplest way involves designating the initial value by 268 | prepending a carot '^' to one of the enumeration values in the definition. 269 | The plugin recognizes that the gear attribute is to be initialized to :neutral. 270 | Alternatively, the :init option can be used to indicate the initial value of the attribute. 271 | 272 | 273 | ==== Setting Attributes to nil 274 | 275 | By default, the attribute setter allows nils unless the :nil option is set to false. 276 | When :nil is set to false, the attribute may initialize to nil, but may not be set 277 | to nil thereafter. 278 | 279 | class Tractor 280 | enum_attr :plow, %w(up down), :nil=>false 281 | end 282 | 283 | t = Tractor.new 284 | t.plow # => nil 285 | t.plow_nil? # => true 286 | t.plow = :up # => :up 287 | t.plow_is_nil? # => false 288 | t.plow_is_not_nil? # => true 289 | t.plow = nil # => raises error 290 | 291 | Regardless of the :nil option setting, the plugin can dynamically recognize and define 292 | predicate methods for testing 'nil' values. The setter methods also treat empty 293 | strings (or '') as nil values. 294 | 295 | 296 | ==== Changing Method Names 297 | 298 | The plugin provides options for changing the method names of the enumeration accessor, incrementor 299 | and decrementor (ie, +gears+, +gear_next+, +gear_previous+): 300 | 301 | class Tractor 302 | enum_attr :lights, %w(^off low high), :plural=>:lights_values, 303 | :inc=>'lights_inc', :dec=>'lights_dec' 304 | end 305 | 306 | t = Tractor.new 307 | t.lights_values # => [:off, :low, :high] 308 | t.lights_inc # => :low 309 | t.lights_dec # => :off 310 | 311 | By default, the plugin uses the plural of the attribute for the accessor method name of the enumeration 312 | values. The pluralization uses a simple algorithm which does not support irregular forms. In 313 | the case of 'lights' as an 314 | attribute, the default pluralization does not work, so the accessor can be changed using 315 | the :plural option. Likewise, the decrementor 316 | and incrementor have options :decrementor and :incrementor, or :inc and :dec, for changing 317 | their method names. 318 | 319 | 320 | === Defining Other Methods 321 | 322 | In the case that other methods are required to support the attribute, 323 | the plugin provides a short-hand for defining these methods in the 324 | +enumerated_attribute+ block. 325 | 326 | class Tractor 327 | enum_attr :gear, %w(reverse ^neutral first second over_drive) do 328 | parked? :neutral 329 | driving? [:first, :second, :over_drive] 330 | end 331 | end 332 | 333 | t = Tractor.new 334 | t.parked? # => true 335 | t.driving? # => false 336 | 337 | Two predicate methods are defined for the 'gear' attribute in the above example using 338 | the plugin's short-hand. 339 | The first method, parked?, defines a method which evaluates 340 | the code {@gear == :neutral}. The second method, driving?, evaluates 341 | to true if the attribute is set to one of the enumeration values defined in the array 342 | [:first, :second, :over_drive]. 343 | 344 | The same short-hand can be used to define methods where the attribute 'is not' equal to the 345 | indicated value or 'is not' included in the array of values. 346 | 347 | class Tractor 348 | enum_attr :gear, %w(reverse ^neutral first second over_drive) do 349 | not_parked? is_not :neutral 350 | not_driving? is_not [:first, :second, :over_drive] 351 | end 352 | end 353 | 354 | 355 | ==== Defining Other Methods With Blocks 356 | 357 | For predicate methods requiring fancier logic, 358 | a block can be used to define the method body. 359 | 360 | class Tractor 361 | enum_attr :gear, %w(reverse ^neutral first second over_drive) do 362 | parked? :neutral 363 | driving? [:first, :second, :over_drive] 364 | end 365 | enum_attr :plow, %w(^up down), :plural=>:plow_values do 366 | plowing? { self.gear_is_in_first? && @plow == :down } 367 | end 368 | end 369 | 370 | Here, a method plowing? is true if the gear attribute equates to :first 371 | and the plow attribute is set to :down. There is 372 | no short-hand for the block. The code must be complete and evaluate in 373 | the context of the instance. 374 | 375 | Method definitions are not limited to predicate methods. Other methods 376 | can be defined to manipulate the attributes. Here, two methods are defined acting 377 | as bounded incrementor and decrementor of the gear attribute. 378 | 379 | class Tractor 380 | enum_attr :gear, %w(reverse ^neutral first second over_drive) do 381 | parked? :neutral 382 | driving? [:first, :second, :over_drive] 383 | upshift { self.gear_is_in_over_drive? ? self.gear : self.gear_next } 384 | downshift { self.driving? ? self.gear_previous : self.gear } 385 | end 386 | end 387 | 388 | t = Tractor.new 389 | t.gear # => :neutral 390 | 10.times { t.upshift } 391 | t.gear # => :over_drive 392 | 10.times { t.downshift } 393 | t.gear # => :neutral 394 | 395 | Methods +upshift+ and +downshift+ use the automatically generated 396 | incrementor and decrementor as 397 | well as a couple predicate methods. +upshift+ increments the gear attribute until 398 | it reaches over_drive and does not allow a wrap around. +downshift+ decrements 399 | until the attribute reaches neutral. 400 | 401 | === Integration 402 | 403 | ==== ActiveRecord integration 404 | 405 | The plugin can be used with ActiveRecord. Enumerated attributes may be declared on 406 | column attributes or as independent enumerations. Declaring an enumerated attribute 407 | on a column attribute will enforce the enumeration using symbols. The 408 | enumerated column attribute must be declared as a STRING in the database schema. 409 | The enumerated attribute will be stored as a string but retrieved in code as a symbol. The 410 | enumeration functionality is consistent across integrations. 411 | 412 | require 'enumerated_attribute' 413 | require 'active_record' 414 | 415 | class Order < ActiveRecord::Base 416 | enum_attr :status, %w(^hold, processing, delayed, shipped) 417 | enum_attr :billing_status, 418 | %w(^unauthorized, authorized, auth_failed, captured, capture_failed, closed) 419 | end 420 | 421 | o = Order.new 422 | o.status # => :hold 423 | o.billing_status # => :unauthorized 424 | o.save! 425 | 426 | o = Order.new(:invoice=>'43556334-W84', :status=>:processing, :billing=>:authorized) 427 | o.save! 428 | o.status # => :processing 429 | o.invoice # => "43556334-W84" 430 | 431 | 432 | ==== Labels 433 | 434 | Each enumeration value has a corresponding text label. The defaults are made 435 | up from the enumeration symbols. For the Tractor class example: 436 | 437 | t=Tractor.new 438 | t.enums(:gear) # => [:reverse, :neutral, :first, :second, :over_drive] 439 | t.enums(:gear).labels # => ['Reverse', 'Neutral', 'First', 'Second', 'Over drive'] 440 | 441 | The +enums(:attribute)+ method provides information about the attribute's enumerations. 442 | It is the same as the plural form of the attribute name. There are several kinds 443 | of information available from the +enums+ method. 444 | 445 | t=Tractor.new 446 | e = t.enums(:plow) # => [:up, :down] 447 | e.labels # => ['Up', 'Down'] 448 | e.hash # => {:up=>'Up', :down=>'Down'} 449 | e.select_options # => [['Up', 'up'], ['Down', 'down']] 450 | e.label(:up) # => 'Up' 451 | 452 | ==== Customizing Labels 453 | 454 | Labels can be customized as shown here: 455 | 456 | class User < ActiveRecord::Base 457 | enum_attr :contact_options, %w(none phone email mail) do 458 | label :none=>'Please do not contact me' 459 | label :phone=>'I would like a representative to call me' 460 | label :email=>'I would like information via email' 461 | label :mail=>'I would like information mailed to me' 462 | end 463 | end 464 | 465 | Likewise, the labels can be provided on the same line 466 | 467 | class Tractor 468 | enum_attr :gear, %w(reverse ^neutral first second over_drive) do 469 | labels :first=>'1st Gear', :second=>'2nd Gear', :over_drive=>'Over Drive' 470 | end 471 | end 472 | 473 | ==== View Helpers 474 | 475 | There are two +enum_select+ helpers, one for use with +form_for+ and one for use without 476 | it. An example for form_for was given in the examples at the beginning. Here's an 477 | example with the +form_tag+ and a @user object. 478 | 479 | <% form_tag :action=>:register do %> 480 | <%= label_tag 'First name' %>: <%= text_field :user, :first_name %>
481 | <%= label_tag 'Gender' %>: <%= enum_select :user, :gender %>
482 | <%= label_tag 'Degree' %>: <%= enum_select :user, :degree %>
483 | ... 484 | <%= submit_tag 'Register' %> 485 | <% end %> 486 | 487 | ==== Generating Scaffolds 488 | 489 | You can generate views with enumerations using your favorite scaffold generator. Currently 490 | supports most scaffold generators including scaffold, wizardly_scaffold, nifty_scaffold, rspec_scaffold and haml_scaffold. 491 | For most scaffolds there are two steps. First, generate the scaffold views and 492 | migrations using the 'enum' type for enumerations 493 | 494 | ./script/generate scaffold contractor name:string gender:enum age:integer status:enum 495 | 496 | Second, do not forget to add the +enum_attr+ macro to the generated model and migrate the database 497 | 498 | class Contractor < ActiveRecord::Base 499 | enum_attr :gender, %w(male female) 500 | enum_attr :status, %w(available unavailable) 501 | end 502 | 503 | === Formtastic integration 504 | 505 | You can display the select input in a Formtastic form with a little monkey patching. First, extend Formtastic with an initializer: 506 | 507 | require 'formtastic' 508 | 509 | module Formtastic #:nodoc: 510 | module Inputs #:nodoc: 511 | class EnumInput < SelectInput #:nodoc: 512 | def to_html 513 | unless options[:collection] 514 | enum = @object.enums(method.to_sym) 515 | choices = enum ? enum.select_options : [] 516 | options[:collection] = choices 517 | end 518 | if (value = @object.__send__(method.to_sym)) 519 | options[:selected] ||= value.to_s 520 | else 521 | options[:include_blank] ||= true 522 | end 523 | super 524 | end 525 | end 526 | end 527 | end 528 | 529 | Then specify the input type as enum in the forms: 530 | 531 | form.input :gear, :as => :enum 532 | 533 | === Implementation Notes 534 | 535 | ==== New and Method_missing methods 536 | 537 | The plugin chains both the 'new' and the 'method_missing' methods. Any 'new' and 'method_missing' 538 | implementations in the same class declaring an enumerated_attribute should come before the 539 | declaration; otherwise, the 'new' and 'method_missing' implementations must chain in order to avoid 540 | overwriting the plugin's methods. The best approach is shown here: 541 | 542 | class Soup 543 | def self.new(*args) 544 | ... 545 | end 546 | 547 | private 548 | def method_missing(methId, *args, &blk) 549 | ... 550 | end 551 | 552 | enum_attr temp:, %w(cold warm hot boiling) 553 | end 554 | 555 | ==== ActiveRecord 556 | 557 | ActiveRecord's write_attribute and read_attribute methods do not support symbols for enumerated attributes. 558 | 559 | 560 | == Testing 561 | 562 | The plugin uses jeweler, RSpec, and Webrat for testing. Make sure you have these gems installed: 563 | 564 | gem install rspec webrat jeweler 565 | 566 | To test the plugin for regular ruby objects, run: 567 | 568 | rake spec:object 569 | 570 | Testing ActiveRecord integration requires the install of Sqlite3 and the 571 | sqlite3-ruby gem. To test ActiveRecord, run: 572 | 573 | rake spec:ar 574 | 575 | And for testing +enum_select+ in form views: 576 | 577 | rake spec:forms 578 | 579 | To test all specs: 580 | 581 | rake spec:all 582 | 583 | 584 | == Dependencies 585 | 586 | * ActiveRecord (but not required) 587 | * Sqlite3 and sqlite3-ruby gem (for testing) 588 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #require 'rake/testtask' 2 | require 'rdoc/task' 3 | require 'rubygems/package_task' 4 | require 'rake/contrib/sshpublisher' 5 | 6 | require 'jeweler' 7 | Jeweler::Tasks.new do |s| 8 | s.name = 'enumerated_attribute' 9 | s.summary = 'Enumerated model attributes and view helpers' 10 | s.description = 'Add enumerated attributes to your models and expose them in drop-down lists in your views' 11 | 12 | s.add_dependency('meta_programming', '>= 0.2.1') 13 | 14 | exclude_folders = 'spec/rails/{doc,lib,log,nbproject,tmp,vendor,test}' 15 | exclude_files = FileList['**/*.log'] + FileList[exclude_folders+'/**/*'] + FileList[exclude_folders] 16 | s.files = FileList['{examples,lib,tasks,spec}/**/*'] + %w(CHANGELOG.rdoc init.rb LICENSE Rakefile README.rdoc .gitignore) - exclude_files 17 | s.require_path = 'lib' 18 | s.has_rdoc = true 19 | s.test_files = Dir['spec/*_spec.rb'] 20 | 21 | s.author = ['Jeff Patmon', 'Turadg Aleahmad'] 22 | s.email = ['jpatmon@gmail.com', "turadg@aleahmad.net"] 23 | s.homepage = 'http://github.com/jeffp/enumerated_attribute/' 24 | 25 | s 26 | end 27 | 28 | Jeweler::GemcutterTasks.new 29 | 30 | 31 | require "rspec/core/rake_task" # RSpec 2.0 32 | 33 | desc "Run specs" 34 | 35 | namespace :spec do 36 | task :default => :all 37 | RSpec::Core::RakeTask.new(:object) do |t| 38 | t.pattern = 'spec/*_spec.rb' 39 | # t.libs << 'lib' << 'spec' 40 | t.rcov = false 41 | t.rspec_opts = ['--options', 'spec/spec.opts'] 42 | #t.rcov_dir = 'coverage' 43 | #t.rcov_opts = ['--exclude', "kernel,load-diff-lcs\.rb,instance_exec\.rb,lib/spec.rb,lib/spec/runner.rb,^spec/*,bin/spec,examples,/gems,/Library/Ruby,\.autotest,#{ENV['GEM_HOME']}"] 44 | end 45 | desc "Run ActiveRecord integration specs" 46 | RSpec::Core::RakeTask.new(:ar) do |t| 47 | t.pattern = 'spec/active_record/*_spec.rb' 48 | # t.libs << 'lib' << 'spec/active_record' 49 | t.rspec_opts = ['--options', 'spec/spec.opts'] 50 | t.rcov = false 51 | end 52 | RSpec::Core::RakeTask.new(:forms) do |t| 53 | t.pattern = 'spec/rails/spec/integrations/*_spec.rb' 54 | # t.libs << 'lib' << 'spec/rails/spec' 55 | t.rspec_opts = ['--options', 'spec/spec.opts'] 56 | t.rcov = false 57 | end 58 | desc "Run all specs" 59 | task :all=>[:object, :ar, :forms] 60 | end 61 | 62 | 63 | desc "Generate documentation for the 'enumerated_attribute' plugin." 64 | Rake::RDocTask.new(:rdoc) do |rdoc| 65 | rdoc.rdoc_dir = 'rdoc' 66 | # rdoc.title = spec.name 67 | #rdoc.template = '../rdoc_template.rb' 68 | rdoc.options << '--line-numbers' << '--inline-source' 69 | rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG.rdoc', 'LICENSE', 'lib/**/*.rb') 70 | end 71 | 72 | Dir['tasks/**/*.rake'].each {|rake| load rake} 73 | -------------------------------------------------------------------------------- /VERSION.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :major: 0 3 | :minor: 3 4 | :patch: 0 5 | :build: 'beta1' 6 | -------------------------------------------------------------------------------- /WISHLIST: -------------------------------------------------------------------------------- 1 | 2 | #todo: is_not/is -- test raised errors contain useful info on invalid syntax 3 | #todo: system wide constants 4 | #todo: setter_callback 5 | #todo: attribute methods gear.enums, gear.inc, gear.dec 6 | #todo: display strings for enum values 7 | -------------------------------------------------------------------------------- /enumerated_attribute.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' 4 | # -*- encoding: utf-8 -*- 5 | 6 | Gem::Specification.new do |s| 7 | s.name = "enumerated_attribute" 8 | s.version = "0.3.0.beta1" 9 | 10 | s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version= 11 | s.authors = ["Jeff Patmon", "Turadg Aleahmad"] 12 | s.date = "2011-09-02" 13 | s.description = "Add enumerated attributes to your models and expose them in drop-down lists in your views" 14 | s.email = ["jpatmon@gmail.com", "turadg@aleahmad.net"] 15 | s.extra_rdoc_files = [ 16 | "LICENSE", 17 | "README.rdoc" 18 | ] 19 | s.files = [ 20 | ".gitignore", 21 | "CHANGELOG.rdoc", 22 | "LICENSE", 23 | "README.rdoc", 24 | "Rakefile", 25 | "init.rb", 26 | "lib/enumerated_attribute.rb", 27 | "lib/enumerated_attribute/attribute.rb", 28 | "lib/enumerated_attribute/attribute/arguments.rb", 29 | "lib/enumerated_attribute/attribute/attribute_descriptor.rb", 30 | "lib/enumerated_attribute/attribute/class_methods.rb", 31 | "lib/enumerated_attribute/attribute/instance_methods.rb", 32 | "lib/enumerated_attribute/integrations.rb", 33 | "lib/enumerated_attribute/integrations/active_record.rb", 34 | "lib/enumerated_attribute/integrations/datamapper.rb", 35 | "lib/enumerated_attribute/integrations/default.rb", 36 | "lib/enumerated_attribute/integrations/object.rb", 37 | "lib/enumerated_attribute/method_definition_dsl.rb", 38 | "lib/enumerated_attribute/rails_helpers.rb", 39 | "spec/active_record/active_record_spec.rb", 40 | "spec/active_record/association_test_classes.rb", 41 | "spec/active_record/associations_spec.rb", 42 | "spec/active_record/cfg.rb", 43 | "spec/active_record/df.rb", 44 | "spec/active_record/inheritance_classes.rb", 45 | "spec/active_record/inheritance_spec.rb", 46 | "spec/active_record/race_car.rb", 47 | "spec/active_record/single_table_inheritance_spec.rb", 48 | "spec/active_record/sti_classes.rb", 49 | "spec/active_record/sti_spec.rb", 50 | "spec/active_record/test_in_memory.rb", 51 | "spec/car.rb", 52 | "spec/cfg.rb", 53 | "spec/inheritance_classes.rb", 54 | "spec/inheritance_spec.rb", 55 | "spec/new_and_method_missing_spec.rb", 56 | "spec/plural.rb", 57 | "spec/poro_spec.rb", 58 | "spec/rails/README", 59 | "spec/rails/Rakefile", 60 | "spec/rails/app/controllers/application_controller.rb", 61 | "spec/rails/app/controllers/form_test_controller.rb", 62 | "spec/rails/app/helpers/application_helper.rb", 63 | "spec/rails/app/helpers/form_test_helper.rb", 64 | "spec/rails/app/models/user.rb", 65 | "spec/rails/app/views/form_test/form.html.erb", 66 | "spec/rails/app/views/form_test/form_for.html.erb", 67 | "spec/rails/app/views/form_test/form_tag.html.erb", 68 | "spec/rails/app/views/form_test/index.html.erb", 69 | "spec/rails/app/views/layouts/application.html.erb", 70 | "spec/rails/config/boot.rb", 71 | "spec/rails/config/database.yml", 72 | "spec/rails/config/environment.rb", 73 | "spec/rails/config/environments/development.rb", 74 | "spec/rails/config/environments/production.rb", 75 | "spec/rails/config/environments/test.rb", 76 | "spec/rails/config/initializers/backtrace_silencers.rb", 77 | "spec/rails/config/initializers/inflections.rb", 78 | "spec/rails/config/initializers/mime_types.rb", 79 | "spec/rails/config/initializers/new_rails_defaults.rb", 80 | "spec/rails/config/initializers/session_store.rb", 81 | "spec/rails/config/locales/en.yml", 82 | "spec/rails/config/routes.rb", 83 | "spec/rails/db/development.sqlite3", 84 | "spec/rails/db/migrate/20090804230221_create_sessions.rb", 85 | "spec/rails/db/migrate/20090804230546_create_users.rb", 86 | "spec/rails/db/schema.rb", 87 | "spec/rails/db/test.sqlite3", 88 | "spec/rails/public/404.html", 89 | "spec/rails/public/422.html", 90 | "spec/rails/public/500.html", 91 | "spec/rails/public/favicon.ico", 92 | "spec/rails/public/images/rails.png", 93 | "spec/rails/public/index.html", 94 | "spec/rails/public/javascripts/application.js", 95 | "spec/rails/public/javascripts/controls.js", 96 | "spec/rails/public/javascripts/dragdrop.js", 97 | "spec/rails/public/javascripts/effects.js", 98 | "spec/rails/public/javascripts/prototype.js", 99 | "spec/rails/public/robots.txt", 100 | "spec/rails/public/stylesheets/scaffold.css", 101 | "spec/rails/script/about", 102 | "spec/rails/script/autospec", 103 | "spec/rails/script/console", 104 | "spec/rails/script/dbconsole", 105 | "spec/rails/script/destroy", 106 | "spec/rails/script/generate", 107 | "spec/rails/script/performance/benchmarker", 108 | "spec/rails/script/performance/profiler", 109 | "spec/rails/script/plugin", 110 | "spec/rails/script/runner", 111 | "spec/rails/script/server", 112 | "spec/rails/script/spec", 113 | "spec/rails/script/spec_server", 114 | "spec/rails/spec/controllers/form_test_controller_spec.rb", 115 | "spec/rails/spec/integrations/enum_select_spec.rb", 116 | "spec/rails/spec/matchers.rb", 117 | "spec/rails/spec/rcov.opts", 118 | "spec/rails/spec/spec.opts", 119 | "spec/rails/spec/spec_helper.rb", 120 | "spec/rails/spec/views/form_test/form.html.erb_spec.rb", 121 | "spec/rails/spec/views/form_test/form_for.html.erb_spec.rb", 122 | "spec/rails/spec/views/form_test/form_tag.html.erb_spec.rb", 123 | "spec/rcov.opts", 124 | "spec/spec.opts", 125 | "spec/tractor.rb" 126 | ] 127 | s.homepage = "http://github.com/jeffp/enumerated_attribute/" 128 | s.require_paths = ["lib"] 129 | s.rubygems_version = "1.8.10" 130 | s.summary = "Enumerated model attributes and view helpers" 131 | s.test_files = ["spec/inheritance_spec.rb", "spec/new_and_method_missing_spec.rb", "spec/poro_spec.rb"] 132 | 133 | if s.respond_to? :specification_version then 134 | s.specification_version = 3 135 | 136 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 137 | s.add_runtime_dependency(%q, [">= 0.2.1"]) 138 | else 139 | s.add_dependency(%q, [">= 0.2.1"]) 140 | end 141 | else 142 | s.add_dependency(%q, [">= 0.2.1"]) 143 | end 144 | end 145 | 146 | -------------------------------------------------------------------------------- /init.rb: -------------------------------------------------------------------------------- 1 | require 'enumerated_attribute' 2 | -------------------------------------------------------------------------------- /lib/enumerated_attribute.rb: -------------------------------------------------------------------------------- 1 | gem 'meta_programming', '>= 0.2.0' 2 | require 'meta_programming' 3 | require 'enumerated_attribute/attribute' 4 | 5 | module EnumeratedAttribute 6 | 7 | module MacroMethods 8 | 9 | def enumerated_attribute(*args, &block) 10 | class << self 11 | include EnumeratedAttribute::Attribute 12 | end 13 | create_enumerated_attribute(*args, &block) 14 | end 15 | alias_method :enum_attr, :enumerated_attribute 16 | 17 | end 18 | 19 | end 20 | 21 | Class.class_eval do 22 | include EnumeratedAttribute::MacroMethods 23 | end 24 | 25 | -------------------------------------------------------------------------------- /lib/enumerated_attribute/attribute.rb: -------------------------------------------------------------------------------- 1 | require 'enumerated_attribute/attribute/attribute_descriptor' 2 | require 'enumerated_attribute/attribute/arguments' 3 | require 'enumerated_attribute/attribute/instance_methods' 4 | require 'enumerated_attribute/attribute/class_methods' 5 | require 'enumerated_attribute/method_definition_dsl' 6 | require 'enumerated_attribute/integrations' 7 | require 'enumerated_attribute/rails_helpers' 8 | require 'ostruct' 9 | 10 | module EnumeratedAttribute 11 | 12 | class EnumeratedAttributeError < StandardError; end 13 | class IntegrationError < EnumeratedAttributeError; end 14 | class InvalidEnumeration < EnumeratedAttributeError; end 15 | class InvalidDefinition < EnumeratedAttributeError; end 16 | class AmbiguousMethod < EnumeratedAttributeError; end 17 | 18 | module Attribute 19 | private 20 | 21 | def create_enumerated_attribute(*args, &block) 22 | return if args.empty? 23 | config = Arguments.parse_enum_attr_arguments(args) 24 | 25 | class_eval do 26 | @enumerated_attributes ||= {} 27 | @enumerated_attributes[config.attr_symbol] = AttributeDescriptor.new(config.attr_symbol, config.enums, config.opts) 28 | 29 | unless @integration_map 30 | @integration_map = Integrations.find_integration_map(self) 31 | @integration_map[:aliasing].each do |p| 32 | alias_method(p.first, p.last) 33 | end 34 | include(EnumeratedAttribute::Integrations::Default) 35 | include(@integration_map[:module]) if @integration_map[:module] 36 | 37 | self.extend ClassMethods 38 | include InstanceMethods 39 | end 40 | end 41 | 42 | #create accessors 43 | define_enumerated_attribute_reader_method(config.attr_symbol) unless (config.opts.key?(:reader) && !config.opts[:reader]) 44 | define_enumerated_attribute_writer_method(config.attr_symbol) unless (config.opts.key?(:writer) && !config.opts[:writer]) 45 | define_enumerated_attribute_new_method 46 | 47 | #create state and action methods from block 48 | config.initial_value = config.opts[:init] || config.initial_value 49 | if block_given? 50 | m = EnumeratedAttribute::MethodDefinitionDSL.new(self, self.enumerated_attributes(false)[config.attr_symbol]) #attr_name, enums) 51 | m.instance_eval(&block) 52 | config.initial_value = m.initial_value || config.initial_value 53 | config.plural_name = m.pluralized_name || config.plural_name 54 | config.decrementor = m.decrementor_name || config.decrementor 55 | config.incrementor = m.incrementor_name || config.incrementor 56 | end 57 | 58 | #define the enum values accessor 59 | class_eval do 60 | @enumerated_attributes ||={} 61 | @enumerated_attributes[config.attr_symbol].init_value = config.initial_value if config.initial_value 62 | 63 | define_method(config.plural_name.to_sym) { self.class.enumerated_attributes[config.attr_symbol]} 64 | define_method(config.incrementor.to_sym) do 65 | z = self.class.enumerated_attributes[config.attr_symbol].enums 66 | index = z.index(read_enumerated_attribute(config.attr_symbol)) 67 | write_enumerated_attribute(config.attr_symbol, z[index >= z.size-1 ? 0 : index+1]) 68 | end 69 | define_method(config.decrementor.to_sym) do 70 | z = self.class.enumerated_attributes[config.attr_symbol].enums 71 | index = z.index(read_enumerated_attribute(config.attr_symbol)) 72 | write_enumerated_attribute(config.attr_symbol, z[index > 0 ? index-1 : z.size-1]) 73 | end 74 | end 75 | refresh_enumerated_attributes 76 | end 77 | end 78 | 79 | end 80 | -------------------------------------------------------------------------------- /lib/enumerated_attribute/attribute/arguments.rb: -------------------------------------------------------------------------------- 1 | module EnumeratedAttribute 2 | module Attribute 3 | module Arguments 4 | def self.validate_enum_attr_arguments(config) 5 | raise(InvalidDefinition, 'second argument of enumerated_attribute/enum_attr is not an array of symbols or strings representing the enum values', caller) if config.enums.empty? 6 | end 7 | def self.init_incrementor_decrementor_method_names(config) 8 | config.incrementor = config.opts[:incrementor] || config.opts[:inc] || "#{config.attr_name}_next" 9 | config.decrementor = config.opts[:decrementor] || config.opts[:dec] || "#{config.attr_name}_previous" 10 | end 11 | def self.init_plural_name(config) 12 | config.plural_name = config.opts[:plural] || config.opts[:enums_accessor] || config.opts[:enums] || begin 13 | case 14 | when config.attr_name =~ /[aeiou]y$/ 15 | "#{config.attr_name}s" 16 | when config.attr_name =~ /y$/ 17 | config.attr_name.sub(/y$/, 'ies') 18 | when config.attr_name =~ /(sh|ch|x|s)$/ 19 | "#{config.attr_name}es" 20 | else 21 | "#{config.attr_name}s" 22 | end 23 | end 24 | end 25 | def self.process_enums_for_initial_value(config) 26 | config.initial_value = nil 27 | config.enums = config.enums.map{|v| (v =~ /^\^/ ? (config.initial_value ||= v[1, v.length-1].to_sym) : v.to_sym )} 28 | end 29 | 30 | def self.parse_enum_attr_arguments(args) 31 | config = OpenStruct.new 32 | config.attr_name = args[0].to_s 33 | config.attr_symbol = config.attr_name.to_sym 34 | config.enums = (args[1] && args[1].is_a?(Array) ? args[1] : []) 35 | index = config.enums.empty? ? 1 : 2 36 | config.opts = (args[index] && args[index].is_a?(Hash) ? args[index] : {}) 37 | 38 | validate_enum_attr_arguments(config) 39 | init_plural_name(config) 40 | init_incrementor_decrementor_method_names(config) 41 | process_enums_for_initial_value(config) 42 | 43 | config 44 | end 45 | end 46 | end 47 | 48 | end -------------------------------------------------------------------------------- /lib/enumerated_attribute/attribute/attribute_descriptor.rb: -------------------------------------------------------------------------------- 1 | 2 | module EnumeratedAttribute 3 | module Attribute 4 | class AttributeDescriptor < Array 5 | attr_reader :name 6 | attr_accessor :init_value 7 | 8 | def initialize(name, enums=[], opts={}) 9 | super enums 10 | @name = name 11 | @options = opts 12 | @labels_hash = Hash[*self.collect{|e| [e, e.to_s.gsub(/_/, ' ').squeeze(' ').capitalize]}.flatten] 13 | end 14 | 15 | def allows_nil? 16 | @options.key?(:nil) ? @options[:nil] : true 17 | end 18 | def allows_value?(value) 19 | self.include?(value.to_sym) 20 | end 21 | 22 | def enums 23 | self 24 | end 25 | def label(value) 26 | @labels_hash[value] 27 | end 28 | def labels 29 | @labels_array ||= self.map{|e| @labels_hash[e]} 30 | end 31 | def hash 32 | @labels_hash 33 | end 34 | def select_options 35 | @select_options ||= self.map{|e| [@labels_hash[e], e.to_s]} 36 | end 37 | 38 | def set_label(enum_value, label_string) 39 | reset_labels 40 | @labels_hash[enum_value.to_sym] = label_string 41 | end 42 | 43 | protected 44 | def reset_labels 45 | @labels_array = nil 46 | @select_options = nil 47 | end 48 | 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/enumerated_attribute/attribute/class_methods.rb: -------------------------------------------------------------------------------- 1 | module EnumeratedAttribute 2 | module Attribute 3 | module ClassMethods 4 | def refresh_enumerated_attributes 5 | @all_enumerated_attributes_cache = nil 6 | end 7 | def enumerated_attributes(all=true) 8 | return @enumerated_attributes unless all 9 | return @all_enumerated_attributes_cache if @all_enumerated_attributes_cache 10 | @all_enumerated_attributes_cache = @enumerated_attributes ? @enumerated_attributes.dup : {} 11 | klass = self.superclass 12 | while (klass) 13 | if (klass.respond_to?(:enumerated_attributes)) 14 | if (sub_enums = klass.enumerated_attributes) 15 | @all_enumerated_attributes_cache = sub_enums.merge @all_enumerated_attributes_cache 16 | break 17 | end 18 | end 19 | klass = klass.superclass 20 | end 21 | @all_enumerated_attributes_cache 22 | end 23 | 24 | def has_enumerated_attribute?(name) 25 | !name.nil? && !!self.enumerated_attributes.key?(name.to_sym) 26 | end 27 | def enumerated_attribute_allows_nil?(name) 28 | (descriptor = self.enumerated_attributes[name.to_sym]) && descriptor.allows_nil? 29 | end 30 | def enumerated_attribute_allows_value?(name, value) 31 | return (false) unless (descriptor = self.enumerated_attributes[name.to_sym]) 32 | return descriptor.allows_nil? if (value == nil || value == '') 33 | descriptor.allows_value?(value) 34 | end 35 | 36 | def define_enumerated_attribute_custom_method(symbol, attr_name, value, negated) 37 | define_method symbol do 38 | ival = read_enumerated_attribute(attr_name) 39 | negated ? ival != value : ival == value 40 | end 41 | end 42 | 43 | end 44 | end 45 | end -------------------------------------------------------------------------------- /lib/enumerated_attribute/attribute/instance_methods.rb: -------------------------------------------------------------------------------- 1 | module EnumeratedAttribute 2 | module Attribute 3 | module InstanceMethods 4 | def self.included(base) 5 | 6 | method_missing_suffix = "enumerated_attribute_#{base.name.gsub(/.+::/,'')}_#{base.hash.abs}".to_sym 7 | define_method("method_missing_with_#{method_missing_suffix}") do |methId, *args| 8 | return self.__send__(methId) if define_enumerated_attribute_dynamic_method(methId) 9 | self.__send__("method_missing_without_#{method_missing_suffix}", methId, *args) 10 | end 11 | 12 | respond_to_suffix = "enumerated_attribute_#{base.name.gsub(/.+::/,'')}_#{base.hash.abs}".to_sym 13 | base.class_eval %{ 14 | def respond_to_with_#{respond_to_suffix}?(method, include_private=false) 15 | self.respond_to_without_#{respond_to_suffix}?(method, include_private) || 16 | (!!parse_dynamic_method_parts!(method.to_s) rescue false) 17 | end 18 | }, __FILE__, __LINE__ 19 | 20 | base.safe_alias_method_chain :method_missing, method_missing_suffix 21 | base.safe_alias_method_chain :respond_to?, respond_to_suffix 22 | 23 | end 24 | 25 | def enums(attr) 26 | self.class.enumerated_attributes[attr.to_sym] 27 | end 28 | 29 | 30 | def initialize_enumerated_attributes(only_if_nil = false) 31 | self.class.enumerated_attributes.each do |k,v| 32 | self.write_enumerated_attribute(k, v.init_value) unless (only_if_nil && read_enumerated_attribute(k) != nil) 33 | end 34 | end 35 | 36 | private 37 | 38 | def parse_dynamic_method_parts!(meth_name) 39 | return(nil) unless meth_name[-1, 1] == '?' 40 | 41 | middle = meth_name.chop #remove the ? 42 | 43 | attr = nil 44 | self.class.enumerated_attributes.keys.each do |name| 45 | if middle.sub!(Regexp.new("^"+name.to_s), "") 46 | attr = name; break 47 | end 48 | end 49 | 50 | value = nil 51 | attr_sym = attr ? attr.to_sym : nil 52 | if (descriptor = self.class.enumerated_attributes[attr_sym]) 53 | descriptor.enums.each do |v| 54 | if middle.sub!(Regexp.new(v.to_s+"$"), "") 55 | value = v; break 56 | end 57 | end 58 | else 59 | #search through enum values one at time and identify any ambiguities 60 | self.class.enumerated_attributes.each do |attr_key,descriptor| 61 | descriptor.enums.each do|v| 62 | if middle.match(v.to_s+"$") 63 | raise(AmbiguousMethod, meth_name+" is ambiguous, use something like "+attr_sym.to_s+(middle[0,1]=='_'? '' : '_')+middle+"? or "+attr_key.to_s+(middle[0,1]=='_'? '' : '_')+middle+"?", caller) if attr_sym 64 | attr_sym = attr_key 65 | value = v 66 | end 67 | end 68 | end 69 | return (nil) unless attr_sym 70 | attr = attr_sym.to_s 71 | middle.sub!(Regexp.new(value.to_s+"$"), "") 72 | end 73 | 74 | unless value #check for nil? 75 | return (nil) unless middle.sub!(Regexp.new('nil$'), "") 76 | value = nil 77 | end 78 | 79 | [attr, middle, value] 80 | end 81 | 82 | def define_enumerated_attribute_dynamic_method(methId) 83 | meth_name = methId.id2name 84 | parts = parse_dynamic_method_parts!(meth_name) 85 | return(false) unless parts 86 | 87 | negated = !!parts[1].downcase.match(/(^|_)not_/) 88 | value = parts[2] ? parts[2].to_sym : nil 89 | self.class.define_enumerated_attribute_custom_method(methId, parts[0], value, negated) 90 | 91 | true 92 | end 93 | 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /lib/enumerated_attribute/integrations.rb: -------------------------------------------------------------------------------- 1 | require 'enumerated_attribute/integrations/active_record' 2 | require 'enumerated_attribute/integrations/object' 3 | require 'enumerated_attribute/integrations/datamapper' 4 | require 'enumerated_attribute/integrations/default' 5 | 6 | module EnumeratedAttribute 7 | module Integrations 8 | 9 | @@integration_map = {} 10 | 11 | def self.add_integration_map(base_klass_name, module_object, aliasing_array=[]) 12 | @@integration_map[base_klass_name] = {:module=>module_object, :aliasing=>aliasing_array} 13 | end 14 | class << self 15 | alias_method(:add, :add_integration_map) 16 | end 17 | 18 | #included mappings 19 | add('Object', EnumeratedAttribute::Integrations::Object) 20 | add('ActiveRecord::Base', EnumeratedAttribute::Integrations::ActiveRecord) 21 | 22 | def self.find_integration_map(klass) 23 | path = "#{klass}" 24 | begin 25 | return @@integration_map[klass.to_s] if @@integration_map.key?(klass.to_s) 26 | klass = klass.superclass 27 | path << " < #{klass}" 28 | end while klass 29 | raise EnumeratedAttribute::IntegrationError, "Unable to find integration for class hierarchy '#{path}'", caller 30 | end 31 | 32 | end 33 | end -------------------------------------------------------------------------------- /lib/enumerated_attribute/integrations/active_record.rb: -------------------------------------------------------------------------------- 1 | module EnumeratedAttribute 2 | module Integrations 3 | 4 | module ActiveRecord 5 | def self.included(klass) 6 | klass.extend(ClassMethods) 7 | klass.validate :validate_enumerated_attribute 8 | end 9 | 10 | def validate_enumerated_attribute 11 | attributes.each do |k,v| 12 | if self.class.has_enumerated_attribute?(k) and not self.class.enumerated_attribute_allows_value?(k, v) 13 | if v 14 | errors.add(k, :inclusion) 15 | else 16 | errors.add(k, :blank) 17 | end 18 | end 19 | end 20 | end 21 | 22 | def write_enumerated_attribute(name, val) 23 | name = name.to_s 24 | return write_attribute(name, val) unless self.class.has_enumerated_attribute?(name) 25 | val = nil if val == '' 26 | val_str = val.to_s if val 27 | val_sym = val.to_sym if val 28 | return instance_variable_set('@'+name, val_sym) unless self.has_attribute?(name) 29 | write_attribute(name, val_str) 30 | val_sym 31 | end 32 | 33 | def read_enumerated_attribute(name) 34 | name = name.to_s 35 | #if not enumerated - let active record handle it 36 | return read_attribute(name) unless self.class.has_enumerated_attribute?(name) 37 | #if enumerated, is it an active record attribute, if not, the value is stored in an instance variable 38 | name_sym = name.to_sym 39 | return instance_variable_get('@'+name) unless self.has_attribute?(name) 40 | #this is an enumerated active record attribute 41 | val = read_attribute(name) 42 | val = val.to_sym if !!val 43 | val 44 | end 45 | 46 | def attributes=(attrs) 47 | return if attrs.nil? 48 | #check the attributes then turn them over 49 | attrs.each do |k, v| 50 | attrs[k] = v.to_s if self.class.has_enumerated_attribute?(k) 51 | end 52 | 53 | super 54 | end 55 | 56 | def attributes 57 | atts = super 58 | atts.each do |k,v| 59 | if self.class.has_enumerated_attribute?(k) 60 | atts[k] = v.to_sym if v 61 | end 62 | end 63 | atts 64 | end 65 | 66 | def [](attr_name); read_enumerated_attribute(attr_name); end 67 | def []=(attr_name, value); write_enumerated_attribute(attr_name, value); end 68 | 69 | private 70 | 71 | def attribute=(attr_name, value); write_enumerated_attribute(attr_name, value); end 72 | 73 | module ClassMethods 74 | def instantiate(record) 75 | object = super(record) 76 | self.enumerated_attributes.each do |k,v| 77 | unless object.has_attribute?(k) #only initialize the non-column enumerated attributes 78 | object.write_enumerated_attribute(k, v.init_value) 79 | end 80 | end 81 | object 82 | end 83 | 84 | private 85 | 86 | def construct_attributes_from_arguments(attribute_names, arguments) 87 | attributes = {} 88 | attribute_names.each_with_index{|name, idx| attributes[name] = has_enumerated_attribute?(name) ? arguments[idx].to_s : arguments[idx]} 89 | attributes 90 | end 91 | 92 | def define_enumerated_attribute_new_method 93 | class_eval do 94 | class << self 95 | unless method_defined?(:new_without_enumerated_attribute) 96 | alias_method :new_without_enumerated_attribute, :new 97 | def new(*args, &block) 98 | result = new_without_enumerated_attribute(*args, &block) 99 | params = (!args.empty? && args.first.instance_of?(Hash)) ? args.first : {} 100 | params.each { |k, v| result.write_enumerated_attribute(k, v) } 101 | result.initialize_enumerated_attributes(true) 102 | yield result if block_given? 103 | result 104 | end 105 | end 106 | unless private_method_defined?(:method_missing_without_enumerated_attribute) 107 | define_chained_method(:method_missing, :enumerated_attribute) do |method_id, *arguments| 108 | arguments = arguments.map{|arg| arg.is_a?(Symbol) ? arg.to_s : arg } 109 | method_missing_without_enumerated_attribute(method_id, *arguments) 110 | end 111 | end 112 | end 113 | end 114 | end 115 | end 116 | end 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /lib/enumerated_attribute/integrations/datamapper.rb: -------------------------------------------------------------------------------- 1 | module EnumerateAttribute 2 | module Integrations 3 | module Datamapper 4 | end 5 | end 6 | end -------------------------------------------------------------------------------- /lib/enumerated_attribute/integrations/default.rb: -------------------------------------------------------------------------------- 1 | module EnumeratedAttribute 2 | module Integrations 3 | 4 | module Default 5 | def self.included(klass); klass.extend(ClassMethods); end 6 | 7 | module ClassMethods 8 | def define_enumerated_attribute_writer_method(name) 9 | method_name = "#{name}=".to_sym 10 | class_eval do 11 | define_method(method_name) {|val| write_enumerated_attribute(name.to_sym, val) } 12 | end 13 | end 14 | 15 | def define_enumerated_attribute_reader_method(name) 16 | name = name.to_sym 17 | class_eval do 18 | define_method(name) { read_enumerated_attribute(name) } 19 | end 20 | end 21 | end 22 | end 23 | 24 | end 25 | end -------------------------------------------------------------------------------- /lib/enumerated_attribute/integrations/object.rb: -------------------------------------------------------------------------------- 1 | module EnumeratedAttribute 2 | module Integrations 3 | 4 | module Object 5 | def self.included(klass) 6 | klass.extend(ClassMethods) 7 | end 8 | 9 | def write_enumerated_attribute(name, val) 10 | name = name.to_s 11 | val = nil if val == '' 12 | val = val.to_sym if val 13 | unless self.class.enumerated_attribute_allows_value?(name, val) 14 | raise(InvalidEnumeration, "nil is not allowed on '#{name}' attribute, set :nil=>true option", caller) unless val 15 | raise(InvalidEnumeration, ":#{val} is not a defined enumeration value for the '#{name}' attribute", caller) 16 | end 17 | instance_variable_set('@'+name, val) 18 | end 19 | 20 | def read_enumerated_attribute(name) 21 | instance_variable_get('@'+name.to_s) 22 | end 23 | 24 | module ClassMethods 25 | private 26 | 27 | def define_enumerated_attribute_new_method 28 | class_eval do 29 | class << self 30 | unless method_defined?(:new_without_enumerated_attribute) 31 | alias_method :new_without_enumerated_attribute, :new 32 | def new(*args, &block) 33 | result = new_without_enumerated_attribute(*args) 34 | result.initialize_enumerated_attributes 35 | yield result if block_given? 36 | result 37 | end 38 | end 39 | end 40 | end 41 | end 42 | 43 | end 44 | 45 | end 46 | end 47 | end -------------------------------------------------------------------------------- /lib/enumerated_attribute/method_definition_dsl.rb: -------------------------------------------------------------------------------- 1 | module EnumeratedAttribute 2 | 3 | class MethodDefinition 4 | attr_accessor :method_name, :negated, :argument 5 | 6 | def initialize(name, arg, negated=false) 7 | @method_name, @negated, @argument = name, negated, arg 8 | end 9 | 10 | def is_predicate_method?; @method_name[-1, 1] == '?'; end 11 | def has_method_name?; !!@method_name; end 12 | def has_argument?; !!@argument; end 13 | end 14 | 15 | class MethodDefinitionDSL 16 | attr_reader :initial_value, :pluralized_name, :decrementor_name, :incrementor_name 17 | 18 | def initialize(class_obj, descriptor) 19 | @class_obj = class_obj 20 | @attr_name = descriptor.name 21 | @attr_descriptor = descriptor #this is the enums array 22 | end 23 | 24 | #we'll by pass this - they can use it if it helps make code more readable - not enforced - should it be?? 25 | def define 26 | end 27 | 28 | def is_not(*args) 29 | arg = args.first if args.length > 0 30 | MethodDefinition.new(nil, arg, true) 31 | end 32 | alias :isnt :is_not 33 | 34 | def is(*args) 35 | arg = args.first if args.length > 0 36 | MethodDefinition.new(nil, arg) 37 | end 38 | 39 | def method_missing(methId, *args, &block) 40 | meth_name = methId.id2name 41 | 42 | meth_def = nil 43 | if args.size > 0 44 | arg = args.first 45 | if arg.instance_of?(EnumeratedAttribute::MethodDefinition) 46 | if arg.has_method_name? 47 | raise_method_syntax_error(meth_name, arg.method_name) 48 | end 49 | meth_def = arg 50 | meth_def.method_name = meth_name 51 | else 52 | meth_def = MethodDefinition.new(meth_name, arg) 53 | end 54 | elsif block_given? 55 | meth_def = MethodDefinition.new(meth_name, block) 56 | else 57 | raise_method_syntax_error(meth_name) 58 | end 59 | evaluate_method_definition(meth_def) 60 | end 61 | 62 | def init(value) 63 | if (!@attr_descriptor.empty? && !@attr_descriptor.include?(value.to_sym)) 64 | raise(InvalidDefinition, "'#{value}' in method 'init' is not an enumeration value for :#{@attr_name} attribute", caller) 65 | end 66 | @initial_value = value 67 | end 68 | 69 | def decrementor(value); @decrementor_name = value; end 70 | def incrementor(value); @incrementor_name = value; end 71 | def enums_accessor(value); @pluralized_name = value; end 72 | alias_method :inc, :incrementor 73 | alias_method :dec, :decrementor 74 | alias_method :enums, :enums_accessor 75 | alias_method :plural, :enums_accessor 76 | 77 | def label(hash) 78 | raise(InvalidDefinition, "label or labels keyword should be followed by a hash of :enum_value=>'label'", caller) unless hash.is_a?(Hash) 79 | hash.each do |k,v| 80 | raise(InvalidDefinition, "#{k} is not an enumeration value for :#{@attr_name} attribute", caller) unless (k.is_a?(Symbol) && @attr_descriptor.include?(k)) 81 | raise(InvalidDefinition, "#{v} is not a string. Labels should be strings", caller) unless v.is_a?(String) 82 | @attr_descriptor.set_label(k, v) 83 | end 84 | end 85 | alias_method :labels, :label 86 | 87 | private 88 | 89 | def raise_method_syntax_error(meth_name, offending_token=nil) 90 | suffix = offending_token ? "found '#{offending_token}'" : "found nothing" 91 | followed_by = (meth_name[-1,1] == '?' ? "is_not, an enumeration value, an array of enumeration values, " : "") + "a proc, lambda or code block" 92 | raise InvalidDefinition, "'#{meth_name}' should be followed by #{followed_by} -- but #{suffix}" 93 | end 94 | 95 | def evaluate_method_definition(mdef) 96 | unless mdef.has_argument? 97 | return raise_method_syntax_error(mdef.method_name) 98 | end 99 | 100 | if mdef.is_predicate_method? 101 | case mdef.argument 102 | when String 103 | return create_custom_method_for_symbol_or_string(mdef) 104 | when Symbol 105 | return create_custom_method_for_symbol_or_string(mdef) 106 | when Array 107 | return create_custom_method_for_array_of_enums(mdef) 108 | when Proc 109 | return create_custom_method_for_proc(mdef) 110 | end 111 | else #action method 112 | if mdef.argument.instance_of?(Proc) 113 | return create_custom_method_for_proc(mdef) 114 | end 115 | end 116 | raise_method_syntax_error(mdef.method_name, mdef.argument) 117 | end 118 | 119 | def create_custom_method_for_proc(mdef) 120 | @class_obj.send(:define_method, mdef.method_name, mdef.argument) 121 | end 122 | 123 | def create_custom_method_for_symbol_or_string(mdef) 124 | if (!@attr_descriptor.empty? && !@attr_descriptor.include?(mdef.argument.to_sym)) 125 | raise(InvalidDefinition, "'#{mdef.argument}' in method '#{mdef.method_name}' is not an enumeration value for :#{@attr_name} attribute", caller) 126 | end 127 | @class_obj.class_eval("def #{mdef.method_name}; self.#{@attr_name} #{mdef.negated ? '!=' : '=='} :#{mdef.argument}; end") 128 | end 129 | 130 | def create_custom_method_for_array_of_enums(mdef) 131 | if !@attr_descriptor.empty? 132 | mdef.argument.each do |m| 133 | if !@attr_descriptor.include?(m.to_sym) 134 | raise(InvalidDefinition, "'#{m}' in method '#{mdef.method_name}' is not an enumeration value for :#{@attr_name} attribute", caller) 135 | end 136 | end 137 | end 138 | @class_obj.class_eval("def #{mdef.method_name}; #{mdef.negated ? '!' : ''}[:#{mdef.argument.join(',:')}].include?(self.#{@attr_name}); end") 139 | end 140 | end 141 | 142 | end 143 | -------------------------------------------------------------------------------- /lib/enumerated_attribute/rails_helpers.rb: -------------------------------------------------------------------------------- 1 | require 'active_record/connection_adapters/abstract/schema_definitions' 2 | 3 | if defined?(ActiveRecord) 4 | module ActiveRecord 5 | module ConnectionAdapters 6 | class TableDefinition 7 | def column_with_enumerated_attribute(name, type, options = {}) 8 | type = 'string' if type.to_s == 'enum' 9 | column_without_enumerated_attribute(name, type, options) 10 | end 11 | safe_alias_method_chain :column, :enumerated_attribute 12 | 13 | def enum(*args) 14 | options = args.extract_options! 15 | column_names = args 16 | column_names.each { |name| column(name, 'string', options) } 17 | end 18 | end 19 | class Table 20 | def column_with_enumerated_attribute(name, type, options = {}) 21 | type = 'string' if type.to_s == 'enum' 22 | column_without_enumerated_attribute(name, type, options) 23 | end 24 | safe_alias_method_chain :column, :enumerated_attribute 25 | 26 | def enum(*args) 27 | options = args.extract_options! 28 | column_names = args 29 | column_names.each { |name| column(name, 'string', options) } 30 | end 31 | end 32 | end 33 | end 34 | end 35 | 36 | #ARGV is used by generators -- if it contains one of these generator commands - add enumeration support 37 | #unless ((ARGV || []) & ["scaffold", "rspec_scaffold", "nifty_scaffold"]).empty? 38 | if ((ARGV || []).any?{|o| o =~ /scaffold/ }) 39 | begin 40 | require 'rails/generators' 41 | require 'rails/generators/generated_attribute' 42 | rescue 43 | rescue Exception => ex 44 | end 45 | 46 | module Rails 47 | module Generators 48 | class GeneratedAttribute 49 | def field_type_with_enumerated_attribute 50 | return (@field_type = :enum_select) if type == :enum 51 | field_type_without_enumerated_attribute 52 | end 53 | alias_method_chain :field_type, :enumerated_attribute 54 | end 55 | end 56 | end 57 | end 58 | 59 | if defined?(ActionView::Base) 60 | module ActionView 61 | module Helpers 62 | 63 | #form_options_helper.rb 64 | module FormOptionsHelper 65 | #def select 66 | def enum_select(object, method, options={}, html_options={}) 67 | InstanceTag.new(object, method, self, options.delete(:object)).to_enum_select_tag(options, html_options) 68 | end 69 | end 70 | 71 | class InstanceTag 72 | def to_enum_select_tag(options, html_options={}) 73 | choices = [] 74 | if self.object.respond_to?(:enums) 75 | enums = self.object.enums(method_name.to_sym) 76 | choices = enums ? enums.select_options : [] 77 | if (value = self.object.__send__(method_name.to_sym)) 78 | options[:selected] ||= value.to_s 79 | else 80 | options[:include_blank] = enums.allows_nil? if options[:include_blank].nil? 81 | end 82 | end 83 | to_select_tag(choices, options, html_options) 84 | end 85 | 86 | #initialize record_name, method, self 87 | if respond_to?(:to_tag) 88 | def to_tag_with_enumerated_attribute(options={}) 89 | #look for an enum 90 | if (column_type == :string && 91 | self.object.class.respond_to?(:has_enumerated_attribute?) && 92 | self.object.class.has_enumerated_attribute?(method_name.to_sym)) 93 | to_enum_select_tag(options) 94 | else 95 | to_tag_without_enumerated_attribute(options) 96 | end 97 | end 98 | alias_method_chain :to_tag, :enumerated_attribute 99 | end 100 | end 101 | 102 | class FormBuilder 103 | def enum_select(method, options={}, html_options={}) 104 | @template.enum_select(@object_name, method, objectify_options(options), @default_options.merge(html_options)) 105 | end 106 | end 107 | 108 | end 109 | end 110 | end 111 | 112 | 113 | -------------------------------------------------------------------------------- /spec/active_record/active_record_spec.rb: -------------------------------------------------------------------------------- 1 | require 'active_record/test_in_memory' 2 | require 'active_record' 3 | require 'enumerated_attribute' 4 | require 'active_record/race_car' 5 | 6 | describe "RaceCar" do 7 | 8 | it "should have default labels for :gear attribute" do 9 | labels_hash = {:reverse=>'Reverse', :neutral=>'Neutral', :first=>'First', :second=>'Second', :over_drive=>'Over drive'} 10 | labels = ['Reverse', 'Neutral', 'First', 'Second', 'Over drive'] 11 | select_options = [['Reverse', 'reverse'], ['Neutral', 'neutral'], ['First', 'first'], ['Second', 'second'], ['Over drive', 'over_drive']] 12 | r=RaceCar.new 13 | r.gears.labels.should == labels 14 | labels_hash.each do |k,v| 15 | r.gears.label(k).should == v 16 | end 17 | r.gears.hash.should == labels_hash 18 | r.gears.select_options.should == select_options 19 | end 20 | 21 | it "should retrieve :gear enums through enums method" do 22 | r=RaceCar.new 23 | r.enums(:gear).should == r.gears 24 | end 25 | 26 | it "should return a Symbol type from reader methods" do 27 | r=RaceCar.new 28 | r.gear.should be_an_instance_of(Symbol) 29 | end 30 | 31 | it "should increment and decrement :gear attribute correctly" do 32 | r=RaceCar.new 33 | r.gear = :neutral 34 | r.gear_next.should == :first 35 | r.gear_next.should == :second 36 | r.gear_next.should == :over_drive 37 | r.gear_next.should == :reverse 38 | r.gear_next.should == :neutral 39 | r.gear.should == :neutral 40 | r.gear_previous.should == :reverse 41 | r.gear_previous.should == :over_drive 42 | r.gear_previous.should == :second 43 | r.gear_previous 44 | r.gear.should == :first 45 | end 46 | 47 | it "should have dynamic predicate methods for :gear attribute" do 48 | r=RaceCar.new 49 | r.gear = :second 50 | r.gear_is_in_second?.should be_true 51 | r.gear_not_in_second?.should be_false 52 | r.gear_is_nil?.should be_false 53 | r.gear_is_not_nil?.should be_true 54 | end 55 | 56 | it "should have working dynamic predicate methods on retrieved objects" do 57 | r=RaceCar.new 58 | r.gear = :second 59 | r.save! 60 | 61 | s=RaceCar.find r.id 62 | s.should_not be_nil 63 | s.gear_is_in_second?.should be_true 64 | s.gear_is_not_in_second?.should be_false 65 | s.gear_is_nil?.should be_false 66 | s.gear_is_not_nil?.should be_true 67 | end 68 | 69 | it "should be created and found with dynamic find or creator method" do 70 | s = RaceCar.find_or_create_by_name_and_gear('specialty', :second) 71 | s.should_not be_nil 72 | s.gear.should == :second 73 | s.name.should == 'specialty' 74 | 75 | s0 = RaceCar.find_or_create_by_name_and_gear('specialty', :second) 76 | s0.gear.should == :second 77 | s0.id.should == s.id 78 | end 79 | it "should be initialized with dynamic find or initialize method" do 80 | s = RaceCar.find_or_initialize_by_name_and_gear('myspecialty', :second) 81 | s.should_not be_nil 82 | s.gear.should == :second 83 | s.name.should == 'myspecialty' 84 | lambda { s.save! }.should_not raise_exception 85 | 86 | s0 = RaceCar.find_or_initialize_by_name_and_gear('myspecialty', :second) 87 | s0.gear.should == :second 88 | s0.id.should == s.id 89 | end 90 | it "should find record using dynamic finder by enumerated column :gear attributes" do 91 | r=RaceCar.new 92 | r.gear = :second 93 | r.name = 'special' 94 | r.save! 95 | 96 | s=RaceCar.find_by_gear_and_name(:second, 'special') 97 | s.should_not be_nil 98 | s.id.should == r.id 99 | end 100 | 101 | it "should initialize according to enumerated attribute definitions" do 102 | r = RaceCar.new 103 | r.gear.should == :neutral 104 | r.choke.should == :none 105 | end 106 | 107 | it "should create new instance using block" do 108 | r = RaceCar.new do |r| 109 | r.gear = :first 110 | r.choke = :medium 111 | r.lights = 'on' 112 | end 113 | r.gear.should == :first 114 | r.lights.should == 'on' 115 | r.choke.should == :medium 116 | end 117 | 118 | it "should initialize using parameter hash with symbol keys" do 119 | r=RaceCar.new(:name=>'FastFurious', :gear=>:second, :lights=>'on', :choke=>:medium) 120 | r.gear.should == :second 121 | r.lights.should == 'on' 122 | r.choke.should == :medium 123 | end 124 | 125 | it "should initialize using parameter hash with string keys" do 126 | r=RaceCar.new({'name'=>'FastFurious', 'gear'=>'second', 'lights'=>'on', 'choke'=>'medium'}) 127 | r.gear.should == :second 128 | r.lights.should == 'on' 129 | r.choke.should == :medium 130 | end 131 | 132 | 133 | it "should convert non-column enumerated attributes from string to symbols" do 134 | r=RaceCar.new 135 | r.choke = 'medium' 136 | r.choke.should == :medium 137 | r.save! 138 | end 139 | 140 | it "should convert enumerated column attributes from string to symbols" do 141 | r=RaceCar.new 142 | r.gear = 'second' 143 | r.gear.should == :second 144 | r.save! 145 | 146 | s=RaceCar.find r.id 147 | s.gear.should == :second 148 | end 149 | 150 | it "should not convert non-enumerated column attributes from string to symbols" do 151 | r=RaceCar.new 152 | r.lights = 'off' 153 | r.lights.should == 'off' 154 | r.save! 155 | 156 | s=RaceCar.find r.id 157 | s.lights.should == 'off' 158 | end 159 | 160 | it "should not raise InvalidEnumeration when parametrically initialized with invalid column attribute value" do 161 | r=RaceCar.new 162 | lambda{ r.gear= :drive}.should_not raise_error(EnumeratedAttribute::InvalidEnumeration) 163 | end 164 | 165 | it "should raise RecordInvalid on create! when parametrically initialized with invalid column attribute value" do 166 | lambda{ RaceCar.create!(:gear => :drive)}.should raise_error(ActiveRecord::RecordInvalid) 167 | end 168 | 169 | 170 | it "should not raise InvalidEnumeration when parametrically initialized with invalid non-column attribute" do 171 | r=RaceCar.new 172 | lambda{ r.choke= :all}.should_not raise_error(EnumeratedAttribute::InvalidEnumeration) 173 | end 174 | 175 | it "should not be valid on non-column attribute with parametrically initialized bad value" do 176 | r=RaceCar.new 177 | r.choke= :all 178 | r.should_not be_valid 179 | end 180 | 181 | 182 | it "should return non-column enumerated attributes from [] method" do 183 | r = RaceCar.new 184 | r[:choke].should == :none 185 | end 186 | 187 | it "should return enumerated column attributes from [] method" do 188 | r=RaceCar.new 189 | r.gear = :neutral 190 | r[:gear].should == :neutral 191 | end 192 | 193 | it "should set non-column enumerated attributes with []= method" do 194 | r=RaceCar.new 195 | r[:choke] = :medium 196 | r.choke.should == :medium 197 | end 198 | 199 | it "should set enumerated column attriubtes with []= method" do 200 | r=RaceCar.new 201 | r[:gear] = :second 202 | r.gear.should == :second 203 | end 204 | 205 | it "should not raise InvalidEnumeration when setting enumerated column attribute with []= method" do 206 | r=RaceCar.new 207 | lambda{ r[:gear]= :drive }.should_not raise_error(EnumeratedAttribute::InvalidEnumeration) 208 | end 209 | 210 | it "should raise RecordInvalid on save! after setting enumerated column attribute with []= method" do 211 | r=RaceCar.new 212 | r[:gear]= :drive 213 | lambda{ r.save! }.should raise_error(ActiveRecord::RecordInvalid) 214 | end 215 | 216 | it "should set and retrieve string for non-enumerated column attributes with []=" do 217 | r=RaceCar.new 218 | r[:lights] = 'on' 219 | r.lights.should == 'on' 220 | r[:lights].should == 'on' 221 | end 222 | 223 | it "should set and retrieve symbol for non-enumerated column attributes with []=" do 224 | r=RaceCar.new 225 | r[:lights] = :on 226 | r.lights.should == :on 227 | r[:lights].should == :on 228 | end 229 | 230 | it "should not raise InvalidEnumeration for invalid enum passed to attributes=" do 231 | r=RaceCar.new 232 | lambda { r.attributes = {:lights=>'off', :gear =>:drive} }.should_not raise_error(EnumeratedAttribute::InvalidEnumeration) 233 | end 234 | 235 | it "should raise RecordInvalid on save! for invalid enum passed to attributes=" do 236 | r=RaceCar.new 237 | r.attributes = {:lights=>'off', :gear =>:drive} 238 | lambda { r.save! }.should raise_error(ActiveRecord::RecordInvalid) 239 | end 240 | 241 | =begin 242 | #do not write symbols to enumerated column attributes using write_attribute 243 | #do not user read_attribute to read an enumerated column attribute 244 | it "should write enumeration with write_attribute" do 245 | r=RaceCar.new 246 | r.write_attribute(:gear, :first) 247 | r.gear.should == :first 248 | r.save! 249 | 250 | s=RaceCar.find r.id 251 | s.gear.should == :first 252 | s.write_attribute(:gear, :second) 253 | s.save! 254 | 255 | t=RaceCar.find s.id 256 | t.gear.should == :second 257 | end 258 | 259 | it "should raise error when setting enumerated column attribute to invalid enum using write_attribute" do 260 | r=RaceCar.new 261 | lambda { r.write_attribute(:gear, :yo) }.should raise_error 262 | end 263 | =end 264 | 265 | it "should retrieve symbols for enumerations from ActiveRecord :attributes method" do 266 | r=RaceCar.new 267 | r.gear = :second 268 | r.choke = :medium 269 | r.lights = 'on' 270 | r.save! 271 | 272 | s = RaceCar.find(r.id) 273 | s.attributes['gear'].should == :second 274 | s.attributes['lights'].should == 'on' 275 | end 276 | 277 | it "should update_attribute for enumerated column attribute" do 278 | r=RaceCar.new 279 | r.gear = :first 280 | r.save! 281 | r.update_attribute(:gear, :second) 282 | r.gear.should == :second 283 | 284 | s=RaceCar.find r.id 285 | s.gear.should == :second 286 | end 287 | 288 | it "should update_attribute for non-enumerated column attribute" do 289 | r=RaceCar.new 290 | r.lights = 'on' 291 | r.save! 292 | r.update_attribute(:lights, 'off') 293 | r.lights.should == 'off' 294 | 295 | s=RaceCar.find r.id 296 | s.lights.should == 'off' 297 | end 298 | 299 | it "should update_attributes for both non- and enumerated column attributes" do 300 | r=RaceCar.new 301 | r.gear = :first 302 | r.lights = 'off' 303 | r.save! 304 | r.update_attributes({:gear=>:second, :lights=>'on'}) 305 | s=RaceCar.find r.id 306 | s.gear.should == :second 307 | s.lights.should == 'on' 308 | s.update_attributes({:gear=>'over_drive', :lights=>'off'}) 309 | t=RaceCar.find s.id 310 | t.gear.should == :over_drive 311 | t.lights.should == 'off' 312 | end 313 | 314 | it "should provide symbol values for enumerated column attributes from the :attributes method" do 315 | r=RaceCar.new 316 | r.lights = 'on' 317 | r.save! 318 | 319 | s=RaceCar.find r.id 320 | s.attributes['gear'].should == :neutral 321 | end 322 | 323 | it "should provide normal values for non-enumerated column attributes from the :attributes method" do 324 | r=RaceCar.new 325 | r.lights = 'on' 326 | r.save! 327 | 328 | s=RaceCar.find r.id 329 | s.attributes['lights'].should == 'on' 330 | end 331 | 332 | it "should not raise InvalidEnumeration when setting invalid enumeration value with :attributes= method" do 333 | r=RaceCar.new 334 | lambda { r.attributes = {:gear=>:yo, :lights=>'on'} }.should_not raise_error(EnumeratedAttribute::InvalidEnumeration) 335 | end 336 | 337 | it "should raise RecordInvalid on save! after setting invalid enumeration value with :attributes= method" do 338 | r=RaceCar.new 339 | r.attributes = {:gear=>:yo, :lights=>'on'} 340 | lambda { r.save! }.should raise_error(ActiveRecord::RecordInvalid) 341 | end 342 | 343 | it "should not set init value for enumerated column attribute saved as nil" do 344 | r=RaceCar.new 345 | r.gear = nil 346 | r.lights = 'on' 347 | r.save! 348 | 349 | s=RaceCar.find r.id 350 | s.gear.should == nil 351 | s.lights.should == 'on' 352 | end 353 | 354 | it "should not set init value for enumerated column attributes saved as value" do 355 | r=RaceCar.new 356 | r.gear = :second 357 | r.lights = 'all' 358 | r.save! 359 | 360 | s=RaceCar.find r.id 361 | s.gear.should == :second 362 | s.lights.should == 'all' 363 | end 364 | 365 | it "should save and retrieve its name" do 366 | r = RaceCar.new 367 | r.name= 'Green Meanie' 368 | r.save! 369 | 370 | s = RaceCar.find r.id 371 | s.should_not be_nil 372 | s.name.should == 'Green Meanie' 373 | end 374 | 375 | it "should save and retrieve symbols for enumerated column attribute" do 376 | r = RaceCar.new 377 | r.gear = :over_drive 378 | r.save! 379 | 380 | s = RaceCar.find r.id 381 | #s.should_not be_nil 382 | s.gear.should == :over_drive 383 | end 384 | 385 | it "should not save values for non-column enumerated attributes" do 386 | r=RaceCar.new 387 | r.choke = :medium 388 | r.save! 389 | 390 | s=RaceCar.find r.id 391 | s.choke.should == :none 392 | end 393 | 394 | it "should save string and retrieve string for non-enumerated column attributes" do 395 | r =RaceCar.new 396 | r.lights = 'on' 397 | r.save! 398 | 399 | s = RaceCar.find r.id 400 | s.lights.should == 'on' 401 | s[:lights].should == 'on' 402 | end 403 | 404 | it "should save symbol and retrieve string for non-enumerated column attributes" do 405 | r =RaceCar.new 406 | r.lights = :off 407 | r.save! 408 | 409 | s = RaceCar.find r.id 410 | s.lights.should == "--- :off\n" 411 | s[:lights].should == "--- :off\n" 412 | end 413 | 414 | end 415 | -------------------------------------------------------------------------------- /spec/active_record/association_test_classes.rb: -------------------------------------------------------------------------------- 1 | 2 | class Company < ActiveRecord::Base 3 | has_one :license 4 | has_many :employees 5 | has_many :contract_workers 6 | has_many :contractors, :through=>:contract_workers 7 | enum_attr :status, %w(s_corp c_corp llc), :nil=>true 8 | end 9 | class Employee < ActiveRecord::Base 10 | belongs_to :company 11 | enum_attr :status, %w(full_time part_time suspended), :nil=>true 12 | end 13 | class License < ActiveRecord::Base 14 | belongs_to :company 15 | enum_attr :status, %w(^current expired) 16 | end 17 | class Contractor < ActiveRecord::Base 18 | has_many :contract_workers 19 | has_many :companies, :through=>:contract_workers 20 | enum_attr :status, %w(^available unavailable) 21 | end 22 | class ContractWorker < ActiveRecord::Base 23 | belongs_to :company 24 | belongs_to :contractor 25 | enum_attr :status, %w(unfresh ^unfresh) #i don't know what to put here 26 | end 27 | 28 | #polymorphic 29 | class Comment < ActiveRecord::Base 30 | belongs_to :document, :polymorphic=>true 31 | enum_attr :status, %w(^unflagged flagged) 32 | end 33 | class Article < ActiveRecord::Base 34 | has_one :comment, :as=>:document 35 | enum_attr :status, %w(^unreviewed accepted) 36 | end 37 | class Image < ActiveRecord::Base 38 | has_one :comment, :as=>:document 39 | enum_attr :status, %w(^unreviewed accepted) 40 | end 41 | -------------------------------------------------------------------------------- /spec/active_record/associations_spec.rb: -------------------------------------------------------------------------------- 1 | require 'active_record/test_in_memory' 2 | require 'enumerated_attribute' 3 | require 'active_record' 4 | require 'active_record/association_test_classes' 5 | 6 | module TestVariables 7 | def company_name; 'Company A'; end 8 | end 9 | 10 | describe "Polymorphic associations" do 11 | include TestVariables 12 | 13 | it "should retrieve enum'ed attribute for articles and images of a comment" do 14 | c1 = Comment.new(:comment=>'i like it') 15 | c1.document = Article.create!(:name=>'Birds of a Feather', :status=>:accepted) 16 | c1.save! 17 | c1.status.should == :unflagged 18 | 19 | c2 = Comment.new(:comment=>'i hate it', :status => :flagged) 20 | c2.document = Image.create!(:name=>'Martian Landscape', :status=>:unreviewed) 21 | c2.save! 22 | c2.status.should == :flagged 23 | 24 | Comment.find(c1.id).document.status.should == :accepted 25 | Comment.find(c2.id).document.status.should == :unreviewed 26 | 27 | end 28 | 29 | it "should retrieve enum'ed attribute for comments on articles and images" do 30 | a = Article.create!(:name=>'Swimming with Whales', :status=>:accepted) 31 | a.create_comment(:comment=>'i like it', :status=>:unflagged) 32 | a.save! 33 | 34 | i = Image.create!(:name=>'Mountain Climbing', :status=>:unreviewed) 35 | i.comment = Comment.create!(:comment=>'i do not like it', :status=>:flagged) 36 | i.save! 37 | 38 | Image.find(i.id).comment.status.should == :flagged 39 | Article.find(a.id).comment.status.should == :unflagged 40 | 41 | end 42 | 43 | end 44 | 45 | describe "Basic Associations" do 46 | include TestVariables 47 | 48 | it "should retrieve enum'ed status for its license" do 49 | c = Company.new(:status=>:llc, :name=>company_name) 50 | c.save! 51 | c.create_license(:status=>:current) 52 | 53 | r = Company.find(c.id) 54 | lic = r.license 55 | r.status.should == :llc 56 | lic.status.should == :current 57 | end 58 | 59 | it "should retrieve enum'ed status for multiple employees" do 60 | c=Company.new(:status=>:llc, :name=>company_name) 61 | c.save! 62 | c.employees.create!(:status=>:full_time, :name=>'edward') 63 | c.employees.create!(:status=>:suspended, :name=>'tina') 64 | 65 | r=Company.find(c.id) 66 | r.employees.find_by_name('edward').status.should == :full_time 67 | r.employees.find_by_name('tina').status.should == :suspended 68 | Employee.delete_all 69 | end 70 | 71 | it "should retrieve enum'ed status for multiple contractors" do 72 | c=Company.new(:status=>:llc, :name=>company_name) 73 | c.save! 74 | c1 = Contractor.new(:status=>:available, :name=>'john') 75 | c2 = Contractor.new(:status=>:unavailable, :name=>'sally') 76 | c.contractors << c1 77 | c.contractors << c2 78 | 79 | r=Company.find(c.id) 80 | r.contractors.find_by_name('sally').status.should == :unavailable 81 | r.contractors.find_by_name('john').status.should == :available 82 | Contractor.delete_all 83 | ContractWorker.delete_all 84 | end 85 | end 86 | 87 | describe "License" do 88 | include TestVariables 89 | it "should retrieve the status for the company" do 90 | Company.delete_all 91 | License.delete_all 92 | lic = License.create!(:status=>:expired) 93 | lic.company = Company.create!(:name=>company_name, :status=>:llc) 94 | lic.save! 95 | 96 | License.find(lic.id).company.status.should == :llc 97 | end 98 | end 99 | 100 | describe "Contractor" do 101 | include TestVariables 102 | it "should retrieve enum'ed status from multiple companies" do 103 | Company.delete_all 104 | Contractor.delete_all 105 | ContractWorker.delete_all 106 | c = Contractor.create!(:name=>'jack', :status=>:available) 107 | 108 | c1 = Company.create!(:name=>company_name, :status=>:s_corp) 109 | c2 = Company.create!(:name=>'other company', :status=>:llc) 110 | c.companies << c1 << c2 111 | 112 | j = Contractor.find(c.id) 113 | j.companies.find_by_name('other company').status.should == :llc 114 | j.companies.find_by_name(company_name).status.should == :s_corp 115 | end 116 | end 117 | 118 | describe "Employee" do 119 | include TestVariables 120 | it "should retrieve enum'ed status from the company" do 121 | Company.delete_all 122 | Employee.delete_all 123 | e = Employee.create!(:name=>'juanita', :status=>:part_time) 124 | e.company = Company.create!(:name=>company_name, :status=>:c_corp) 125 | e.save! 126 | 127 | emp = Employee.find(e.id) 128 | emp.company.status.should == :c_corp 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /spec/active_record/cfg.rb: -------------------------------------------------------------------------------- 1 | #irb -r cfg ==> to work configure for working in IRB 2 | $:.unshift '../../lib' 3 | require 'active_record/test_in_memory' 4 | require 'enumerated_attribute' 5 | require 'active_record/race_car' 6 | require 'active_record/sti_classes' 7 | -------------------------------------------------------------------------------- /spec/active_record/df.rb: -------------------------------------------------------------------------------- 1 | require 'cfg' 2 | 3 | r=RaceCar.new 4 | r.gear = :second 5 | r.name = 'special' 6 | r.save! 7 | 8 | #RaceCar.find_by_gear_and_name(:second, 'special') 9 | car = RaceCar.find_or_create_by_name_and_gear('special', :second) 10 | debugger 11 | car0 = RaceCar.find_or_create_by_name_and_gear('special', :second) 12 | car0 -------------------------------------------------------------------------------- /spec/active_record/inheritance_classes.rb: -------------------------------------------------------------------------------- 1 | require 'active_record/race_car' 2 | 3 | class SubRaceCar < RaceCar 4 | set_table_name "race_cars" 5 | 6 | enum_attr :extra, %w(^one two three four) 7 | end 8 | 9 | class SubRaceCar2 < RaceCar 10 | set_table_name "race_cars" 11 | 12 | end 13 | 14 | class SubRaceCar3 < RaceCar 15 | set_table_name "race_cars" 16 | 17 | enum_attr :gear, %w(reverse neutral ^first second third over_drive) 18 | end 19 | 20 | -------------------------------------------------------------------------------- /spec/active_record/inheritance_spec.rb: -------------------------------------------------------------------------------- 1 | require 'active_record/test_in_memory' 2 | require 'enumerated_attribute' 3 | require 'active_record' 4 | require 'active_record/inheritance_classes' 5 | 6 | def one_to_four; [:one, :two, :three, :four]; end 7 | def gear1; [:reverse, :neutral, :first, :second, :over_drive]; end 8 | def gear2; [:reverse, :neutral, :first, :second, :third, :over_drive]; end 9 | def choke; [:none, :medium, :full]; end 10 | 11 | describe "SubRaceCar" do 12 | it "should have :gear init to :neutral" do 13 | o=SubRaceCar.new 14 | o.gear.should == :neutral 15 | o.enums(:gear).should == gear1 16 | o.gear_previous.should == :reverse 17 | end 18 | it "should have :choke init to :none" do 19 | o=SubRaceCar.new 20 | o.choke.should == :none 21 | o.enums(:choke).should == choke 22 | o.choke_previous.should == :full 23 | end 24 | it "should have :extra init to :one" do 25 | o=SubRaceCar.new 26 | o.extra.should == :one 27 | o.enums(:extra).should == one_to_four 28 | o.extra_previous.should == :four 29 | end 30 | end 31 | 32 | describe "SubRaceCar2" do 33 | it "should have :gear init to :neutral" do 34 | o=SubRaceCar2.new 35 | o.gear.should == :neutral 36 | o.enums(:gear).should == gear1 37 | o.gear_previous.should == :reverse 38 | end 39 | it "should have :choke init to :none" do 40 | o=SubRaceCar2.new 41 | o.choke.should == :none 42 | o.enums(:choke).should == choke 43 | o.choke_previous.should == :full 44 | end 45 | end 46 | 47 | describe "SubRaceCar3" do 48 | it "should have overridden :gear init to :first" do 49 | o=SubRaceCar3.new 50 | o.gear.should == :first 51 | o.enums(:gear).should == gear2 52 | o.gear_previous.should == :neutral 53 | end 54 | it "should have :choke init to :none" do 55 | o=SubRaceCar3.new 56 | o.choke.should == :none 57 | o.enums(:choke).should == choke 58 | o.choke_previous.should == :full 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /spec/active_record/race_car.rb: -------------------------------------------------------------------------------- 1 | 2 | class RaceCar < ActiveRecord::Base 3 | enum_attr :gear, %w(reverse ^neutral first second over_drive) 4 | enum_attr :choke, %w(^none medium full) 5 | end 6 | 7 | #gear = enumerated column attribute 8 | #choke = enumerated non-column attribute 9 | #lights = non-enumerated column attribute 10 | 11 | =begin 12 | t.string :name 13 | t.string :gear 14 | t.string :lights 15 | =end 16 | -------------------------------------------------------------------------------- /spec/active_record/single_table_inheritance_spec.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffp/enumerated_attribute/84221b872f7f71152994b212520c833742a263e3/spec/active_record/single_table_inheritance_spec.rb -------------------------------------------------------------------------------- /spec/active_record/sti_classes.rb: -------------------------------------------------------------------------------- 1 | 2 | class StiParent < ActiveRecord::Base 3 | enum_attr :parent_enum, %w(p1 p2 p3) 4 | 5 | end 6 | 7 | class StiSub < StiParent 8 | enum_attr :sub_enum, %w(s1 s2 s3) 9 | end 10 | 11 | -------------------------------------------------------------------------------- /spec/active_record/sti_spec.rb: -------------------------------------------------------------------------------- 1 | require 'active_record/test_in_memory' 2 | require 'enumerated_attribute' 3 | require 'active_record' 4 | require 'active_record/sti_classes' 5 | 6 | 7 | #contributed by Rob Chekaluk 8 | 9 | describe "enum_attr for STI subclass" do 10 | it "should assign, save and retrieve non-enumerated attribute" do 11 | s = StiSub.new() 12 | s.sub_nonenum = "value2" 13 | s.save 14 | s0 = StiSub.find(s.id) 15 | s0.sub_nonenum.should == "value2" 16 | end 17 | it "should assign, save and retrieve subclass enum" do 18 | s = StiSub.new() 19 | s.sub_enum = :s3 20 | s.sub_enum.should == :s3 21 | s.save 22 | s0 = StiSub.find(s.id) 23 | s0.sub_enum.should == :s3 24 | end 25 | it "should assign, save and retrieve inherited enum" do 26 | s = StiSub.new() 27 | s.parent_enum = :p2 28 | s.parent_enum.should == :p2 29 | s.save 30 | s0 = StiSub.find(s.id) 31 | s0.parent_enum.should == :p2 32 | end 33 | it "should use predicate methods to access enumerated attributes" do 34 | s=StiSub.create(:parent_enum=>:p2, :sub_enum=>:s1) 35 | s.parent_enum_is_p2?.should be_true 36 | s.parent_enum_is_not_p2?.should be_false 37 | s.sub_enum_is_s2?.should be_false 38 | s.sub_enum_is_not_s2?.should be_true 39 | end 40 | end 41 | 42 | -------------------------------------------------------------------------------- /spec/active_record/test_in_memory.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | gem 'activerecord', ENV['AR_VERSION'] ? "=#{ENV['AR_VERSION']}" : '>=2.1.0' 4 | require 'active_record' 5 | require 'active_support/core_ext/logger' rescue nil # rails3 6 | 7 | require 'enumerated_attribute' 8 | 9 | ActiveRecord::Base.establish_connection({'adapter' => 'sqlite3', 'database' => ':memory:'}) 10 | ActiveRecord::Base.logger = Logger.new("#{File.dirname(__FILE__)}/active_record.log") 11 | 12 | connection = ActiveRecord::Base.connection 13 | connection.create_table(:race_cars, :force=>true) do |t| 14 | t.string :name 15 | t.enum :gear 16 | t.enum :lights 17 | t.timestamps 18 | end 19 | connection.create_table(:bicycles, :force=>true) do |t| 20 | t.string :name 21 | t.enum :speed 22 | t.enum :gear 23 | end 24 | 25 | #basic_associations 26 | connection.create_table(:companies, :force=>true) do |t| 27 | t.string :name 28 | t.string :status 29 | end 30 | connection.create_table(:contract_workers, :force=>true) do |t| 31 | t.references :company 32 | t.references :contractor 33 | t.string :status 34 | end 35 | connection.create_table(:licenses, :force=>true) do |t| 36 | t.references :company 37 | t.string :status 38 | end 39 | connection.create_table(:contractors, :force=>true) do |t| 40 | t.string :name 41 | t.string :status 42 | end 43 | connection.create_table(:employees, :force=>true) do |t| 44 | t.references :company 45 | t.string :name 46 | t.string :status 47 | end 48 | 49 | #polymorphic_associations 50 | connection.create_table(:comments, :force=>true) do |t| 51 | t.references :document, :polymorphic=>true 52 | t.text :comment 53 | t.string :status 54 | end 55 | connection.create_table(:articles, :force=>true) do |t| 56 | t.string :name 57 | t.string :status 58 | end 59 | connection.create_table(:images, :force=>true) do |t| 60 | t.string :name 61 | t.string :status 62 | end 63 | 64 | #single table inheritance 65 | connection.create_table(:sti_parents, :force=>true) do |t| 66 | t.string :type 67 | t.enum :parent_enum 68 | t.string :sub_nonenum 69 | t.enum :sub_enum 70 | t.timestamps 71 | end 72 | -------------------------------------------------------------------------------- /spec/car.rb: -------------------------------------------------------------------------------- 1 | require 'enumerated_attribute' 2 | 3 | #used to test that method_missing chaining plays nice in inheritance situations 4 | 5 | class Vehicle 6 | def vehicle_method_missing_called?; @vehicle_method_missing_called; end 7 | def self.vehicle_new_called?; @@vehicle_new_called; end 8 | def self.reset; @@vehicle_new_called = false; end 9 | 10 | @@vehicle_new_called = false 11 | def initialize 12 | @vehicle_method_missing_called = false 13 | super 14 | end 15 | 16 | def method_missing(methId, *args) 17 | @vehicle_method_missing_called = true 18 | #end here 19 | end 20 | 21 | def self.new 22 | @@vehicle_new_called = true 23 | super 24 | end 25 | 26 | end 27 | 28 | class CarWithMethods < Vehicle 29 | def car_method_missing_called?; @car_method_missing_called; end 30 | def self.car_new_called?; @@car_new_called; end 31 | def self.reset; @@car_new_called = false; super; end 32 | 33 | def self.new 34 | @@car_new_called = true 35 | super 36 | end 37 | 38 | def method_missing(methId, *args) 39 | @car_method_missing_called = true 40 | super 41 | end 42 | 43 | enum_attr :gear, %w(reverse ^neutral drive) 44 | 45 | @@car_new_called = false 46 | def initialize 47 | @car_method_missing_called = false 48 | super 49 | end 50 | 51 | end 52 | 53 | class CarWithoutMethods < Vehicle 54 | def car_method_missing_called?; @car_method_missing_called; end 55 | def self.car_new_called?; @@car_new_called; end 56 | def self.reset; @@car_new_called = false; super; end 57 | 58 | enum_attr :gear, %w(reverse ^neutral drive) 59 | 60 | @@car_new_called = false 61 | def initialize 62 | @car_method_missing_called = false 63 | super 64 | end 65 | 66 | end 67 | 68 | -------------------------------------------------------------------------------- /spec/cfg.rb: -------------------------------------------------------------------------------- 1 | #irb -r cfg ==> to work configure for working in IRB 2 | $:.unshift '../lib' 3 | require 'enumerated_attribute' 4 | require 'tractor' 5 | require 'car' 6 | require 'plural' 7 | require 'inheritance_classes' 8 | 9 | -------------------------------------------------------------------------------- /spec/inheritance_classes.rb: -------------------------------------------------------------------------------- 1 | require 'enumerated_attribute' 2 | 3 | class Base 4 | enum_attr :base1, %w(^one two three four) 5 | enum_attr :inherited1, %w(^one two three four) 6 | enum_attr :inherited2, %w(^one two three four) 7 | end 8 | 9 | class Sub < Base 10 | enum_attr :sub1, %w(one ^two three four) 11 | enum_attr :inherited1, %w(one ^two three four) 12 | enum_attr :inherited2, %w(^five six seven eight) 13 | end 14 | 15 | class Sub2 < Base 16 | end 17 | -------------------------------------------------------------------------------- /spec/inheritance_spec.rb: -------------------------------------------------------------------------------- 1 | require 'inheritance_classes' 2 | 3 | def one_to_four; [:one, :two, :three, :four]; end 4 | def five_to_eight; [:five, :six, :seven, :eight]; end 5 | 6 | describe "Base" do 7 | it "should have :base1 init to :one" do 8 | o=Base.new 9 | o.base1.should == :one 10 | o.enums(:base1).should == one_to_four 11 | o.base1_previous.should == :four 12 | end 13 | it "should have :inherited1 init to :one" do 14 | o=Base.new 15 | o.inherited1.should == :one 16 | o.enums(:inherited1).should == one_to_four 17 | o.inherited1_previous.should == :four 18 | end 19 | it "should have :inherited2 init to :one" do 20 | o=Base.new 21 | o.inherited2.should == :one 22 | o.enums(:inherited2).should == one_to_four 23 | o.inherited2_previous.should == :four 24 | end 25 | end 26 | 27 | describe "Sub'Reverse', :neutral=>'Neutral', :first=>'First', :second=>'Second', :over_drive=>'Over drive'} 54 | labels = ['Reverse', 'Neutral', 'First', 'Second', 'Over drive'] 55 | select_options = [['Reverse', 'reverse'], ['Neutral', 'neutral'], ['First', 'first'], ['Second', 'second'], ['Over drive', 'over_drive']] 56 | t=Tractor.new 57 | t.gears.labels.should == labels 58 | labels_hash.each do |k,v| 59 | t.gears.label(k).should == v 60 | end 61 | t.gears.hash.should == labels_hash 62 | t.gears.select_options.should == select_options 63 | end 64 | 65 | it "should retrieve :gear enums through enums method" do 66 | t=Tractor.new 67 | t.enums(:gear).should == t.gears 68 | end 69 | 70 | it "should retrieve custom labels for :side_light attribute" do 71 | labels_hash = {:off=>'OFF', :low=>'LOW DIM', :high=>'HIGH BEAM', :super_high=>'SUPER BEAM'} 72 | t=Tractor.new 73 | enum = t.enums(:side_light) 74 | t.enums(:side_light).hash.each do |k,v| 75 | enum.label(k).should == labels_hash[k] 76 | end 77 | end 78 | 79 | it "should return a Symbol type from reader methods" do 80 | t=Tractor.new 81 | t.gear.should be_an_instance_of(Symbol) 82 | end 83 | 84 | it "should not raise errors for dynamic predicate methods missing attribute name" do 85 | t=Tractor.new 86 | lambda{ t.neutral?.should be_true }.should_not raise_error 87 | lambda{ t.is_neutral?.should be_true }.should_not raise_error 88 | lambda{ t.not_neutral?.should be_false}.should_not raise_error 89 | t.gear = :first 90 | t.neutral?.should be_false 91 | t.not_neutral?.should be_true 92 | end 93 | 94 | it "should raise AmbiguousMethod when calling :off?" do 95 | t=Tractor.new 96 | lambda { t.off? }.should raise_error(EnumeratedAttribute::AmbiguousMethod) 97 | end 98 | 99 | it "should raise AmbiguousMethod when calling :in_reverse?" do 100 | t=Tractor.new 101 | lambda {t.in_reverse?}.should raise_error(EnumeratedAttribute::AmbiguousMethod) 102 | end 103 | 104 | it "should raise AmbiguousMethod when calling :not_reverse?" do 105 | t=Tractor.new 106 | lambda {t.not_reverse?}.should raise_error(EnumeratedAttribute::AmbiguousMethod) 107 | end 108 | 109 | it "should initialize :gear for two instances of the same class" do 110 | t=Tractor.new 111 | t.gear.should == :neutral 112 | s=Tractor.new 113 | s.gear.should == :neutral 114 | end 115 | 116 | it "should dynamically create :plow_nil? and :plow_not_nil?" do 117 | t=Tractor.new 118 | t.plow_nil?.should be_false 119 | t.plow_not_nil?.should be_true 120 | t.plow = nil 121 | t.plow_not_nil?.should be_false 122 | t.plow_nil?.should be_true 123 | Tractor.should define_instance_method(:plow_nil?) 124 | Tractor.should define_instance_method(:plow_not_nil?) 125 | end 126 | 127 | it "should dynamically create :plow_is_nil? and :plow_is_not_nil?" do 128 | t=Tractor.new 129 | t.plow_is_nil?.should be_false 130 | t.plow_is_not_nil?.should be_true 131 | t.plow = nil 132 | t.plow_is_not_nil?.should be_false 133 | t.plow_is_nil?.should be_true 134 | Tractor.should define_instance_method(:plow_is_nil?) 135 | Tractor.should define_instance_method(:plow_is_not_nil?) 136 | end 137 | 138 | it "should negate result for not_parked? defined with is_not" do 139 | t=Tractor.new 140 | t.gear = :neutral 141 | t.not_parked?.should be_false 142 | end 143 | 144 | it "should negate result for not_driving? defined with is_not" do 145 | t=Tractor.new 146 | t.gear = :neutral 147 | t.not_driving?.should be_true 148 | end 149 | 150 | =begin 151 | it "should have getter but no setter for :temperature" do 152 | Tractor.instance_methods.should_not include('temperature=') 153 | Tractor.instance_methods.should include('temperature') 154 | end 155 | 156 | it "should have setter but no getter for :ignition" do 157 | Tractor.instance_methods.should_not include('ignition') 158 | Tractor.instance_methods.should include('ignition=') 159 | end 160 | =end 161 | 162 | it "should be able to set :plow to nil" do 163 | t=Tractor.new 164 | lambda { t.plow = nil }.should_not raise_error(EnumeratedAttribute::InvalidEnumeration) 165 | end 166 | 167 | it "should have method :plow_nil? that operates correctly" do 168 | t=Tractor.new 169 | t.plow.should_not be_nil 170 | t.plow_nil?.should be_false 171 | t.plow = nil 172 | t.plow.should be_nil 173 | t.plow_nil?.should be_true 174 | end 175 | 176 | it "should raise EnumeratedAttribute::InvalidEnumeration when setting :gear to nil" do 177 | t=Tractor.new 178 | lambda{ t.gear = nil }.should raise_error(EnumeratedAttribute::InvalidEnumeration) 179 | end 180 | 181 | it "should have respond_to? method for :gear_is_in_neutral?" do 182 | t=Tractor.new 183 | t.respond_to?('gear_is_in_neutral?').should be_true 184 | end 185 | 186 | it "should have respond_to? method for :side_light_is_super_high?" do 187 | t=Tractor.new 188 | t.respond_to?(:side_light_is_super_high?).should be_true 189 | end 190 | 191 | it "should not have respond_to? method for :gear_is_in_high?" do 192 | t=Tractor.new 193 | t.respond_to?(:gear_is_in_high?).should be_false 194 | end 195 | 196 | it "should return true when calling respond_to? for :gear_is_in_neutral? with an optional argument" do 197 | t=Tractor.new 198 | t.respond_to?(:gear_is_in_neutral?, true).should be_true 199 | t.respond_to?(:gear_is_in_neutral?, false).should be_true 200 | end 201 | 202 | it "should return true when called respond_to? for :gear_is_in_high? with an optional argument" do 203 | t=Tractor.new 204 | t.respond_to?(:gear_is_in_high?, true).should be_false 205 | t.respond_to?(:gear_is_in_high?, false).should be_false 206 | end 207 | 208 | it "should initially set :plow to :up" do 209 | t=Tractor.new 210 | t.plow.should == :up 211 | end 212 | 213 | it "should have plowing? state method" do 214 | t=Tractor.new 215 | t.plowing?.should be_false 216 | t.plow=:down 217 | t.plowing?.should be_false 218 | t.gear= :first 219 | t.plowing?.should be_true 220 | t.plow=:up 221 | t.plowing?.should be_false 222 | end 223 | 224 | it "should have :side_light_up incrementor" do 225 | t=Tractor.new 226 | t.side_light = :off 227 | t.side_light_up.should == :low 228 | t.side_light_up.should == :high 229 | t.side_light_up.should == :super_high 230 | t.side_light_up.should == :off 231 | end 232 | 233 | it "should have :side_light_down incrementor" do 234 | t=Tractor.new 235 | t.side_light_down.should == :super_high 236 | t.side_light_down.should == :high 237 | t.side_light_down.should == :low 238 | t.side_light_down.should == :off 239 | end 240 | 241 | it "should have :turn_lights_up incrementor" do 242 | t=Tractor.new 243 | t.lights = :off 244 | t.turn_lights_up.should == :low 245 | t.turn_lights_up.should == :high 246 | end 247 | 248 | it "should have :turn_lights_down decrementor" do 249 | t=Tractor.new 250 | t.lights=:high 251 | t.turn_lights_down.should == :low 252 | t.turn_lights_down.should == :off 253 | end 254 | 255 | it "should have :gear_previous which wraps from :neutral to :over_drive" do 256 | t=Tractor.new 257 | t.gear_previous.should == :reverse 258 | t.gear.should == :reverse 259 | t.gear_previous.should == :over_drive 260 | t.gear.should == :over_drive 261 | end 262 | 263 | it "should have :gear_next which wraps from :second to :reverse" do 264 | t=Tractor.new 265 | t.gear = :second 266 | t.gear_next.should == :over_drive 267 | t.gear.should == :over_drive 268 | t.gear_next.should == :reverse 269 | t.gear.should == :reverse 270 | end 271 | 272 | it "should have :upshift which increments :gear from :neutral to :over_drive without wrapping" do 273 | t=Tractor.new 274 | t.upshift.should == :first 275 | t.upshift.should == :second 276 | t.upshift.should == :over_drive 277 | t.upshift.should == :over_drive 278 | end 279 | 280 | it "should have :downshift which decrements :gear from :over_drive to :neutral without wrapping" do 281 | t=Tractor.new 282 | t.gear = :over_drive 283 | t.downshift.should == :second 284 | t.downshift.should == :first 285 | t.downshift.should == :neutral 286 | t.downshift.should == :neutral 287 | end 288 | 289 | it "should have parked? method" do 290 | t=Tractor.new 291 | t.parked?.should be_true 292 | t.gear = :reverse 293 | t.parked?.should be_false 294 | end 295 | 296 | it "should have driving? method" do 297 | t=Tractor.new 298 | t.driving?.should be_false 299 | [:first, :second, :over_drive].each do |g| 300 | t.gear=g 301 | t.driving?.should be_true 302 | end 303 | end 304 | 305 | it "should initially set side_light to :off" do 306 | t=Tractor.new 307 | t.side_light.should == :off 308 | end 309 | 310 | it "should have side_light_enums method" do 311 | t = Tractor.new 312 | t.side_light_enums.should == Tractor::SIDE_LIGHT_ENUM_VALUES 313 | end 314 | 315 | it "should have state method side_light_is_off?" do 316 | t=Tractor.new 317 | t.side_light_is_off?.should be_true 318 | end 319 | 320 | it "should have state method side_light_is_super_high?" do 321 | t=Tractor.new 322 | t.side_light_is_super_high?.should be_false 323 | end 324 | 325 | it "should initially set :gear to :neutral" do 326 | t=Tractor.new 327 | t.gear.should == :neutral 328 | end 329 | 330 | it "should set lights initially to :off" do 331 | t=Tractor.new 332 | t.lights.should == :off 333 | end 334 | 335 | it "should create a lights_enums method for all light enumerated values" do 336 | t=Tractor.new 337 | t.lights_enums.should == Tractor::LIGHTS_ENUM_VALUES 338 | end 339 | 340 | it "should initially set lights to :off" do 341 | t=Tractor.new 342 | t.lights.should equal(:off) 343 | end 344 | 345 | it "should have dynamic state methods for :reverse and :neutral" do 346 | t = Tractor.new 347 | t.gear_is_in_reverse?.should be_false 348 | t.gear_is_in_neutral?.should be_true 349 | end 350 | 351 | it "should have negative dynamic state methods for :reverses and :neutral" do 352 | t = Tractor.new 353 | t.gear_is_not_in_reverse?.should be_true 354 | t.gear_is_not_in_neutral?.should be_false 355 | end 356 | 357 | it "should have negative and positive dynamic state methods for :over_drive" do 358 | t = Tractor.new 359 | t.gear_is_in_over_drive?.should be_false 360 | t.gear_is_not_in_over_drive?.should be_true 361 | end 362 | 363 | it "should have created instance methods for :reverse" do 364 | Tractor.should define_instance_method(:gear_is_in_reverse?) 365 | Tractor.should define_instance_method(:gear_is_not_in_reverse?) 366 | end 367 | 368 | it "should have created instance methods for :neutral" do 369 | Tractor.should define_instance_method(:gear_is_in_neutral?) 370 | Tractor.should define_instance_method(:gear_is_not_in_neutral?) 371 | end 372 | 373 | it "should have created instance methods for :over_drive" do 374 | Tractor.should define_instance_method :gear_is_in_over_drive? 375 | Tractor.should define_instance_method :gear_is_not_in_over_drive? 376 | end 377 | 378 | it "should raise NoMethodError for dynamic state methods not querying valid enumeration values" do 379 | t = Tractor.new 380 | lambda { t.gear_is_in_high? }.should raise_error(NoMethodError) 381 | end 382 | 383 | it "should convert string values to symbols for attr setters" do 384 | t = Tractor.new 385 | t.gear= 'reverse' 386 | t.gear.should == :reverse 387 | end 388 | 389 | it "should have instance method gears equal to enumeration array" do 390 | Tractor.new.gears.should == Tractor::GEAR_ENUM_VALUES 391 | end 392 | 393 | it "should have gear attribute initialized to :neutral" do 394 | t = Tractor.new 395 | t.gear.should == :neutral 396 | end 397 | 398 | it "should set gear attribute to :first" do 399 | t = Tractor.new 400 | t.gear = :first 401 | t.gear.should == :first 402 | end 403 | 404 | it "should raise error when set gear attribute to :broken" do 405 | t = Tractor.new 406 | lambda { t.gear= :broken }.should raise_error(EnumeratedAttribute::InvalidEnumeration) 407 | end 408 | 409 | it "should have name attribute initially set to 'old faithful'" do 410 | t = Tractor.new 411 | t.name.should == 'old faithful' 412 | end 413 | 414 | it "should set name attribute to 'broke n busted'" do 415 | t = Tractor.new 416 | t.name = 'broke n busted' 417 | t.name.should == 'broke n busted' 418 | end 419 | 420 | end 421 | -------------------------------------------------------------------------------- /spec/rails/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | **/*.log 3 | pkg 4 | rdoc 5 | coverage 6 | doc 7 | log 8 | nbproject 9 | tmp 10 | vendor 11 | lib/tasks/* 12 | TODO.rdoc -------------------------------------------------------------------------------- /spec/rails/README: -------------------------------------------------------------------------------- 1 | == Welcome to Rails 2 | 3 | Rails is a web-application framework that includes everything needed to create 4 | database-backed web applications according to the Model-View-Control pattern. 5 | 6 | This pattern splits the view (also called the presentation) into "dumb" templates 7 | that are primarily responsible for inserting pre-built data in between HTML tags. 8 | The model contains the "smart" domain objects (such as Account, Product, Person, 9 | Post) that holds all the business logic and knows how to persist themselves to 10 | a database. The controller handles the incoming requests (such as Save New Account, 11 | Update Product, Show Post) by manipulating the model and directing data to the view. 12 | 13 | In Rails, the model is handled by what's called an object-relational mapping 14 | layer entitled Active Record. This layer allows you to present the data from 15 | database rows as objects and embellish these data objects with business logic 16 | methods. You can read more about Active Record in 17 | link:files/vendor/rails/activerecord/README.html. 18 | 19 | The controller and view are handled by the Action Pack, which handles both 20 | layers by its two parts: Action View and Action Controller. These two layers 21 | are bundled in a single package due to their heavy interdependence. This is 22 | unlike the relationship between the Active Record and Action Pack that is much 23 | more separate. Each of these packages can be used independently outside of 24 | Rails. You can read more about Action Pack in 25 | link:files/vendor/rails/actionpack/README.html. 26 | 27 | 28 | == Getting Started 29 | 30 | 1. At the command prompt, start a new Rails application using the rails command 31 | and your application name. Ex: rails myapp 32 | 2. Change directory into myapp and start the web server: script/server (run with --help for options) 33 | 3. Go to http://localhost:3000/ and get "Welcome aboard: You're riding the Rails!" 34 | 4. Follow the guidelines to start developing your application 35 | 36 | 37 | == Web Servers 38 | 39 | By default, Rails will try to use Mongrel if it's are installed when started with script/server, otherwise Rails will use WEBrick, the webserver that ships with Ruby. But you can also use Rails 40 | with a variety of other web servers. 41 | 42 | Mongrel is a Ruby-based webserver with a C component (which requires compilation) that is 43 | suitable for development and deployment of Rails applications. If you have Ruby Gems installed, 44 | getting up and running with mongrel is as easy as: gem install mongrel. 45 | More info at: http://mongrel.rubyforge.org 46 | 47 | Say other Ruby web servers like Thin and Ebb or regular web servers like Apache or LiteSpeed or 48 | Lighttpd or IIS. The Ruby web servers are run through Rack and the latter can either be setup to use 49 | FCGI or proxy to a pack of Mongrels/Thin/Ebb servers. 50 | 51 | == Apache .htaccess example for FCGI/CGI 52 | 53 | # General Apache options 54 | AddHandler fastcgi-script .fcgi 55 | AddHandler cgi-script .cgi 56 | Options +FollowSymLinks +ExecCGI 57 | 58 | # If you don't want Rails to look in certain directories, 59 | # use the following rewrite rules so that Apache won't rewrite certain requests 60 | # 61 | # Example: 62 | # RewriteCond %{REQUEST_URI} ^/notrails.* 63 | # RewriteRule .* - [L] 64 | 65 | # Redirect all requests not available on the filesystem to Rails 66 | # By default the cgi dispatcher is used which is very slow 67 | # 68 | # For better performance replace the dispatcher with the fastcgi one 69 | # 70 | # Example: 71 | # RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] 72 | RewriteEngine On 73 | 74 | # If your Rails application is accessed via an Alias directive, 75 | # then you MUST also set the RewriteBase in this htaccess file. 76 | # 77 | # Example: 78 | # Alias /myrailsapp /path/to/myrailsapp/public 79 | # RewriteBase /myrailsapp 80 | 81 | RewriteRule ^$ index.html [QSA] 82 | RewriteRule ^([^.]+)$ $1.html [QSA] 83 | RewriteCond %{REQUEST_FILENAME} !-f 84 | RewriteRule ^(.*)$ dispatch.cgi [QSA,L] 85 | 86 | # In case Rails experiences terminal errors 87 | # Instead of displaying this message you can supply a file here which will be rendered instead 88 | # 89 | # Example: 90 | # ErrorDocument 500 /500.html 91 | 92 | ErrorDocument 500 "

Application error

Rails application failed to start properly" 93 | 94 | 95 | == Debugging Rails 96 | 97 | Sometimes your application goes wrong. Fortunately there are a lot of tools that 98 | will help you debug it and get it back on the rails. 99 | 100 | First area to check is the application log files. Have "tail -f" commands running 101 | on the server.log and development.log. Rails will automatically display debugging 102 | and runtime information to these files. Debugging info will also be shown in the 103 | browser on requests from 127.0.0.1. 104 | 105 | You can also log your own messages directly into the log file from your code using 106 | the Ruby logger class from inside your controllers. Example: 107 | 108 | class WeblogController < ActionController::Base 109 | def destroy 110 | @weblog = Weblog.find(params[:id]) 111 | @weblog.destroy 112 | logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") 113 | end 114 | end 115 | 116 | The result will be a message in your log file along the lines of: 117 | 118 | Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1 119 | 120 | More information on how to use the logger is at http://www.ruby-doc.org/core/ 121 | 122 | Also, Ruby documentation can be found at http://www.ruby-lang.org/ including: 123 | 124 | * The Learning Ruby (Pickaxe) Book: http://www.ruby-doc.org/docs/ProgrammingRuby/ 125 | * Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) 126 | 127 | These two online (and free) books will bring you up to speed on the Ruby language 128 | and also on programming in general. 129 | 130 | 131 | == Debugger 132 | 133 | Debugger support is available through the debugger command when you start your Mongrel or 134 | Webrick server with --debugger. This means that you can break out of execution at any point 135 | in the code, investigate and change the model, AND then resume execution! 136 | You need to install ruby-debug to run the server in debugging mode. With gems, use 'gem install ruby-debug' 137 | Example: 138 | 139 | class WeblogController < ActionController::Base 140 | def index 141 | @posts = Post.find(:all) 142 | debugger 143 | end 144 | end 145 | 146 | So the controller will accept the action, run the first line, then present you 147 | with a IRB prompt in the server window. Here you can do things like: 148 | 149 | >> @posts.inspect 150 | => "[#nil, \"body\"=>nil, \"id\"=>\"1\"}>, 151 | #\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]" 152 | >> @posts.first.title = "hello from a debugger" 153 | => "hello from a debugger" 154 | 155 | ...and even better is that you can examine how your runtime objects actually work: 156 | 157 | >> f = @posts.first 158 | => #nil, "body"=>nil, "id"=>"1"}> 159 | >> f. 160 | Display all 152 possibilities? (y or n) 161 | 162 | Finally, when you're ready to resume execution, you enter "cont" 163 | 164 | 165 | == Console 166 | 167 | You can interact with the domain model by starting the console through script/console. 168 | Here you'll have all parts of the application configured, just like it is when the 169 | application is running. You can inspect domain models, change values, and save to the 170 | database. Starting the script without arguments will launch it in the development environment. 171 | Passing an argument will specify a different environment, like script/console production. 172 | 173 | To reload your controllers and models after launching the console run reload! 174 | 175 | == dbconsole 176 | 177 | You can go to the command line of your database directly through script/dbconsole. 178 | You would be connected to the database with the credentials defined in database.yml. 179 | Starting the script without arguments will connect you to the development database. Passing an 180 | argument will connect you to a different database, like script/dbconsole production. 181 | Currently works for mysql, postgresql and sqlite. 182 | 183 | == Description of Contents 184 | 185 | app 186 | Holds all the code that's specific to this particular application. 187 | 188 | app/controllers 189 | Holds controllers that should be named like weblogs_controller.rb for 190 | automated URL mapping. All controllers should descend from ApplicationController 191 | which itself descends from ActionController::Base. 192 | 193 | app/models 194 | Holds models that should be named like post.rb. 195 | Most models will descend from ActiveRecord::Base. 196 | 197 | app/views 198 | Holds the template files for the view that should be named like 199 | weblogs/index.html.erb for the WeblogsController#index action. All views use eRuby 200 | syntax. 201 | 202 | app/views/layouts 203 | Holds the template files for layouts to be used with views. This models the common 204 | header/footer method of wrapping views. In your views, define a layout using the 205 | layout :default and create a file named default.html.erb. Inside default.html.erb, 206 | call <% yield %> to render the view using this layout. 207 | 208 | app/helpers 209 | Holds view helpers that should be named like weblogs_helper.rb. These are generated 210 | for you automatically when using script/generate for controllers. Helpers can be used to 211 | wrap functionality for your views into methods. 212 | 213 | config 214 | Configuration files for the Rails environment, the routing map, the database, and other dependencies. 215 | 216 | db 217 | Contains the database schema in schema.rb. db/migrate contains all 218 | the sequence of Migrations for your schema. 219 | 220 | doc 221 | This directory is where your application documentation will be stored when generated 222 | using rake doc:app 223 | 224 | lib 225 | Application specific libraries. Basically, any kind of custom code that doesn't 226 | belong under controllers, models, or helpers. This directory is in the load path. 227 | 228 | public 229 | The directory available for the web server. Contains subdirectories for images, stylesheets, 230 | and javascripts. Also contains the dispatchers and the default HTML files. This should be 231 | set as the DOCUMENT_ROOT of your web server. 232 | 233 | script 234 | Helper scripts for automation and generation. 235 | 236 | test 237 | Unit and functional tests along with fixtures. When using the script/generate scripts, template 238 | test files will be generated for you and placed in this directory. 239 | 240 | vendor 241 | External libraries that the application depends on. Also includes the plugins subdirectory. 242 | If the app has frozen rails, those gems also go here, under vendor/rails/. 243 | This directory is in the load path. 244 | -------------------------------------------------------------------------------- /spec/rails/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require(File.join(File.dirname(__FILE__), 'config', 'boot')) 5 | 6 | require 'rake' 7 | require 'rake/testtask' 8 | require 'rake/rdoctask' 9 | 10 | require 'tasks/rails' 11 | -------------------------------------------------------------------------------- /spec/rails/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | # Filters added to this controller apply to all controllers in the application. 2 | # Likewise, all the methods added will be available for all controllers. 3 | 4 | class ApplicationController < ActionController::Base 5 | helper :all # include all helpers, all the time 6 | protect_from_forgery # See ActionController::RequestForgeryProtection for details 7 | 8 | # Scrub sensitive parameters from your log 9 | # filter_parameter_logging :password 10 | end 11 | -------------------------------------------------------------------------------- /spec/rails/app/controllers/form_test_controller.rb: -------------------------------------------------------------------------------- 1 | class FormTestController < ApplicationController 2 | 3 | def index 4 | end 5 | 6 | #ActionView::Helpers:ActiveRecordHelper 7 | def form 8 | @user = User.new(params[:user]) 9 | return if request.get? 10 | 11 | return unless @user.save 12 | redirect_to :action=>:index 13 | end 14 | 15 | #ActionView::Helpers::FormHelper 16 | def form_for 17 | @user = User.new(params[:user]) 18 | return if request.get? 19 | 20 | return unless @user.save 21 | redirect_to :action=>:index 22 | end 23 | 24 | #ActionView::Helpers::FormTagHelper 25 | def form_tag 26 | @user = User.new(params[:user]) 27 | return if request.get? 28 | 29 | return unless @user.save 30 | redirect_to :action=>:index 31 | end 32 | 33 | #ActionView::Helpers::FormOptionsHelper 34 | # def select 35 | # 36 | # end 37 | 38 | end 39 | -------------------------------------------------------------------------------- /spec/rails/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | # Methods added to this helper will be available to all templates in the application. 2 | module ApplicationHelper 3 | end 4 | -------------------------------------------------------------------------------- /spec/rails/app/helpers/form_test_helper.rb: -------------------------------------------------------------------------------- 1 | module FormTestHelper 2 | end 3 | -------------------------------------------------------------------------------- /spec/rails/app/models/user.rb: -------------------------------------------------------------------------------- 1 | require 'enumerated_attribute' 2 | class User < ActiveRecord::Base 3 | validates_presence_of :first_name, :gender, :age, :status, :degree 4 | validates_numericality_of :age 5 | 6 | enum_attr :gender, %w(male female) 7 | enum_attr :status, %w(single married divorced widowed) 8 | enum_attr :degree, %w(^none high_school college graduate) 9 | end 10 | -------------------------------------------------------------------------------- /spec/rails/app/views/form_test/form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form 'user', :action=>:form, :submit_value=>'Save' %> 2 | -------------------------------------------------------------------------------- /spec/rails/app/views/form_test/form_for.html.erb: -------------------------------------------------------------------------------- 1 | <% form_for :user do |f| %> 2 | <%= content_tag('div', f.error_messages) if f.error_messages %> 3 | <%= f.label :first_name%>: <%= f.text_field :first_name %>
4 | <%= f.label :age %>: <%= f.text_field :age %>
5 | <%= f.label :gender %>: <%= f.enum_select :gender %>
6 | <%= f.label :DOB %>: <%= date_select(:user, :DOB) %>
7 | <%= f.label :degree %>: <%= f.enum_select :degree %>
8 | <%= f.label :status %>: <%= f.enum_select :status %>
9 | <%= submit_tag 'Save' %> 10 | <% end %> 11 | -------------------------------------------------------------------------------- /spec/rails/app/views/form_test/form_tag.html.erb: -------------------------------------------------------------------------------- 1 | <% form_tag :action=>:form_tag do %> 2 | <%= label_tag 'First name' %>: <%= text_field :user, :first_name %>
3 | <%= label_tag 'Gender' %>: <%= enum_select :user, :gender %>
4 | <%= label_tag 'Age' %>: <%= text_field :user, :age %>
5 | <%= label_tag 'DOB' %>: <%= date_select(:user, :DOB) %>
6 | <%= label_tag 'Degree' %>: <%= enum_select :user, :degree %>
7 | <%= label_tag 'Status' %>: <%= enum_select :user, :status %>
8 | <%= submit_tag 'Save' %> 9 | <% end %> 10 | -------------------------------------------------------------------------------- /spec/rails/app/views/form_test/index.html.erb: -------------------------------------------------------------------------------- 1 | 2 |
<%=link_to 'form test', :action=>:form %>
3 | 4 |
<%=link_to 'form_tag test', :action=>:form_tag %>
5 | 6 |
<%=link_to 'form_for test', :action=>:form_for %>
7 | -------------------------------------------------------------------------------- /spec/rails/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= stylesheet_link_tag 'scaffold' %> 5 | 6 | 7 | 8 | 9 | <%= yield :layout %> 10 | 11 | -------------------------------------------------------------------------------- /spec/rails/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Don't change this file! 2 | # Configure your app in config/environment.rb and config/environments/*.rb 3 | 4 | RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT) 5 | 6 | module Rails 7 | class << self 8 | def boot! 9 | unless booted? 10 | preinitialize 11 | pick_boot.run 12 | end 13 | end 14 | 15 | def booted? 16 | defined? Rails::Initializer 17 | end 18 | 19 | def pick_boot 20 | (vendor_rails? ? VendorBoot : GemBoot).new 21 | end 22 | 23 | def vendor_rails? 24 | File.exist?("#{RAILS_ROOT}/vendor/rails") 25 | end 26 | 27 | def preinitialize 28 | load(preinitializer_path) if File.exist?(preinitializer_path) 29 | end 30 | 31 | def preinitializer_path 32 | "#{RAILS_ROOT}/config/preinitializer.rb" 33 | end 34 | end 35 | 36 | class Boot 37 | def run 38 | load_initializer 39 | Rails::Initializer.run(:set_load_path) 40 | end 41 | end 42 | 43 | class VendorBoot < Boot 44 | def load_initializer 45 | require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" 46 | Rails::Initializer.run(:install_gem_spec_stubs) 47 | Rails::GemDependency.add_frozen_gem_path 48 | end 49 | end 50 | 51 | class GemBoot < Boot 52 | def load_initializer 53 | self.class.load_rubygems 54 | load_rails_gem 55 | require 'initializer' 56 | end 57 | 58 | def load_rails_gem 59 | if version = self.class.gem_version 60 | gem 'rails', version 61 | else 62 | gem 'rails' 63 | end 64 | rescue Gem::LoadError => load_error 65 | $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.) 66 | exit 1 67 | end 68 | 69 | class << self 70 | def rubygems_version 71 | Gem::RubyGemsVersion rescue nil 72 | end 73 | 74 | def gem_version 75 | if defined? RAILS_GEM_VERSION 76 | RAILS_GEM_VERSION 77 | elsif ENV.include?('RAILS_GEM_VERSION') 78 | ENV['RAILS_GEM_VERSION'] 79 | else 80 | parse_gem_version(read_environment_rb) 81 | end 82 | end 83 | 84 | def load_rubygems 85 | require 'rubygems' 86 | min_version = '1.3.1' 87 | unless rubygems_version >= min_version 88 | $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.) 89 | exit 1 90 | end 91 | 92 | rescue LoadError 93 | $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org) 94 | exit 1 95 | end 96 | 97 | def parse_gem_version(text) 98 | $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/ 99 | end 100 | 101 | private 102 | def read_environment_rb 103 | File.read("#{RAILS_ROOT}/config/environment.rb") 104 | end 105 | end 106 | end 107 | end 108 | 109 | # All that for this: 110 | Rails.boot! 111 | -------------------------------------------------------------------------------- /spec/rails/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3-ruby (not necessary on OS X Leopard) 3 | development: 4 | adapter: sqlite3 5 | database: db/development.sqlite3 6 | pool: 5 7 | timeout: 5000 8 | 9 | # Warning: The database defined as "test" will be erased and 10 | # re-generated from your development database when you run "rake". 11 | # Do not set this db to the same as development or production. 12 | test: 13 | adapter: sqlite3 14 | database: db/test.sqlite3 15 | pool: 5 16 | timeout: 5000 17 | 18 | production: 19 | adapter: sqlite3 20 | database: db/production.sqlite3 21 | pool: 5 22 | timeout: 5000 23 | -------------------------------------------------------------------------------- /spec/rails/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file 2 | 3 | # Specifies gem version of Rails to use when vendor/rails is not present 4 | RAILS_GEM_VERSION = '< 2.4.0' unless defined? RAILS_GEM_VERSION 5 | 6 | # Bootstrap the Rails environment, frameworks, and default configuration 7 | require File.join(File.dirname(__FILE__), 'boot') 8 | 9 | Rails::Initializer.run do |config| 10 | # Settings in config/environments/* take precedence over those specified here. 11 | # Application configuration should go into files in config/initializers 12 | # -- all .rb files in that directory are automatically loaded. 13 | 14 | # Add additional load paths for your own custom dirs 15 | # config.load_paths += %W( #{RAILS_ROOT}/extras ) 16 | 17 | # Specify gems that this application depends on and have them installed with rake gems:install 18 | # config.gem "bj" 19 | # config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net" 20 | # config.gem "sqlite3-ruby", :lib => "sqlite3" 21 | # config.gem "aws-s3", :lib => "aws/s3" 22 | 23 | 24 | # Only load the plugins named here, in the order given (default is alphabetical). 25 | # :all can be used as a placeholder for all plugins not explicitly named 26 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 27 | 28 | # Skip frameworks you're not going to use. To use Rails without a database, 29 | # you must remove the Active Record framework. 30 | # config.frameworks -= [ :active_record, :active_resource, :action_mailer ] 31 | 32 | # Activate observers that should always be running 33 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 34 | 35 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 36 | # Run "rake -D time" for a list of tasks for finding time zone names. 37 | config.time_zone = 'UTC' 38 | 39 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 40 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')] 41 | # config.i18n.default_locale = :de 42 | end 43 | 44 | $:.unshift File.dirname(__FILE__)+'/../../../lib' 45 | require 'enumerated_attribute' 46 | -------------------------------------------------------------------------------- /spec/rails/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | # Settings specified here will take precedence over those in config/environment.rb 2 | 3 | # In the development environment your application's code is reloaded on 4 | # every request. This slows down response time but is perfect for development 5 | # since you don't have to restart the webserver when you make code changes. 6 | config.cache_classes = false 7 | 8 | # Log error messages when you accidentally call methods on nil. 9 | config.whiny_nils = true 10 | 11 | # Show full error reports and disable caching 12 | config.action_controller.consider_all_requests_local = true 13 | config.action_view.debug_rjs = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send 17 | config.action_mailer.raise_delivery_errors = false -------------------------------------------------------------------------------- /spec/rails/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | # Settings specified here will take precedence over those in config/environment.rb 2 | 3 | # The production environment is meant for finished, "live" apps. 4 | # Code is not reloaded between requests 5 | config.cache_classes = true 6 | 7 | # Full error reports are disabled and caching is turned on 8 | config.action_controller.consider_all_requests_local = false 9 | config.action_controller.perform_caching = true 10 | config.action_view.cache_template_loading = true 11 | 12 | # See everything in the log (default is :info) 13 | # config.log_level = :debug 14 | 15 | # Use a different logger for distributed setups 16 | # config.logger = SyslogLogger.new 17 | 18 | # Use a different cache store in production 19 | # config.cache_store = :mem_cache_store 20 | 21 | # Enable serving of images, stylesheets, and javascripts from an asset server 22 | # config.action_controller.asset_host = "http://assets.example.com" 23 | 24 | # Disable delivery errors, bad email addresses will be ignored 25 | # config.action_mailer.raise_delivery_errors = false 26 | 27 | # Enable threaded mode 28 | # config.threadsafe! -------------------------------------------------------------------------------- /spec/rails/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # Settings specified here will take precedence over those in config/environment.rb 2 | 3 | # The test environment is used exclusively to run your application's 4 | # test suite. You never need to work with it otherwise. Remember that 5 | # your test database is "scratch space" for the test suite and is wiped 6 | # and recreated between test runs. Don't rely on the data there! 7 | config.cache_classes = true 8 | 9 | # Log error messages when you accidentally call methods on nil. 10 | config.whiny_nils = true 11 | 12 | # Show full error reports and disable caching 13 | config.action_controller.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | config.action_view.cache_template_loading = true 16 | 17 | # Disable request forgery protection in test environment 18 | config.action_controller.allow_forgery_protection = false 19 | 20 | # Tell Action Mailer not to deliver emails to the real world. 21 | # The :test delivery method accumulates sent emails in the 22 | # ActionMailer::Base.deliveries array. 23 | config.action_mailer.delivery_method = :test 24 | 25 | # Use SQL instead of Active Record's schema dumper when creating the test database. 26 | # This is necessary if your schema can't be completely dumped by the schema dumper, 27 | # like if you have constraints or database-specific column types 28 | # config.active_record.schema_format = :sql -------------------------------------------------------------------------------- /spec/rails/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying do debug a problem that might steem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! -------------------------------------------------------------------------------- /spec/rails/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | -------------------------------------------------------------------------------- /spec/rails/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /spec/rails/config/initializers/new_rails_defaults.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # These settings change the behavior of Rails 2 apps and will be defaults 4 | # for Rails 3. You can remove this initializer when Rails 3 is released. 5 | 6 | if defined?(ActiveRecord) 7 | # Include Active Record class name as root for JSON serialized output. 8 | ActiveRecord::Base.include_root_in_json = true 9 | 10 | # Store the full class name (including module namespace) in STI type column. 11 | ActiveRecord::Base.store_full_sti_class = true 12 | end 13 | 14 | # Use ISO 8601 format for JSON serialized times and dates. 15 | ActiveSupport.use_standard_json_time_format = true 16 | 17 | # Don't escape HTML entities in JSON, leave that for the #json_escape helper. 18 | # if you're including raw json in an HTML page. 19 | ActiveSupport.escape_html_entities_in_json = false -------------------------------------------------------------------------------- /spec/rails/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying cookie session data integrity. 4 | # If you change this key, all old sessions will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | ActionController::Base.session = { 8 | :key => '_rails_session', 9 | :secret => '7cb35e777af9e2ff81ebe4a7cccc55ff93ae7a300e34ba8f381531c4ff95ec551e7c29f0b6a433ba5389d8b51df78338a7157f58f9c052b45d7063a7c5dfa458' 10 | } 11 | 12 | # Use the database for sessions instead of the cookie-based default, 13 | # which shouldn't be used to store highly confidential information 14 | # (create the session table with "rake db:sessions:create") 15 | # ActionController::Base.session_store = :active_record_store 16 | -------------------------------------------------------------------------------- /spec/rails/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" -------------------------------------------------------------------------------- /spec/rails/config/routes.rb: -------------------------------------------------------------------------------- 1 | ActionController::Routing::Routes.draw do |map| 2 | # The priority is based upon order of creation: first created -> highest priority. 3 | 4 | # Sample of regular route: 5 | # map.connect 'products/:id', :controller => 'catalog', :action => 'view' 6 | # Keep in mind you can assign values other than :controller and :action 7 | 8 | # Sample of named route: 9 | # map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase' 10 | # This route can be invoked with purchase_url(:id => product.id) 11 | 12 | # Sample resource route (maps HTTP verbs to controller actions automatically): 13 | # map.resources :products 14 | 15 | # Sample resource route with options: 16 | # map.resources :products, :member => { :short => :get, :toggle => :post }, :collection => { :sold => :get } 17 | 18 | # Sample resource route with sub-resources: 19 | # map.resources :products, :has_many => [ :comments, :sales ], :has_one => :seller 20 | 21 | # Sample resource route with more complex sub-resources 22 | # map.resources :products do |products| 23 | # products.resources :comments 24 | # products.resources :sales, :collection => { :recent => :get } 25 | # end 26 | 27 | # Sample resource route within a namespace: 28 | # map.namespace :admin do |admin| 29 | # # Directs /admin/products/* to Admin::ProductsController (app/controllers/admin/products_controller.rb) 30 | # admin.resources :products 31 | # end 32 | 33 | # You can have the root of your site routed with map.root -- just remember to delete public/index.html. 34 | # map.root :controller => "welcome" 35 | 36 | # See how all your routes lay out with "rake routes" 37 | 38 | # Install the default routes as the lowest priority. 39 | # Note: These default routes make all actions in every controller accessible via GET requests. You should 40 | # consider removing the them or commenting them out if you're using named routes and resources. 41 | map.connect ':controller/:action/:id' 42 | map.connect ':controller/:action/:id.:format' 43 | end 44 | -------------------------------------------------------------------------------- /spec/rails/db/development.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffp/enumerated_attribute/84221b872f7f71152994b212520c833742a263e3/spec/rails/db/development.sqlite3 -------------------------------------------------------------------------------- /spec/rails/db/migrate/20090804230221_create_sessions.rb: -------------------------------------------------------------------------------- 1 | class CreateSessions < ActiveRecord::Migration 2 | def self.up 3 | create_table :sessions do |t| 4 | t.string :session_id, :null => false 5 | t.text :data 6 | t.timestamps 7 | end 8 | 9 | add_index :sessions, :session_id 10 | add_index :sessions, :updated_at 11 | end 12 | 13 | def self.down 14 | drop_table :sessions 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/rails/db/migrate/20090804230546_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration 2 | def self.up 3 | create_table :users do |t| 4 | t.string :first_name 5 | t.integer :age 6 | t.enum :gender 7 | #t.string :gender 8 | t.date :DOB 9 | t.column :degree, :enum 10 | t.enum :status 11 | #t.string :degree 12 | #t.string :status 13 | 14 | t.timestamps 15 | end 16 | end 17 | 18 | def self.down 19 | drop_table :users 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/rails/db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead of editing this file, 2 | # please use the migrations feature of Active Record to incrementally modify your database, and 3 | # then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your database schema. If you need 6 | # to create the application database on another system, you should be using db:schema:load, not running 7 | # all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations 8 | # you'll amass, the slower it'll run and the greater likelihood for issues). 9 | # 10 | # It's strongly recommended to check this file into your version control system. 11 | 12 | ActiveRecord::Schema.define(:version => 20090804230546) do 13 | 14 | create_table "sessions", :force => true do |t| 15 | t.string "session_id", :null => false 16 | t.text "data" 17 | t.datetime "created_at" 18 | t.datetime "updated_at" 19 | end 20 | 21 | add_index "sessions", ["session_id"], :name => "index_sessions_on_session_id" 22 | add_index "sessions", ["updated_at"], :name => "index_sessions_on_updated_at" 23 | 24 | create_table "users", :force => true do |t| 25 | t.string "first_name" 26 | t.integer "age" 27 | t.string "gender" 28 | t.date "DOB" 29 | t.string "degree" 30 | t.string "status" 31 | t.datetime "created_at" 32 | t.datetime "updated_at" 33 | end 34 | 35 | end 36 | -------------------------------------------------------------------------------- /spec/rails/db/test.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffp/enumerated_attribute/84221b872f7f71152994b212520c833742a263e3/spec/rails/db/test.sqlite3 -------------------------------------------------------------------------------- /spec/rails/public/404.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | The page you were looking for doesn't exist (404) 9 | 21 | 22 | 23 | 24 | 25 |
26 |

The page you were looking for doesn't exist.

27 |

You may have mistyped the address or the page may have moved.

28 |
29 | 30 | -------------------------------------------------------------------------------- /spec/rails/public/422.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | The change you wanted was rejected (422) 9 | 21 | 22 | 23 | 24 | 25 |
26 |

The change you wanted was rejected.

27 |

Maybe you tried to change something you didn't have access to.

28 |
29 | 30 | -------------------------------------------------------------------------------- /spec/rails/public/500.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | We're sorry, but something went wrong (500) 9 | 21 | 22 | 23 | 24 | 25 |
26 |

We're sorry, but something went wrong.

27 |

We've been notified about this issue and we'll take a look at it shortly.

28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /spec/rails/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffp/enumerated_attribute/84221b872f7f71152994b212520c833742a263e3/spec/rails/public/favicon.ico -------------------------------------------------------------------------------- /spec/rails/public/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffp/enumerated_attribute/84221b872f7f71152994b212520c833742a263e3/spec/rails/public/images/rails.png -------------------------------------------------------------------------------- /spec/rails/public/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Ruby on Rails: Welcome aboard 7 | 181 | 182 | 183 | 204 | 205 | 206 |
207 | 237 | 238 |
239 | 243 | 244 | 248 | 249 |
250 |

Getting started

251 |

Here’s how to get rolling:

252 | 253 |
    254 |
  1. 255 |

    Use script/generate to create your models and controllers

    256 |

    To see all available options, run it without parameters.

    257 |
  2. 258 | 259 |
  3. 260 |

    Set up a default route and remove or rename this file

    261 |

    Routes are set up in config/routes.rb.

    262 |
  4. 263 | 264 |
  5. 265 |

    Create your database

    266 |

    Run rake db:migrate to create your database. If you're not using SQLite (the default), edit config/database.yml with your username and password.

    267 |
  6. 268 |
269 |
270 |
271 | 272 | 273 |
274 | 275 | -------------------------------------------------------------------------------- /spec/rails/public/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // Place your application-specific JavaScript functions and classes here 2 | // This file is automatically included by javascript_include_tag :defaults 3 | -------------------------------------------------------------------------------- /spec/rails/public/javascripts/dragdrop.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 2 | // (c) 2005-2008 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) 3 | // 4 | // script.aculo.us is freely distributable under the terms of an MIT-style license. 5 | // For details, see the script.aculo.us web site: http://script.aculo.us/ 6 | 7 | if(Object.isUndefined(Effect)) 8 | throw("dragdrop.js requires including script.aculo.us' effects.js library"); 9 | 10 | var Droppables = { 11 | drops: [], 12 | 13 | remove: function(element) { 14 | this.drops = this.drops.reject(function(d) { return d.element==$(element) }); 15 | }, 16 | 17 | add: function(element) { 18 | element = $(element); 19 | var options = Object.extend({ 20 | greedy: true, 21 | hoverclass: null, 22 | tree: false 23 | }, arguments[1] || { }); 24 | 25 | // cache containers 26 | if(options.containment) { 27 | options._containers = []; 28 | var containment = options.containment; 29 | if(Object.isArray(containment)) { 30 | containment.each( function(c) { options._containers.push($(c)) }); 31 | } else { 32 | options._containers.push($(containment)); 33 | } 34 | } 35 | 36 | if(options.accept) options.accept = [options.accept].flatten(); 37 | 38 | Element.makePositioned(element); // fix IE 39 | options.element = element; 40 | 41 | this.drops.push(options); 42 | }, 43 | 44 | findDeepestChild: function(drops) { 45 | deepest = drops[0]; 46 | 47 | for (i = 1; i < drops.length; ++i) 48 | if (Element.isParent(drops[i].element, deepest.element)) 49 | deepest = drops[i]; 50 | 51 | return deepest; 52 | }, 53 | 54 | isContained: function(element, drop) { 55 | var containmentNode; 56 | if(drop.tree) { 57 | containmentNode = element.treeNode; 58 | } else { 59 | containmentNode = element.parentNode; 60 | } 61 | return drop._containers.detect(function(c) { return containmentNode == c }); 62 | }, 63 | 64 | isAffected: function(point, element, drop) { 65 | return ( 66 | (drop.element!=element) && 67 | ((!drop._containers) || 68 | this.isContained(element, drop)) && 69 | ((!drop.accept) || 70 | (Element.classNames(element).detect( 71 | function(v) { return drop.accept.include(v) } ) )) && 72 | Position.within(drop.element, point[0], point[1]) ); 73 | }, 74 | 75 | deactivate: function(drop) { 76 | if(drop.hoverclass) 77 | Element.removeClassName(drop.element, drop.hoverclass); 78 | this.last_active = null; 79 | }, 80 | 81 | activate: function(drop) { 82 | if(drop.hoverclass) 83 | Element.addClassName(drop.element, drop.hoverclass); 84 | this.last_active = drop; 85 | }, 86 | 87 | show: function(point, element) { 88 | if(!this.drops.length) return; 89 | var drop, affected = []; 90 | 91 | this.drops.each( function(drop) { 92 | if(Droppables.isAffected(point, element, drop)) 93 | affected.push(drop); 94 | }); 95 | 96 | if(affected.length>0) 97 | drop = Droppables.findDeepestChild(affected); 98 | 99 | if(this.last_active && this.last_active != drop) this.deactivate(this.last_active); 100 | if (drop) { 101 | Position.within(drop.element, point[0], point[1]); 102 | if(drop.onHover) 103 | drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); 104 | 105 | if (drop != this.last_active) Droppables.activate(drop); 106 | } 107 | }, 108 | 109 | fire: function(event, element) { 110 | if(!this.last_active) return; 111 | Position.prepare(); 112 | 113 | if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) 114 | if (this.last_active.onDrop) { 115 | this.last_active.onDrop(element, this.last_active.element, event); 116 | return true; 117 | } 118 | }, 119 | 120 | reset: function() { 121 | if(this.last_active) 122 | this.deactivate(this.last_active); 123 | } 124 | }; 125 | 126 | var Draggables = { 127 | drags: [], 128 | observers: [], 129 | 130 | register: function(draggable) { 131 | if(this.drags.length == 0) { 132 | this.eventMouseUp = this.endDrag.bindAsEventListener(this); 133 | this.eventMouseMove = this.updateDrag.bindAsEventListener(this); 134 | this.eventKeypress = this.keyPress.bindAsEventListener(this); 135 | 136 | Event.observe(document, "mouseup", this.eventMouseUp); 137 | Event.observe(document, "mousemove", this.eventMouseMove); 138 | Event.observe(document, "keypress", this.eventKeypress); 139 | } 140 | this.drags.push(draggable); 141 | }, 142 | 143 | unregister: function(draggable) { 144 | this.drags = this.drags.reject(function(d) { return d==draggable }); 145 | if(this.drags.length == 0) { 146 | Event.stopObserving(document, "mouseup", this.eventMouseUp); 147 | Event.stopObserving(document, "mousemove", this.eventMouseMove); 148 | Event.stopObserving(document, "keypress", this.eventKeypress); 149 | } 150 | }, 151 | 152 | activate: function(draggable) { 153 | if(draggable.options.delay) { 154 | this._timeout = setTimeout(function() { 155 | Draggables._timeout = null; 156 | window.focus(); 157 | Draggables.activeDraggable = draggable; 158 | }.bind(this), draggable.options.delay); 159 | } else { 160 | window.focus(); // allows keypress events if window isn't currently focused, fails for Safari 161 | this.activeDraggable = draggable; 162 | } 163 | }, 164 | 165 | deactivate: function() { 166 | this.activeDraggable = null; 167 | }, 168 | 169 | updateDrag: function(event) { 170 | if(!this.activeDraggable) return; 171 | var pointer = [Event.pointerX(event), Event.pointerY(event)]; 172 | // Mozilla-based browsers fire successive mousemove events with 173 | // the same coordinates, prevent needless redrawing (moz bug?) 174 | if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; 175 | this._lastPointer = pointer; 176 | 177 | this.activeDraggable.updateDrag(event, pointer); 178 | }, 179 | 180 | endDrag: function(event) { 181 | if(this._timeout) { 182 | clearTimeout(this._timeout); 183 | this._timeout = null; 184 | } 185 | if(!this.activeDraggable) return; 186 | this._lastPointer = null; 187 | this.activeDraggable.endDrag(event); 188 | this.activeDraggable = null; 189 | }, 190 | 191 | keyPress: function(event) { 192 | if(this.activeDraggable) 193 | this.activeDraggable.keyPress(event); 194 | }, 195 | 196 | addObserver: function(observer) { 197 | this.observers.push(observer); 198 | this._cacheObserverCallbacks(); 199 | }, 200 | 201 | removeObserver: function(element) { // element instead of observer fixes mem leaks 202 | this.observers = this.observers.reject( function(o) { return o.element==element }); 203 | this._cacheObserverCallbacks(); 204 | }, 205 | 206 | notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' 207 | if(this[eventName+'Count'] > 0) 208 | this.observers.each( function(o) { 209 | if(o[eventName]) o[eventName](eventName, draggable, event); 210 | }); 211 | if(draggable.options[eventName]) draggable.options[eventName](draggable, event); 212 | }, 213 | 214 | _cacheObserverCallbacks: function() { 215 | ['onStart','onEnd','onDrag'].each( function(eventName) { 216 | Draggables[eventName+'Count'] = Draggables.observers.select( 217 | function(o) { return o[eventName]; } 218 | ).length; 219 | }); 220 | } 221 | }; 222 | 223 | /*--------------------------------------------------------------------------*/ 224 | 225 | var Draggable = Class.create({ 226 | initialize: function(element) { 227 | var defaults = { 228 | handle: false, 229 | reverteffect: function(element, top_offset, left_offset) { 230 | var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; 231 | new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, 232 | queue: {scope:'_draggable', position:'end'} 233 | }); 234 | }, 235 | endeffect: function(element) { 236 | var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0; 237 | new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, 238 | queue: {scope:'_draggable', position:'end'}, 239 | afterFinish: function(){ 240 | Draggable._dragging[element] = false 241 | } 242 | }); 243 | }, 244 | zindex: 1000, 245 | revert: false, 246 | quiet: false, 247 | scroll: false, 248 | scrollSensitivity: 20, 249 | scrollSpeed: 15, 250 | snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } 251 | delay: 0 252 | }; 253 | 254 | if(!arguments[1] || Object.isUndefined(arguments[1].endeffect)) 255 | Object.extend(defaults, { 256 | starteffect: function(element) { 257 | element._opacity = Element.getOpacity(element); 258 | Draggable._dragging[element] = true; 259 | new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); 260 | } 261 | }); 262 | 263 | var options = Object.extend(defaults, arguments[1] || { }); 264 | 265 | this.element = $(element); 266 | 267 | if(options.handle && Object.isString(options.handle)) 268 | this.handle = this.element.down('.'+options.handle, 0); 269 | 270 | if(!this.handle) this.handle = $(options.handle); 271 | if(!this.handle) this.handle = this.element; 272 | 273 | if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { 274 | options.scroll = $(options.scroll); 275 | this._isScrollChild = Element.childOf(this.element, options.scroll); 276 | } 277 | 278 | Element.makePositioned(this.element); // fix IE 279 | 280 | this.options = options; 281 | this.dragging = false; 282 | 283 | this.eventMouseDown = this.initDrag.bindAsEventListener(this); 284 | Event.observe(this.handle, "mousedown", this.eventMouseDown); 285 | 286 | Draggables.register(this); 287 | }, 288 | 289 | destroy: function() { 290 | Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); 291 | Draggables.unregister(this); 292 | }, 293 | 294 | currentDelta: function() { 295 | return([ 296 | parseInt(Element.getStyle(this.element,'left') || '0'), 297 | parseInt(Element.getStyle(this.element,'top') || '0')]); 298 | }, 299 | 300 | initDrag: function(event) { 301 | if(!Object.isUndefined(Draggable._dragging[this.element]) && 302 | Draggable._dragging[this.element]) return; 303 | if(Event.isLeftClick(event)) { 304 | // abort on form elements, fixes a Firefox issue 305 | var src = Event.element(event); 306 | if((tag_name = src.tagName.toUpperCase()) && ( 307 | tag_name=='INPUT' || 308 | tag_name=='SELECT' || 309 | tag_name=='OPTION' || 310 | tag_name=='BUTTON' || 311 | tag_name=='TEXTAREA')) return; 312 | 313 | var pointer = [Event.pointerX(event), Event.pointerY(event)]; 314 | var pos = Position.cumulativeOffset(this.element); 315 | this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); 316 | 317 | Draggables.activate(this); 318 | Event.stop(event); 319 | } 320 | }, 321 | 322 | startDrag: function(event) { 323 | this.dragging = true; 324 | if(!this.delta) 325 | this.delta = this.currentDelta(); 326 | 327 | if(this.options.zindex) { 328 | this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); 329 | this.element.style.zIndex = this.options.zindex; 330 | } 331 | 332 | if(this.options.ghosting) { 333 | this._clone = this.element.cloneNode(true); 334 | this._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); 335 | if (!this._originallyAbsolute) 336 | Position.absolutize(this.element); 337 | this.element.parentNode.insertBefore(this._clone, this.element); 338 | } 339 | 340 | if(this.options.scroll) { 341 | if (this.options.scroll == window) { 342 | var where = this._getWindowScroll(this.options.scroll); 343 | this.originalScrollLeft = where.left; 344 | this.originalScrollTop = where.top; 345 | } else { 346 | this.originalScrollLeft = this.options.scroll.scrollLeft; 347 | this.originalScrollTop = this.options.scroll.scrollTop; 348 | } 349 | } 350 | 351 | Draggables.notify('onStart', this, event); 352 | 353 | if(this.options.starteffect) this.options.starteffect(this.element); 354 | }, 355 | 356 | updateDrag: function(event, pointer) { 357 | if(!this.dragging) this.startDrag(event); 358 | 359 | if(!this.options.quiet){ 360 | Position.prepare(); 361 | Droppables.show(pointer, this.element); 362 | } 363 | 364 | Draggables.notify('onDrag', this, event); 365 | 366 | this.draw(pointer); 367 | if(this.options.change) this.options.change(this); 368 | 369 | if(this.options.scroll) { 370 | this.stopScrolling(); 371 | 372 | var p; 373 | if (this.options.scroll == window) { 374 | with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } 375 | } else { 376 | p = Position.page(this.options.scroll); 377 | p[0] += this.options.scroll.scrollLeft + Position.deltaX; 378 | p[1] += this.options.scroll.scrollTop + Position.deltaY; 379 | p.push(p[0]+this.options.scroll.offsetWidth); 380 | p.push(p[1]+this.options.scroll.offsetHeight); 381 | } 382 | var speed = [0,0]; 383 | if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); 384 | if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); 385 | if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); 386 | if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); 387 | this.startScrolling(speed); 388 | } 389 | 390 | // fix AppleWebKit rendering 391 | if(Prototype.Browser.WebKit) window.scrollBy(0,0); 392 | 393 | Event.stop(event); 394 | }, 395 | 396 | finishDrag: function(event, success) { 397 | this.dragging = false; 398 | 399 | if(this.options.quiet){ 400 | Position.prepare(); 401 | var pointer = [Event.pointerX(event), Event.pointerY(event)]; 402 | Droppables.show(pointer, this.element); 403 | } 404 | 405 | if(this.options.ghosting) { 406 | if (!this._originallyAbsolute) 407 | Position.relativize(this.element); 408 | delete this._originallyAbsolute; 409 | Element.remove(this._clone); 410 | this._clone = null; 411 | } 412 | 413 | var dropped = false; 414 | if(success) { 415 | dropped = Droppables.fire(event, this.element); 416 | if (!dropped) dropped = false; 417 | } 418 | if(dropped && this.options.onDropped) this.options.onDropped(this.element); 419 | Draggables.notify('onEnd', this, event); 420 | 421 | var revert = this.options.revert; 422 | if(revert && Object.isFunction(revert)) revert = revert(this.element); 423 | 424 | var d = this.currentDelta(); 425 | if(revert && this.options.reverteffect) { 426 | if (dropped == 0 || revert != 'failure') 427 | this.options.reverteffect(this.element, 428 | d[1]-this.delta[1], d[0]-this.delta[0]); 429 | } else { 430 | this.delta = d; 431 | } 432 | 433 | if(this.options.zindex) 434 | this.element.style.zIndex = this.originalZ; 435 | 436 | if(this.options.endeffect) 437 | this.options.endeffect(this.element); 438 | 439 | Draggables.deactivate(this); 440 | Droppables.reset(); 441 | }, 442 | 443 | keyPress: function(event) { 444 | if(event.keyCode!=Event.KEY_ESC) return; 445 | this.finishDrag(event, false); 446 | Event.stop(event); 447 | }, 448 | 449 | endDrag: function(event) { 450 | if(!this.dragging) return; 451 | this.stopScrolling(); 452 | this.finishDrag(event, true); 453 | Event.stop(event); 454 | }, 455 | 456 | draw: function(point) { 457 | var pos = Position.cumulativeOffset(this.element); 458 | if(this.options.ghosting) { 459 | var r = Position.realOffset(this.element); 460 | pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; 461 | } 462 | 463 | var d = this.currentDelta(); 464 | pos[0] -= d[0]; pos[1] -= d[1]; 465 | 466 | if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { 467 | pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; 468 | pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; 469 | } 470 | 471 | var p = [0,1].map(function(i){ 472 | return (point[i]-pos[i]-this.offset[i]) 473 | }.bind(this)); 474 | 475 | if(this.options.snap) { 476 | if(Object.isFunction(this.options.snap)) { 477 | p = this.options.snap(p[0],p[1],this); 478 | } else { 479 | if(Object.isArray(this.options.snap)) { 480 | p = p.map( function(v, i) { 481 | return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)); 482 | } else { 483 | p = p.map( function(v) { 484 | return (v/this.options.snap).round()*this.options.snap }.bind(this)); 485 | } 486 | }} 487 | 488 | var style = this.element.style; 489 | if((!this.options.constraint) || (this.options.constraint=='horizontal')) 490 | style.left = p[0] + "px"; 491 | if((!this.options.constraint) || (this.options.constraint=='vertical')) 492 | style.top = p[1] + "px"; 493 | 494 | if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering 495 | }, 496 | 497 | stopScrolling: function() { 498 | if(this.scrollInterval) { 499 | clearInterval(this.scrollInterval); 500 | this.scrollInterval = null; 501 | Draggables._lastScrollPointer = null; 502 | } 503 | }, 504 | 505 | startScrolling: function(speed) { 506 | if(!(speed[0] || speed[1])) return; 507 | this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; 508 | this.lastScrolled = new Date(); 509 | this.scrollInterval = setInterval(this.scroll.bind(this), 10); 510 | }, 511 | 512 | scroll: function() { 513 | var current = new Date(); 514 | var delta = current - this.lastScrolled; 515 | this.lastScrolled = current; 516 | if(this.options.scroll == window) { 517 | with (this._getWindowScroll(this.options.scroll)) { 518 | if (this.scrollSpeed[0] || this.scrollSpeed[1]) { 519 | var d = delta / 1000; 520 | this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); 521 | } 522 | } 523 | } else { 524 | this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; 525 | this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; 526 | } 527 | 528 | Position.prepare(); 529 | Droppables.show(Draggables._lastPointer, this.element); 530 | Draggables.notify('onDrag', this); 531 | if (this._isScrollChild) { 532 | Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); 533 | Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; 534 | Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; 535 | if (Draggables._lastScrollPointer[0] < 0) 536 | Draggables._lastScrollPointer[0] = 0; 537 | if (Draggables._lastScrollPointer[1] < 0) 538 | Draggables._lastScrollPointer[1] = 0; 539 | this.draw(Draggables._lastScrollPointer); 540 | } 541 | 542 | if(this.options.change) this.options.change(this); 543 | }, 544 | 545 | _getWindowScroll: function(w) { 546 | var T, L, W, H; 547 | with (w.document) { 548 | if (w.document.documentElement && documentElement.scrollTop) { 549 | T = documentElement.scrollTop; 550 | L = documentElement.scrollLeft; 551 | } else if (w.document.body) { 552 | T = body.scrollTop; 553 | L = body.scrollLeft; 554 | } 555 | if (w.innerWidth) { 556 | W = w.innerWidth; 557 | H = w.innerHeight; 558 | } else if (w.document.documentElement && documentElement.clientWidth) { 559 | W = documentElement.clientWidth; 560 | H = documentElement.clientHeight; 561 | } else { 562 | W = body.offsetWidth; 563 | H = body.offsetHeight; 564 | } 565 | } 566 | return { top: T, left: L, width: W, height: H }; 567 | } 568 | }); 569 | 570 | Draggable._dragging = { }; 571 | 572 | /*--------------------------------------------------------------------------*/ 573 | 574 | var SortableObserver = Class.create({ 575 | initialize: function(element, observer) { 576 | this.element = $(element); 577 | this.observer = observer; 578 | this.lastValue = Sortable.serialize(this.element); 579 | }, 580 | 581 | onStart: function() { 582 | this.lastValue = Sortable.serialize(this.element); 583 | }, 584 | 585 | onEnd: function() { 586 | Sortable.unmark(); 587 | if(this.lastValue != Sortable.serialize(this.element)) 588 | this.observer(this.element) 589 | } 590 | }); 591 | 592 | var Sortable = { 593 | SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, 594 | 595 | sortables: { }, 596 | 597 | _findRootElement: function(element) { 598 | while (element.tagName.toUpperCase() != "BODY") { 599 | if(element.id && Sortable.sortables[element.id]) return element; 600 | element = element.parentNode; 601 | } 602 | }, 603 | 604 | options: function(element) { 605 | element = Sortable._findRootElement($(element)); 606 | if(!element) return; 607 | return Sortable.sortables[element.id]; 608 | }, 609 | 610 | destroy: function(element){ 611 | element = $(element); 612 | var s = Sortable.sortables[element.id]; 613 | 614 | if(s) { 615 | Draggables.removeObserver(s.element); 616 | s.droppables.each(function(d){ Droppables.remove(d) }); 617 | s.draggables.invoke('destroy'); 618 | 619 | delete Sortable.sortables[s.element.id]; 620 | } 621 | }, 622 | 623 | create: function(element) { 624 | element = $(element); 625 | var options = Object.extend({ 626 | element: element, 627 | tag: 'li', // assumes li children, override with tag: 'tagname' 628 | dropOnEmpty: false, 629 | tree: false, 630 | treeTag: 'ul', 631 | overlap: 'vertical', // one of 'vertical', 'horizontal' 632 | constraint: 'vertical', // one of 'vertical', 'horizontal', false 633 | containment: element, // also takes array of elements (or id's); or false 634 | handle: false, // or a CSS class 635 | only: false, 636 | delay: 0, 637 | hoverclass: null, 638 | ghosting: false, 639 | quiet: false, 640 | scroll: false, 641 | scrollSensitivity: 20, 642 | scrollSpeed: 15, 643 | format: this.SERIALIZE_RULE, 644 | 645 | // these take arrays of elements or ids and can be 646 | // used for better initialization performance 647 | elements: false, 648 | handles: false, 649 | 650 | onChange: Prototype.emptyFunction, 651 | onUpdate: Prototype.emptyFunction 652 | }, arguments[1] || { }); 653 | 654 | // clear any old sortable with same element 655 | this.destroy(element); 656 | 657 | // build options for the draggables 658 | var options_for_draggable = { 659 | revert: true, 660 | quiet: options.quiet, 661 | scroll: options.scroll, 662 | scrollSpeed: options.scrollSpeed, 663 | scrollSensitivity: options.scrollSensitivity, 664 | delay: options.delay, 665 | ghosting: options.ghosting, 666 | constraint: options.constraint, 667 | handle: options.handle }; 668 | 669 | if(options.starteffect) 670 | options_for_draggable.starteffect = options.starteffect; 671 | 672 | if(options.reverteffect) 673 | options_for_draggable.reverteffect = options.reverteffect; 674 | else 675 | if(options.ghosting) options_for_draggable.reverteffect = function(element) { 676 | element.style.top = 0; 677 | element.style.left = 0; 678 | }; 679 | 680 | if(options.endeffect) 681 | options_for_draggable.endeffect = options.endeffect; 682 | 683 | if(options.zindex) 684 | options_for_draggable.zindex = options.zindex; 685 | 686 | // build options for the droppables 687 | var options_for_droppable = { 688 | overlap: options.overlap, 689 | containment: options.containment, 690 | tree: options.tree, 691 | hoverclass: options.hoverclass, 692 | onHover: Sortable.onHover 693 | }; 694 | 695 | var options_for_tree = { 696 | onHover: Sortable.onEmptyHover, 697 | overlap: options.overlap, 698 | containment: options.containment, 699 | hoverclass: options.hoverclass 700 | }; 701 | 702 | // fix for gecko engine 703 | Element.cleanWhitespace(element); 704 | 705 | options.draggables = []; 706 | options.droppables = []; 707 | 708 | // drop on empty handling 709 | if(options.dropOnEmpty || options.tree) { 710 | Droppables.add(element, options_for_tree); 711 | options.droppables.push(element); 712 | } 713 | 714 | (options.elements || this.findElements(element, options) || []).each( function(e,i) { 715 | var handle = options.handles ? $(options.handles[i]) : 716 | (options.handle ? $(e).select('.' + options.handle)[0] : e); 717 | options.draggables.push( 718 | new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); 719 | Droppables.add(e, options_for_droppable); 720 | if(options.tree) e.treeNode = element; 721 | options.droppables.push(e); 722 | }); 723 | 724 | if(options.tree) { 725 | (Sortable.findTreeElements(element, options) || []).each( function(e) { 726 | Droppables.add(e, options_for_tree); 727 | e.treeNode = element; 728 | options.droppables.push(e); 729 | }); 730 | } 731 | 732 | // keep reference 733 | this.sortables[element.id] = options; 734 | 735 | // for onupdate 736 | Draggables.addObserver(new SortableObserver(element, options.onUpdate)); 737 | 738 | }, 739 | 740 | // return all suitable-for-sortable elements in a guaranteed order 741 | findElements: function(element, options) { 742 | return Element.findChildren( 743 | element, options.only, options.tree ? true : false, options.tag); 744 | }, 745 | 746 | findTreeElements: function(element, options) { 747 | return Element.findChildren( 748 | element, options.only, options.tree ? true : false, options.treeTag); 749 | }, 750 | 751 | onHover: function(element, dropon, overlap) { 752 | if(Element.isParent(dropon, element)) return; 753 | 754 | if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { 755 | return; 756 | } else if(overlap>0.5) { 757 | Sortable.mark(dropon, 'before'); 758 | if(dropon.previousSibling != element) { 759 | var oldParentNode = element.parentNode; 760 | element.style.visibility = "hidden"; // fix gecko rendering 761 | dropon.parentNode.insertBefore(element, dropon); 762 | if(dropon.parentNode!=oldParentNode) 763 | Sortable.options(oldParentNode).onChange(element); 764 | Sortable.options(dropon.parentNode).onChange(element); 765 | } 766 | } else { 767 | Sortable.mark(dropon, 'after'); 768 | var nextElement = dropon.nextSibling || null; 769 | if(nextElement != element) { 770 | var oldParentNode = element.parentNode; 771 | element.style.visibility = "hidden"; // fix gecko rendering 772 | dropon.parentNode.insertBefore(element, nextElement); 773 | if(dropon.parentNode!=oldParentNode) 774 | Sortable.options(oldParentNode).onChange(element); 775 | Sortable.options(dropon.parentNode).onChange(element); 776 | } 777 | } 778 | }, 779 | 780 | onEmptyHover: function(element, dropon, overlap) { 781 | var oldParentNode = element.parentNode; 782 | var droponOptions = Sortable.options(dropon); 783 | 784 | if(!Element.isParent(dropon, element)) { 785 | var index; 786 | 787 | var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); 788 | var child = null; 789 | 790 | if(children) { 791 | var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); 792 | 793 | for (index = 0; index < children.length; index += 1) { 794 | if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { 795 | offset -= Element.offsetSize (children[index], droponOptions.overlap); 796 | } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { 797 | child = index + 1 < children.length ? children[index + 1] : null; 798 | break; 799 | } else { 800 | child = children[index]; 801 | break; 802 | } 803 | } 804 | } 805 | 806 | dropon.insertBefore(element, child); 807 | 808 | Sortable.options(oldParentNode).onChange(element); 809 | droponOptions.onChange(element); 810 | } 811 | }, 812 | 813 | unmark: function() { 814 | if(Sortable._marker) Sortable._marker.hide(); 815 | }, 816 | 817 | mark: function(dropon, position) { 818 | // mark on ghosting only 819 | var sortable = Sortable.options(dropon.parentNode); 820 | if(sortable && !sortable.ghosting) return; 821 | 822 | if(!Sortable._marker) { 823 | Sortable._marker = 824 | ($('dropmarker') || Element.extend(document.createElement('DIV'))). 825 | hide().addClassName('dropmarker').setStyle({position:'absolute'}); 826 | document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); 827 | } 828 | var offsets = Position.cumulativeOffset(dropon); 829 | Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); 830 | 831 | if(position=='after') 832 | if(sortable.overlap == 'horizontal') 833 | Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); 834 | else 835 | Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); 836 | 837 | Sortable._marker.show(); 838 | }, 839 | 840 | _tree: function(element, options, parent) { 841 | var children = Sortable.findElements(element, options) || []; 842 | 843 | for (var i = 0; i < children.length; ++i) { 844 | var match = children[i].id.match(options.format); 845 | 846 | if (!match) continue; 847 | 848 | var child = { 849 | id: encodeURIComponent(match ? match[1] : null), 850 | element: element, 851 | parent: parent, 852 | children: [], 853 | position: parent.children.length, 854 | container: $(children[i]).down(options.treeTag) 855 | }; 856 | 857 | /* Get the element containing the children and recurse over it */ 858 | if (child.container) 859 | this._tree(child.container, options, child); 860 | 861 | parent.children.push (child); 862 | } 863 | 864 | return parent; 865 | }, 866 | 867 | tree: function(element) { 868 | element = $(element); 869 | var sortableOptions = this.options(element); 870 | var options = Object.extend({ 871 | tag: sortableOptions.tag, 872 | treeTag: sortableOptions.treeTag, 873 | only: sortableOptions.only, 874 | name: element.id, 875 | format: sortableOptions.format 876 | }, arguments[1] || { }); 877 | 878 | var root = { 879 | id: null, 880 | parent: null, 881 | children: [], 882 | container: element, 883 | position: 0 884 | }; 885 | 886 | return Sortable._tree(element, options, root); 887 | }, 888 | 889 | /* Construct a [i] index for a particular node */ 890 | _constructIndex: function(node) { 891 | var index = ''; 892 | do { 893 | if (node.id) index = '[' + node.position + ']' + index; 894 | } while ((node = node.parent) != null); 895 | return index; 896 | }, 897 | 898 | sequence: function(element) { 899 | element = $(element); 900 | var options = Object.extend(this.options(element), arguments[1] || { }); 901 | 902 | return $(this.findElements(element, options) || []).map( function(item) { 903 | return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; 904 | }); 905 | }, 906 | 907 | setSequence: function(element, new_sequence) { 908 | element = $(element); 909 | var options = Object.extend(this.options(element), arguments[2] || { }); 910 | 911 | var nodeMap = { }; 912 | this.findElements(element, options).each( function(n) { 913 | if (n.id.match(options.format)) 914 | nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; 915 | n.parentNode.removeChild(n); 916 | }); 917 | 918 | new_sequence.each(function(ident) { 919 | var n = nodeMap[ident]; 920 | if (n) { 921 | n[1].appendChild(n[0]); 922 | delete nodeMap[ident]; 923 | } 924 | }); 925 | }, 926 | 927 | serialize: function(element) { 928 | element = $(element); 929 | var options = Object.extend(Sortable.options(element), arguments[1] || { }); 930 | var name = encodeURIComponent( 931 | (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); 932 | 933 | if (options.tree) { 934 | return Sortable.tree(element, arguments[1]).children.map( function (item) { 935 | return [name + Sortable._constructIndex(item) + "[id]=" + 936 | encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); 937 | }).flatten().join('&'); 938 | } else { 939 | return Sortable.sequence(element, arguments[1]).map( function(item) { 940 | return name + "[]=" + encodeURIComponent(item); 941 | }).join('&'); 942 | } 943 | } 944 | }; 945 | 946 | // Returns true if child is contained within element 947 | Element.isParent = function(child, element) { 948 | if (!child.parentNode || child == element) return false; 949 | if (child.parentNode == element) return true; 950 | return Element.isParent(child.parentNode, element); 951 | }; 952 | 953 | Element.findChildren = function(element, only, recursive, tagName) { 954 | if(!element.hasChildNodes()) return null; 955 | tagName = tagName.toUpperCase(); 956 | if(only) only = [only].flatten(); 957 | var elements = []; 958 | $A(element.childNodes).each( function(e) { 959 | if(e.tagName && e.tagName.toUpperCase()==tagName && 960 | (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) 961 | elements.push(e); 962 | if(recursive) { 963 | var grandchildren = Element.findChildren(e, only, recursive, tagName); 964 | if(grandchildren) elements.push(grandchildren); 965 | } 966 | }); 967 | 968 | return (elements.length>0 ? elements.flatten() : []); 969 | }; 970 | 971 | Element.offsetSize = function (element, type) { 972 | return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; 973 | }; -------------------------------------------------------------------------------- /spec/rails/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /spec/rails/public/stylesheets/scaffold.css: -------------------------------------------------------------------------------- 1 | body { background-color: #fff; color: #333; } 2 | 3 | body, p, ol, ul, td { 4 | font-family: verdana, arial, helvetica, sans-serif; 5 | font-size: 13px; 6 | line-height: 18px; 7 | } 8 | 9 | pre { 10 | background-color: #eee; 11 | padding: 10px; 12 | font-size: 11px; 13 | } 14 | 15 | a { color: #000; } 16 | a:visited { color: #666; } 17 | a:hover { color: #fff; background-color:#000; } 18 | 19 | .fieldWithErrors { 20 | padding: 2px; 21 | background-color: red; 22 | display: table; 23 | } 24 | 25 | #errorExplanation { 26 | width: 400px; 27 | border: 2px solid red; 28 | padding: 7px; 29 | padding-bottom: 12px; 30 | margin-bottom: 20px; 31 | background-color: #f0f0f0; 32 | } 33 | 34 | #errorExplanation h2 { 35 | text-align: left; 36 | font-weight: bold; 37 | padding: 5px 5px 5px 15px; 38 | font-size: 12px; 39 | margin: -7px; 40 | background-color: #c00; 41 | color: #fff; 42 | } 43 | 44 | #errorExplanation p { 45 | color: #333; 46 | margin-bottom: 0; 47 | padding: 5px; 48 | } 49 | 50 | #errorExplanation ul li { 51 | font-size: 12px; 52 | list-style: square; 53 | } 54 | 55 | -------------------------------------------------------------------------------- /spec/rails/script/about: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.dirname(__FILE__) + '/../config/boot' 3 | $LOAD_PATH.unshift "#{RAILTIES_PATH}/builtin/rails_info" 4 | require 'commands/about' -------------------------------------------------------------------------------- /spec/rails/script/autospec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | gem 'test-unit', '1.2.3' if RUBY_VERSION.to_f >= 1.9 3 | ENV['RSPEC'] = 'true' # allows autotest to discover rspec 4 | ENV['AUTOTEST'] = 'true' # allows autotest to run w/ color on linux 5 | system((RUBY_PLATFORM =~ /mswin|mingw/ ? 'autotest.bat' : 'autotest'), *ARGV) || 6 | $stderr.puts("Unable to find autotest. Please install ZenTest or fix your PATH") 7 | -------------------------------------------------------------------------------- /spec/rails/script/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.dirname(__FILE__) + '/../config/boot' 3 | require 'commands/console' 4 | -------------------------------------------------------------------------------- /spec/rails/script/dbconsole: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.dirname(__FILE__) + '/../config/boot' 3 | require 'commands/dbconsole' 4 | -------------------------------------------------------------------------------- /spec/rails/script/destroy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.dirname(__FILE__) + '/../config/boot' 3 | require 'commands/destroy' 4 | -------------------------------------------------------------------------------- /spec/rails/script/generate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.dirname(__FILE__) + '/../config/boot' 3 | require 'commands/generate' 4 | -------------------------------------------------------------------------------- /spec/rails/script/performance/benchmarker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.dirname(__FILE__) + '/../../config/boot' 3 | require 'commands/performance/benchmarker' 4 | -------------------------------------------------------------------------------- /spec/rails/script/performance/profiler: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.dirname(__FILE__) + '/../../config/boot' 3 | require 'commands/performance/profiler' 4 | -------------------------------------------------------------------------------- /spec/rails/script/plugin: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.dirname(__FILE__) + '/../config/boot' 3 | require 'commands/plugin' 4 | -------------------------------------------------------------------------------- /spec/rails/script/runner: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.dirname(__FILE__) + '/../config/boot' 3 | require 'commands/runner' 4 | -------------------------------------------------------------------------------- /spec/rails/script/server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.dirname(__FILE__) + '/../config/boot' 3 | require 'commands/server' 4 | -------------------------------------------------------------------------------- /spec/rails/script/spec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | if ARGV.any? {|arg| %w[--drb -X --generate-options -G --help -h --version -v].include?(arg)} 3 | require 'rubygems' unless ENV['NO_RUBYGEMS'] 4 | else 5 | gem 'test-unit', '1.2.3' if RUBY_VERSION.to_f >= 1.9 6 | ENV["RAILS_ENV"] ||= 'test' 7 | require File.expand_path(File.dirname(__FILE__) + "/../config/environment") unless defined?(RAILS_ROOT) 8 | end 9 | require 'spec/autorun' 10 | exit ::RSpec::Runner::CommandLine.run 11 | -------------------------------------------------------------------------------- /spec/rails/script/spec_server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | gem 'test-unit', '1.2.3' if RUBY_VERSION.to_f >= 1.9 3 | 4 | puts "Loading Rails environment" 5 | ENV["RAILS_ENV"] ||= 'test' 6 | require File.expand_path(File.dirname(__FILE__) + "/../config/environment") unless defined?(RAILS_ROOT) 7 | 8 | require 'optparse' 9 | require 'spec/rails/spec_server' 10 | -------------------------------------------------------------------------------- /spec/rails/spec/controllers/form_test_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 2 | 3 | describe FormTestController do 4 | 5 | #Delete these examples and add some real ones 6 | it "should use FormTestController" do 7 | controller.should be_an_instance_of(FormTestController) 8 | end 9 | 10 | describe "GET 'form'" do 11 | it "should return gender with male and female choices" do 12 | get 'form' 13 | response.should be_success 14 | end 15 | end 16 | 17 | describe "POST 'form'" do 18 | 19 | end 20 | 21 | describe "GET 'form_for'" do 22 | it "should be successful" do 23 | get 'form_for' 24 | response.should be_success 25 | end 26 | end 27 | 28 | describe "POST 'form_for'" do 29 | end 30 | 31 | describe "GET 'form_tag'" do 32 | it "should be successful" do 33 | get 'form_tag' 34 | response.should be_success 35 | end 36 | end 37 | 38 | describe "POST 'form_tag'" do 39 | 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/rails/spec/integrations/enum_select_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.join(File.dirname(__FILE__), '../spec_helper.rb')) 2 | 3 | shared_examples_for "enum_select form" do 4 | it "should have select boxes for gender, status and degree initially set blank" do 5 | visit form_page 6 | select_option('', /gender/) 7 | select_option('', /status/) 8 | select_option('None', /degree/) 9 | end 10 | it "should verify all options for each enumeration" do 11 | visit form_page 12 | %w(Male Female).each {|e| select e, :from=>/gender/} 13 | %w(Single Married Widowed Divorced).each {|e| select e, :from=>/status/} 14 | ["None", "High school", "College", "Graduate"].each {|e| select e, :from=>/degree/} 15 | end 16 | it "should submit and keep values when form is invalid" do 17 | visit form_page 18 | select 'Male', :from=>/gender/ 19 | select 'Single', :from=>/status/ 20 | select 'College', :from=>/degree/ 21 | click_button 'Save' 22 | select_option('Male', /gender/) 23 | select_option('Single', /status/) 24 | select_option('College', /degree/) 25 | end 26 | it "should select values and submit without validation errors and create an object with those values" do 27 | User.delete_all 28 | visit form_page 29 | select 'Male', :from=>/gender/ 30 | select 'Single', :from=>/status/ 31 | select 'College', :from=>/degree/ 32 | fill_in(/first_name/, :with=>'john') 33 | fill_in(/age/, :with=>'30') 34 | click_button 'Save' 35 | #response.code.should be_success 36 | u = User.find(:first) 37 | u.first_name.should == 'john' 38 | u.age.should == 30 39 | u.gender.should == :male 40 | u.status.should == :single 41 | u.degree.should == :college 42 | end 43 | end 44 | 45 | describe "Form using form_for and FormBuilder" do 46 | def form_page; '/form_test/form_for'; end 47 | 48 | it_should_behave_like "enum_select form" 49 | 50 | end 51 | 52 | describe "Form using option_helper_tags" do 53 | def form_page; '/form_test/form_tag'; end 54 | 55 | it_should_behave_like "enum_select form" 56 | 57 | end 58 | 59 | =begin 60 | describe "Form using ActiveRecord helpers" do 61 | def form_page; '/form_test/form'; end 62 | puts 63 | puts "*****************************************************" 64 | puts "warning: there's a bug in ActionView::Helpers::ActiveRecord 'form' method" 65 | puts "must change in active_record_helper.rb 'form' method" 66 | puts "contents = form_tag(:action=>action, :method =>(options[:method] || 'post'), :enctype => options[:multipart] ? 'multipart/form-data': nil)" 67 | puts "to" 68 | puts "contents = form_tag(action, :method =>(options[:method] || 'post'), :enctype => options[:multipart] ? 'multipart/form-data': nil)" 69 | puts "*****************************************************" 70 | puts 71 | 72 | it_should_behave_like "enum_select form" 73 | 74 | end 75 | =end -------------------------------------------------------------------------------- /spec/rails/spec/matchers.rb: -------------------------------------------------------------------------------- 1 | #defines custom rspec matcher for this test 2 | RSpec::Matchers.define :be_blank do 3 | match do |value| 4 | value == nil || value == '' 5 | end 6 | end 7 | RSpec::Matchers.define :be_nil do 8 | match do |value| 9 | value == nil 10 | end 11 | end 12 | 13 | -------------------------------------------------------------------------------- /spec/rails/spec/rcov.opts: -------------------------------------------------------------------------------- 1 | --exclude "spec/*,gems/*" 2 | --rails -------------------------------------------------------------------------------- /spec/rails/spec/spec.opts: -------------------------------------------------------------------------------- 1 | --colour 2 | --format progress 3 | --loadby mtime 4 | --reverse 5 | -------------------------------------------------------------------------------- /spec/rails/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to ~/spec when you run 'ruby script/generate rspec' 2 | # from the project root directory. 3 | ENV["RAILS_ENV"] ||= 'test' 4 | require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) 5 | #require 'enumerated_attribute' 6 | require 'spec/autorun' 7 | gem 'rspec-rails', '>= 1.3.2' 8 | require 'spec/rails' 9 | gem 'webrat', '>= 0.7.1' 10 | require 'webrat' 11 | require 'matchers' 12 | 13 | # Requires supporting files with custom matchers and macros, etc, 14 | # in ./support/ and its subdirectories. 15 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} 16 | 17 | Webrat.configure do |config| 18 | config.mode = :rails 19 | end 20 | 21 | RSpec::Runner.configure do |config| 22 | end 23 | 24 | #setup for integrating webrat with rspec 25 | module RSpec::Rails::Example 26 | class IntegrationExampleGroup < ActionController::IntegrationTest 27 | 28 | def initialize(defined_description, options={}, &implementation) 29 | defined_description.instance_eval do 30 | def to_s 31 | self 32 | end 33 | end 34 | super(defined_description) 35 | end 36 | 37 | RSpec::Example::ExampleGroupFactory.register(:integration, self) 38 | end 39 | end 40 | 41 | -------------------------------------------------------------------------------- /spec/rails/spec/views/form_test/form.html.erb_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') 2 | 3 | describe "/dog/bark" do 4 | before(:each) do 5 | render 'dog/bark' 6 | end 7 | 8 | #Delete this example and add some real ones or delete this file 9 | it "should tell you where to find the file" do 10 | response.should have_tag('p', %r[Find me in app/views/dog/bark]) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/rails/spec/views/form_test/form_for.html.erb_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') 2 | 3 | describe "/dog/bark" do 4 | before(:each) do 5 | render 'dog/bark' 6 | end 7 | 8 | #Delete this example and add some real ones or delete this file 9 | it "should tell you where to find the file" do 10 | response.should have_tag('p', %r[Find me in app/views/dog/bark]) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/rails/spec/views/form_test/form_tag.html.erb_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') 2 | 3 | describe "/dog/bark" do 4 | before(:each) do 5 | render 'dog/bark' 6 | end 7 | 8 | #Delete this example and add some real ones or delete this file 9 | it "should tell you where to find the file" do 10 | response.should have_tag('p', %r[Find me in app/views/dog/bark]) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/rails/test/fixtures/users.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html 2 | 3 | one: 4 | name: MyString 5 | age: 1 6 | gender: MyString 7 | DOB: 2009-08-04 8 | degree: MyString 9 | status: MyString 10 | 11 | two: 12 | name: MyString 13 | age: 1 14 | gender: MyString 15 | DOB: 2009-08-04 16 | degree: MyString 17 | status: MyString 18 | -------------------------------------------------------------------------------- /spec/rails/test/functional/form_test_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class FormTestControllerTest < ActionController::TestCase 4 | # Replace this with your real tests. 5 | test "the truth" do 6 | assert true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/rails/test/performance/browsing_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'performance_test_help' 3 | 4 | # Profiling results for each test method are written to tmp/performance. 5 | class BrowsingTest < ActionController::PerformanceTest 6 | def test_homepage 7 | get '/' 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/rails/test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] = "test" 2 | require File.expand_path(File.dirname(__FILE__) + "/../config/environment") 3 | require 'test_help' 4 | 5 | class ActiveSupport::TestCase 6 | # Transactional fixtures accelerate your tests by wrapping each test method 7 | # in a transaction that's rolled back on completion. This ensures that the 8 | # test database remains unchanged so your fixtures don't have to be reloaded 9 | # between every test method. Fewer database queries means faster tests. 10 | # 11 | # Read Mike Clark's excellent walkthrough at 12 | # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting 13 | # 14 | # Every Active Record database supports transactions except MyISAM tables 15 | # in MySQL. Turn off transactional fixtures in this case; however, if you 16 | # don't care one way or the other, switching from MyISAM to InnoDB tables 17 | # is recommended. 18 | # 19 | # The only drawback to using transactional fixtures is when you actually 20 | # need to test transactions. Since your test is bracketed by a transaction, 21 | # any transactions started in your code will be automatically rolled back. 22 | self.use_transactional_fixtures = true 23 | 24 | # Instantiated fixtures are slow, but give you @david where otherwise you 25 | # would need people(:david). If you don't want to migrate your existing 26 | # test cases which use the @david style and don't mind the speed hit (each 27 | # instantiated fixtures translates to a database query per test method), 28 | # then set this back to true. 29 | self.use_instantiated_fixtures = false 30 | 31 | # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. 32 | # 33 | # Note: You'll currently still have to declare fixtures explicitly in integration tests 34 | # -- they do not yet inherit this setting 35 | fixtures :all 36 | 37 | # Add more helper methods to be used by all tests here... 38 | end 39 | -------------------------------------------------------------------------------- /spec/rails/test/unit/helpers/form_test_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class FormTestHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /spec/rails/test/unit/user_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class UserTest < ActiveSupport::TestCase 4 | # Replace this with your real tests. 5 | test "the truth" do 6 | assert true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/rcov.opts: -------------------------------------------------------------------------------- 1 | --exclude "spec/*,gems/*" 2 | --rails -------------------------------------------------------------------------------- /spec/spec.opts: -------------------------------------------------------------------------------- 1 | --colour 2 | --format progress 3 | -------------------------------------------------------------------------------- /spec/tractor.rb: -------------------------------------------------------------------------------- 1 | require 'enumerated_attribute' 2 | 3 | class Tractor 4 | 5 | GEAR_ENUM_VALUES = %w(reverse neutral first second over_drive).map{|v| v.to_sym} 6 | LIGHTS_ENUM_VALUES = %w(off low high).map{|v| v.to_sym} 7 | SIDE_LIGHT_ENUM_VALUES = [:off,:low,:high,:super_high] 8 | 9 | attr_accessor :name 10 | 11 | def initialize 12 | @name = 'old faithful' 13 | end 14 | 15 | enumerated_attribute :gear, %w(reverse ^neutral first second over_drive), :nil=>false do 16 | parked? is :neutral 17 | driving? is [:first, :second, :over_drive] 18 | not_parked? is_not :neutral 19 | not_driving? is_not [:first, :second, :over_drive] 20 | upshift { self.gear_is_in_over_drive? ? self.gear : self.gear_next } 21 | downshift { self.driving? ? self.gear_previous : self.gear } 22 | end 23 | 24 | enum_attr :pto, %w(reverse ^off forward) 25 | 26 | enum_attr :plow, %w(^up down), :nil=>true do 27 | plowing? { self.gear_is_in_first? && self.plow == :down } 28 | end 29 | 30 | enum_attr :lights, LIGHTS_ENUM_VALUES, :plural=>:lights_enums, :init=>:off, :decrementor=>:turn_lights_down, :incrementor=>:turn_lights_up do 31 | lights_are_on? [:low, :high] 32 | lights_are_not_on? :off 33 | end 34 | 35 | enum_attr :side_light, %w(off low high super_high) do 36 | init :off 37 | enums_accessor :side_light_enums 38 | incrementor :side_light_up 39 | decrementor :side_light_down 40 | label :off=>'OFF' 41 | labels :low => 'LOW DIM', :high => 'HIGH BEAM' 42 | labels :super_high => 'SUPER BEAM' 43 | end 44 | 45 | #enum_attr_reader :temperature, %w(low med high) 46 | #enum_attr_writer :ignition, %w(^off activate) 47 | 48 | end 49 | --------------------------------------------------------------------------------