├── test ├── dummy │ ├── log │ │ └── .keep │ ├── app │ │ ├── mailers │ │ │ └── .keep │ │ ├── models │ │ │ ├── .keep │ │ │ ├── concerns │ │ │ │ └── .keep │ │ │ ├── tag.rb │ │ │ ├── person.rb │ │ │ ├── sub_task.rb │ │ │ ├── answer.rb │ │ │ ├── presentation.rb │ │ │ ├── project_tag.rb │ │ │ ├── assignment.rb │ │ │ ├── email.rb │ │ │ ├── question.rb │ │ │ ├── task.rb │ │ │ ├── producer.rb │ │ │ ├── song.rb │ │ │ ├── profile.rb │ │ │ ├── survey.rb │ │ │ ├── artist.rb │ │ │ ├── speaker.rb │ │ │ ├── user.rb │ │ │ ├── project.rb │ │ │ └── conference.rb │ │ ├── assets │ │ │ ├── images │ │ │ │ └── .keep │ │ │ ├── javascripts │ │ │ │ ├── application.js │ │ │ │ └── projects.js │ │ │ └── stylesheets │ │ │ │ └── application.css │ │ ├── controllers │ │ │ ├── concerns │ │ │ │ └── .keep │ │ │ ├── application_controller.rb │ │ │ ├── assignments_controller.rb │ │ │ ├── users_controller.rb │ │ │ ├── songs_controller.rb │ │ │ ├── surveys_controller.rb │ │ │ ├── conferences_controller.rb │ │ │ └── projects_controller.rb │ │ ├── helpers │ │ │ ├── songs_helper.rb │ │ │ ├── surveys_helper.rb │ │ │ ├── users_helper.rb │ │ │ ├── projects_helper.rb │ │ │ ├── application_helper.rb │ │ │ ├── assignments_helper.rb │ │ │ └── conferences_helper.rb │ │ ├── views │ │ │ ├── projects │ │ │ │ ├── new.html.erb │ │ │ │ ├── _tag_fields.html.erb │ │ │ │ ├── edit.html.erb │ │ │ │ ├── _owner_fields.html.erb │ │ │ │ ├── _contributor_fields.html.erb │ │ │ │ ├── _sub_task_fields.html.erb │ │ │ │ ├── _project_tag_fields.html.erb │ │ │ │ ├── index.html.erb │ │ │ │ ├── _task_fields.html.erb │ │ │ │ ├── show.html.erb │ │ │ │ └── _form.html.erb │ │ │ ├── songs │ │ │ │ ├── new.html.erb │ │ │ │ ├── edit.html.erb │ │ │ │ ├── _producer.html.erb │ │ │ │ ├── _artist.html.erb │ │ │ │ ├── show.html.erb │ │ │ │ ├── index.html.erb │ │ │ │ └── _form.html.erb │ │ │ ├── users │ │ │ │ ├── new.html.erb │ │ │ │ ├── _email.html.erb │ │ │ │ ├── edit.html.erb │ │ │ │ ├── _profile.html.erb │ │ │ │ ├── show.html.erb │ │ │ │ ├── index.html.erb │ │ │ │ └── _form.html.erb │ │ │ ├── surveys │ │ │ │ ├── new.html.erb │ │ │ │ ├── edit.html.erb │ │ │ │ ├── _answer_fields.html.erb │ │ │ │ ├── _question_fields.html.erb │ │ │ │ ├── show.html.erb │ │ │ │ ├── index.html.erb │ │ │ │ └── _form.html.erb │ │ │ ├── assignments │ │ │ │ ├── new.html.erb │ │ │ │ ├── edit.html.erb │ │ │ │ ├── _task_fields.html.erb │ │ │ │ ├── show.html.erb │ │ │ │ ├── index.html.erb │ │ │ │ └── _form.html.erb │ │ │ ├── conferences │ │ │ │ ├── new.html.erb │ │ │ │ ├── edit.html.erb │ │ │ │ ├── _presentation_fields.html.erb │ │ │ │ ├── _speaker.html.erb │ │ │ │ ├── index.html.erb │ │ │ │ ├── show.html.erb │ │ │ │ └── _form.html.erb │ │ │ └── layouts │ │ │ │ └── application.html.erb │ │ └── forms │ │ │ ├── assignment_form.rb │ │ │ ├── song_form.rb │ │ │ ├── survey_form.rb │ │ │ ├── conference_form.rb │ │ │ ├── user_form.rb │ │ │ └── project_form.rb │ ├── lib │ │ ├── assets │ │ │ └── .keep │ │ ├── templates │ │ │ └── erb │ │ │ │ └── scaffold │ │ │ │ └── _form.html.erb │ │ └── act_as_gendered.rb │ ├── public │ │ ├── favicon.ico │ │ ├── 500.html │ │ ├── 422.html │ │ └── 404.html │ ├── test │ │ ├── fixtures │ │ │ ├── demo.txt │ │ │ ├── assignments.yml │ │ │ ├── conferences.yml │ │ │ ├── artists.yml │ │ │ ├── songs.yml │ │ │ ├── users.yml │ │ │ ├── surveys.yml │ │ │ ├── emails.yml │ │ │ ├── tags.yml │ │ │ ├── producers.yml │ │ │ ├── questions.yml │ │ │ ├── speakers.yml │ │ │ ├── profiles.yml │ │ │ ├── project_tags.yml │ │ │ ├── projects.yml │ │ │ ├── answers.yml │ │ │ ├── presentations.yml │ │ │ ├── sub_tasks.yml │ │ │ ├── tasks.yml │ │ │ └── people.yml │ │ ├── helpers │ │ │ ├── songs_helper_test.rb │ │ │ ├── users_helper_test.rb │ │ │ ├── projects_helper_test.rb │ │ │ ├── surveys_helper_test.rb │ │ │ ├── assignments_helper_test.rb │ │ │ └── conferences_helper_test.rb │ │ ├── models │ │ │ ├── email_test.rb │ │ │ ├── song_test.rb │ │ │ ├── tag_test.rb │ │ │ ├── task_test.rb │ │ │ ├── user_test.rb │ │ │ ├── answer_test.rb │ │ │ ├── artist_test.rb │ │ │ ├── person_test.rb │ │ │ ├── profile_test.rb │ │ │ ├── project_test.rb │ │ │ ├── speaker_test.rb │ │ │ ├── sub_task_test.rb │ │ │ ├── survey_test.rb │ │ │ ├── assignment_test.rb │ │ │ ├── conference_test.rb │ │ │ ├── producer_test.rb │ │ │ ├── question_test.rb │ │ │ ├── presentation_test.rb │ │ │ └── project_tag_test.rb │ │ └── controllers │ │ │ ├── projects_controller_test.rb │ │ │ ├── assignments_controller_test.rb │ │ │ ├── users_controller_test.rb │ │ │ ├── songs_controller_test.rb │ │ │ └── conferences_controller_test.rb │ ├── config │ │ ├── initializers │ │ │ ├── gender.rb │ │ │ ├── cookies_serializer.rb │ │ │ ├── session_store.rb │ │ │ ├── mime_types.rb │ │ │ ├── filter_parameter_logging.rb │ │ │ ├── assets.rb │ │ │ ├── backtrace_silencers.rb │ │ │ ├── wrap_parameters.rb │ │ │ ├── inflections.rb │ │ │ └── simple_form.rb │ │ ├── environment.rb │ │ ├── boot.rb │ │ ├── database.yml │ │ ├── locales │ │ │ ├── en.yml │ │ │ └── simple_form.en.yml │ │ ├── application.rb │ │ ├── secrets.yml │ │ ├── environments │ │ │ ├── development.rb │ │ │ ├── test.rb │ │ │ └── production.rb │ │ └── routes.rb │ ├── bin │ │ ├── rake │ │ ├── bundle │ │ └── rails │ ├── config.ru │ ├── db │ │ ├── migrate │ │ │ ├── 20140806122911_add_owner_to_projects.rb │ │ │ ├── 20140821074917_add_assignment_ref_to_tasks.rb │ │ │ ├── 20140806122945_create_tags.rb │ │ │ ├── 20140729170014_create_surveys.rb │ │ │ ├── 20140820071344_create_assignments.rb │ │ │ ├── 20140729164623_create_songs.rb │ │ │ ├── 20140806122636_create_projects.rb │ │ │ ├── 20140729165425_create_conferences.rb │ │ │ ├── 20140729162246_create_emails.rb │ │ │ ├── 20140729164653_create_artists.rb │ │ │ ├── 20140729162110_create_users.rb │ │ │ ├── 20140729170034_create_questions.rb │ │ │ ├── 20140729170112_create_answers.rb │ │ │ ├── 20140806123008_create_project_tags.rb │ │ │ ├── 20140729164713_create_producers.rb │ │ │ ├── 20140729162335_create_profiles.rb │ │ │ ├── 20140729165445_create_speakers.rb │ │ │ ├── 20140729165506_create_presentations.rb │ │ │ ├── 20140806122735_create_tasks.rb │ │ │ ├── 20140806122834_create_people.rb │ │ │ └── 20140806122805_create_sub_tasks.rb │ │ └── schema.rb │ ├── Rakefile │ └── README.rdoc ├── action_form_test.rb ├── fixtures │ ├── user_form_fixture.rb │ └── user_with_email_form_fixture.rb ├── forms │ ├── poro_form_fixture.rb │ ├── poro_form_test.rb │ ├── single_model_form_test.rb │ ├── nested_model_form_test.rb │ ├── two_nesting_level_form_test.rb │ └── nested_models_form_test.rb ├── test_helper.rb └── generators │ ├── form_install_generator_test.rb │ └── form_generator_test.rb ├── lib ├── action_form │ ├── version.rb │ ├── too_many_records.rb │ ├── form_definition.rb │ ├── form_helpers.rb │ ├── view_helpers.rb │ ├── base.rb │ ├── form.rb │ └── form_collection.rb ├── tasks │ └── action_form_tasks.rake ├── rails │ └── generators │ │ └── form │ │ ├── templates │ │ └── form.rb │ │ ├── form_generator.rb │ │ └── form_install_generator.rb └── action_form.rb ├── Gemfile ├── .gitignore ├── .travis.yml ├── Rakefile ├── actionform.gemspec ├── MIT-LICENSE ├── app └── assets │ └── javascripts │ └── action_form.js ├── Gemfile.lock └── README.md /test/dummy/log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/app/mailers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/app/models/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/app/assets/images/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/test/fixtures/demo.txt: -------------------------------------------------------------------------------- 1 | 123 2 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/songs_helper.rb: -------------------------------------------------------------------------------- 1 | module SongsHelper 2 | end 3 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/surveys_helper.rb: -------------------------------------------------------------------------------- 1 | module SurveysHelper 2 | end 3 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/users_helper.rb: -------------------------------------------------------------------------------- 1 | module UsersHelper 2 | end 3 | -------------------------------------------------------------------------------- /test/dummy/app/models/tag.rb: -------------------------------------------------------------------------------- 1 | class Tag < ActiveRecord::Base 2 | end 3 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/projects_helper.rb: -------------------------------------------------------------------------------- 1 | module ProjectsHelper 2 | end 3 | -------------------------------------------------------------------------------- /lib/action_form/version.rb: -------------------------------------------------------------------------------- 1 | module ActionForm 2 | VERSION = "0.0.1" 3 | end 4 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/assignments_helper.rb: -------------------------------------------------------------------------------- 1 | module AssignmentsHelper 2 | end 3 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/conferences_helper.rb: -------------------------------------------------------------------------------- 1 | module ConferencesHelper 2 | end 3 | -------------------------------------------------------------------------------- /test/dummy/app/models/person.rb: -------------------------------------------------------------------------------- 1 | class Person < ActiveRecord::Base 2 | belongs_to :project 3 | end 4 | -------------------------------------------------------------------------------- /test/dummy/app/models/sub_task.rb: -------------------------------------------------------------------------------- 1 | class SubTask < ActiveRecord::Base 2 | belongs_to :task 3 | end 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | gem 'jquery-rails' 6 | gem 'simple_form' 7 | -------------------------------------------------------------------------------- /test/dummy/app/models/answer.rb: -------------------------------------------------------------------------------- 1 | class Answer < ActiveRecord::Base 2 | belongs_to :question 3 | end 4 | -------------------------------------------------------------------------------- /lib/action_form/too_many_records.rb: -------------------------------------------------------------------------------- 1 | module ActionForm 2 | class TooManyRecords < RuntimeError 3 | end 4 | end -------------------------------------------------------------------------------- /test/dummy/app/models/presentation.rb: -------------------------------------------------------------------------------- 1 | class Presentation < ActiveRecord::Base 2 | belongs_to :speaker 3 | end 4 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/gender.rb: -------------------------------------------------------------------------------- 1 | require "act_as_gendered" 2 | 3 | ActiveRecord::Base.send(:include, ActAsGendered) -------------------------------------------------------------------------------- /test/dummy/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /test/dummy/app/views/projects/new.html.erb: -------------------------------------------------------------------------------- 1 |

New project

2 | 3 | <%= render 'form' %> 4 | <%= link_to 'Back', projects_path %> -------------------------------------------------------------------------------- /test/dummy/test/helpers/songs_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SongsHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/test/helpers/users_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class UsersHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /lib/tasks/action_form_tasks.rake: -------------------------------------------------------------------------------- 1 | # desc "Explaining what the task does" 2 | # task :action_form do 3 | # # Task goes here 4 | # end 5 | -------------------------------------------------------------------------------- /test/dummy/app/models/project_tag.rb: -------------------------------------------------------------------------------- 1 | class ProjectTag < ActiveRecord::Base 2 | belongs_to :project 3 | belongs_to :tag 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/views/songs/new.html.erb: -------------------------------------------------------------------------------- 1 |

New song

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Back', songs_path %> 6 | -------------------------------------------------------------------------------- /test/dummy/app/views/users/new.html.erb: -------------------------------------------------------------------------------- 1 |

New user

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Back', users_path %> 6 | -------------------------------------------------------------------------------- /test/dummy/test/helpers/projects_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ProjectsHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/test/helpers/surveys_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SurveysHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/views/surveys/new.html.erb: -------------------------------------------------------------------------------- 1 |

New survey

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Back', surveys_path %> 6 | -------------------------------------------------------------------------------- /test/dummy/app/models/assignment.rb: -------------------------------------------------------------------------------- 1 | class Assignment < ActiveRecord::Base 2 | has_many :tasks 3 | validates :name, uniqueness: true 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/models/email.rb: -------------------------------------------------------------------------------- 1 | class Email < ActiveRecord::Base 2 | belongs_to :user 3 | 4 | validates :address, uniqueness: true 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/test/helpers/assignments_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AssignmentsHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/test/helpers/conferences_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ConferencesHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/models/question.rb: -------------------------------------------------------------------------------- 1 | class Question < ActiveRecord::Base 2 | belongs_to :survey 3 | has_many :answers, dependent: :destroy 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/views/assignments/new.html.erb: -------------------------------------------------------------------------------- 1 |

New assignment

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Back', assignments_path %> 6 | -------------------------------------------------------------------------------- /test/dummy/app/views/conferences/new.html.erb: -------------------------------------------------------------------------------- 1 |

New conference

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Back', conferences_path %> 6 | -------------------------------------------------------------------------------- /test/dummy/app/views/users/_email.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= email.label :address %>
3 | <%= email.text_field :address %> 4 |
-------------------------------------------------------------------------------- /test/dummy/app/models/task.rb: -------------------------------------------------------------------------------- 1 | class Task < ActiveRecord::Base 2 | belongs_to :assignment 3 | belongs_to :project 4 | has_many :sub_tasks 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/app/models/producer.rb: -------------------------------------------------------------------------------- 1 | class Producer < ActiveRecord::Base 2 | belongs_to :artist 3 | 4 | validates :name, :studio, uniqueness: true 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/app/models/song.rb: -------------------------------------------------------------------------------- 1 | class Song < ActiveRecord::Base 2 | has_one :artist, dependent: :destroy 3 | 4 | validates :title, uniqueness: true 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /test/dummy/app/views/projects/_tag_fields.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.input :name, :hint => 'how should it be tagged', :label => false %> 3 |
-------------------------------------------------------------------------------- /test/dummy/test/fixtures/assignments.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | yard: 4 | name: Yard Work -------------------------------------------------------------------------------- /test/dummy/app/models/profile.rb: -------------------------------------------------------------------------------- 1 | class Profile < ActiveRecord::Base 2 | belongs_to :user 3 | 4 | validates :twitter_name, :github_name, uniqueness: true 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/app/models/survey.rb: -------------------------------------------------------------------------------- 1 | class Survey < ActiveRecord::Base 2 | has_many :questions, dependent: :destroy 3 | 4 | validates :name, uniqueness: true 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/app/views/songs/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Editing song

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Show', @song %> | 6 | <%= link_to 'Back', songs_path %> 7 | -------------------------------------------------------------------------------- /test/dummy/app/views/users/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Editing user

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Show', @user %> | 6 | <%= link_to 'Back', users_path %> 7 | -------------------------------------------------------------------------------- /test/dummy/app/views/surveys/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Editing survey

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Show', @survey %> | 6 | <%= link_to 'Back', surveys_path %> 7 | -------------------------------------------------------------------------------- /test/dummy/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../../config/application', __FILE__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /test/dummy/test/models/email_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class EmailTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/test/models/song_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SongTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/test/models/tag_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TagTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/test/models/task_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TaskTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/test/models/user_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class UserTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/action_form_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ActionFormTest < ActiveSupport::TestCase 4 | test "truth" do 5 | assert_kind_of Module, ActionForm 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.action_dispatch.cookies_serializer = :json -------------------------------------------------------------------------------- /test/dummy/test/fixtures/conferences.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | ruby: 4 | name: EuRuCo 5 | city: Athens 6 | -------------------------------------------------------------------------------- /test/dummy/test/models/answer_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AnswerTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/test/models/artist_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ArtistTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/test/models/person_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class PersonTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/test/models/profile_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ProfileTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/test/models/project_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ProjectTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/test/models/speaker_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SpeakerTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/test/models/sub_task_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SubTaskTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/test/models/survey_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SurveyTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/models/artist.rb: -------------------------------------------------------------------------------- 1 | class Artist < ActiveRecord::Base 2 | has_one :producer, dependent: :destroy 3 | belongs_to :song 4 | 5 | validates :name, uniqueness: true 6 | end 7 | -------------------------------------------------------------------------------- /test/dummy/app/views/projects/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Editing project

2 | 3 | <%= render 'form' %> 4 | <%= link_to 'Show', @project %> 5 | <%= '|' %> 6 | <%= link_to 'Back', projects_path %> -------------------------------------------------------------------------------- /test/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /test/dummy/test/fixtures/artists.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | kanye: 4 | name: Kanye West 5 | song: lockdown 6 | -------------------------------------------------------------------------------- /test/dummy/test/fixtures/songs.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | lockdown: 4 | title: Love Lockdown 5 | length: 350 6 | -------------------------------------------------------------------------------- /test/dummy/test/fixtures/users.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | peter: 4 | name: m-peter 5 | age: 23 6 | gender: 0 7 | -------------------------------------------------------------------------------- /test/dummy/test/models/assignment_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AssignmentTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/test/models/conference_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ConferenceTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/test/models/producer_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ProducerTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/test/models/question_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class QuestionTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/views/assignments/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Editing assignment

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Show', @assignment %> | 6 | <%= link_to 'Back', assignments_path %> 7 | -------------------------------------------------------------------------------- /test/dummy/app/views/conferences/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Editing conference

2 | 3 | <%= render 'form' %> 4 | 5 | <%= link_to 'Show', @conference %> | 6 | <%= link_to 'Back', conferences_path %> 7 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_dummy_session' 4 | -------------------------------------------------------------------------------- /test/dummy/test/fixtures/surveys.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | programming: 4 | name: Your favorite programming language 5 | -------------------------------------------------------------------------------- /test/dummy/test/models/presentation_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class PresentationTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/test/models/project_tag_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ProjectTagTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle/ 2 | log/*.log 3 | pkg/ 4 | test/dummy/db/*.sqlite3 5 | test/dummy/db/*.sqlite3-journal 6 | test/dummy/log/*.log 7 | test/dummy/tmp/ 8 | test/dummy/.sass-cache 9 | tmp 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.1 4 | - 2.2 5 | script: 6 | - cd test/dummy/ 7 | - RAILS_ENV=test bundle exec rake db:migrate 8 | - cd ../.. 9 | - bundle exec rake 10 | -------------------------------------------------------------------------------- /test/dummy/app/models/speaker.rb: -------------------------------------------------------------------------------- 1 | class Speaker < ActiveRecord::Base 2 | has_many :presentations, dependent: :destroy 3 | belongs_to :conference 4 | validates :name, uniqueness: true 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20140806122911_add_owner_to_projects.rb: -------------------------------------------------------------------------------- 1 | class AddOwnerToProjects < ActiveRecord::Migration 2 | def change 3 | add_column :projects, :owner_id, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/test/fixtures/emails.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | peters: 4 | address: markoupetr@gmail.com 5 | user: peter 6 | -------------------------------------------------------------------------------- /test/dummy/test/fixtures/tags.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | forms: 4 | name: Html forms 5 | 6 | models: 7 | name: Nested models -------------------------------------------------------------------------------- /test/dummy/test/fixtures/producers.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | jayz: 4 | name: Jay-Z 5 | studio: Ztudio 6 | artist: kanye 7 | -------------------------------------------------------------------------------- /test/dummy/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 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20140821074917_add_assignment_ref_to_tasks.rb: -------------------------------------------------------------------------------- 1 | class AddAssignmentRefToTasks < ActiveRecord::Migration 2 | def change 3 | add_reference :tasks, :assignment, index: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/test/fixtures/questions.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | content: Which language allows closures? 5 | survey: programming 6 | -------------------------------------------------------------------------------- /test/dummy/test/fixtures/speakers.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | peter: 4 | name: Peter Markou 5 | occupation: Developer 6 | conference: ruby 7 | -------------------------------------------------------------------------------- /test/dummy/app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | act_as_gendered 3 | has_one :email, dependent: :destroy 4 | has_one :profile, dependent: :destroy 5 | 6 | validates :name, uniqueness: true 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/test/fixtures/profiles.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | peters: 4 | twitter_name: twitter_peter 5 | github_name: github_peter 6 | user: peter 7 | -------------------------------------------------------------------------------- /test/dummy/test/fixtures/project_tags.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | project: gsoc 5 | tag: forms 6 | 7 | two: 8 | project: gsoc 9 | tag: models -------------------------------------------------------------------------------- /test/dummy/app/views/projects/_owner_fields.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.input :name %> 3 | <%= f.input :role %> 4 | <%= f.input :description %> 5 | <%= link_to_remove_association "remove owner", f %> 6 |
-------------------------------------------------------------------------------- /test/dummy/db/migrate/20140806122945_create_tags.rb: -------------------------------------------------------------------------------- 1 | class CreateTags < ActiveRecord::Migration 2 | def change 3 | create_table :tags do |t| 4 | t.string :name 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/projects/_contributor_fields.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.input :name %> 3 | <%= f.input :role %> 4 | <%= f.input :description %> 5 | <%= link_to_remove_association "remove contributor", f %> 6 |
-------------------------------------------------------------------------------- /test/dummy/app/views/assignments/_task_fields.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= f.label :task %>
4 | <%= f.text_field :name %> 5 | <%= link_to_remove_association "Delete", f %> 6 |
7 |
-------------------------------------------------------------------------------- /test/dummy/db/migrate/20140729170014_create_surveys.rb: -------------------------------------------------------------------------------- 1 | class CreateSurveys < ActiveRecord::Migration 2 | def change 3 | create_table :surveys do |t| 4 | t.string :name 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/dummy/app/forms/assignment_form.rb: -------------------------------------------------------------------------------- 1 | class AssignmentForm < ActionForm::Base 2 | self.main_model = :assignment 3 | 4 | attribute :name, required: true 5 | 6 | association :tasks, records: 3 do 7 | attribute :name, required: true 8 | end 9 | end -------------------------------------------------------------------------------- /test/dummy/db/migrate/20140820071344_create_assignments.rb: -------------------------------------------------------------------------------- 1 | class CreateAssignments < ActiveRecord::Migration 2 | def change 3 | create_table :assignments do |t| 4 | t.string :name 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :exception 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20140729164623_create_songs.rb: -------------------------------------------------------------------------------- 1 | class CreateSongs < ActiveRecord::Migration 2 | def change 3 | create_table :songs do |t| 4 | t.string :title 5 | t.string :length 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/dummy/test/fixtures/projects.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | yard: 4 | name: Yard Work 5 | 6 | gsoc: 7 | name: Add Form Models 8 | description: Nesting models in a single form 9 | owner: peter -------------------------------------------------------------------------------- /test/dummy/app/models/project.rb: -------------------------------------------------------------------------------- 1 | class Project < ActiveRecord::Base 2 | has_many :tasks 3 | has_many :contributors, :class_name => 'Person' 4 | belongs_to :owner, :class_name => 'Person' 5 | 6 | has_many :project_tags 7 | has_many :tags, through: :project_tags 8 | end 9 | -------------------------------------------------------------------------------- /test/dummy/app/views/surveys/_answer_fields.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= f.label :content, "Answer" %>
4 | <%= f.text_field :content, :size => 40 %> 5 | <%= link_to_remove_association "Delete", f %> 6 |
7 |
-------------------------------------------------------------------------------- /test/fixtures/user_form_fixture.rb: -------------------------------------------------------------------------------- 1 | class UserFormFixture < ActionForm::Base 2 | self.main_model = :user 3 | 4 | attributes :name, :age, :gender, required: true 5 | 6 | validates :name, length: { in: 6..20 } 7 | validates :age, numericality: { only_integer: true } 8 | end -------------------------------------------------------------------------------- /test/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__) 3 | 4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 5 | $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__) 6 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20140806122636_create_projects.rb: -------------------------------------------------------------------------------- 1 | class CreateProjects < ActiveRecord::Migration 2 | def change 3 | create_table :projects do |t| 4 | t.string :name 5 | t.string :description 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/dummy/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.expand_path('../config/application', __FILE__) 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /test/dummy/app/views/projects/_sub_task_fields.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | Sub Task 4 | <%= f.input :name %> 5 | <%= f.input :description %> 6 | <%= link_to_remove_association "remove sub-task", f %> 7 |
8 |
-------------------------------------------------------------------------------- /test/dummy/app/views/songs/_producer.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= producer.label :name, "Producer Name" %>
3 | <%= producer.text_field :name %> 4 |
5 | 6 |
7 | <%= producer.label :studio %>
8 | <%= producer.text_field :studio %> 9 |
-------------------------------------------------------------------------------- /test/dummy/db/migrate/20140729165425_create_conferences.rb: -------------------------------------------------------------------------------- 1 | class CreateConferences < ActiveRecord::Migration 2 | def change 3 | create_table :conferences do |t| 4 | t.string :name 5 | t.string :city 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/dummy/test/fixtures/answers.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | ruby: 4 | content: Ruby Programming Language 5 | question: one 6 | 7 | cs: 8 | content: CSharp Programming Language 9 | question: one 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/users/_profile.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= profile.label :twitter_name %>
3 | <%= profile.text_field :twitter_name %> 4 |
5 | 6 |
7 | <%= profile.label :github_name %>
8 | <%= profile.text_field :github_name %> 9 |
-------------------------------------------------------------------------------- /test/dummy/db/migrate/20140729162246_create_emails.rb: -------------------------------------------------------------------------------- 1 | class CreateEmails < ActiveRecord::Migration 2 | def change 3 | create_table :emails do |t| 4 | t.string :address 5 | t.references :user, index: true 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20140729164653_create_artists.rb: -------------------------------------------------------------------------------- 1 | class CreateArtists < ActiveRecord::Migration 2 | def change 3 | create_table :artists do |t| 4 | t.string :name 5 | t.references :song, index: true 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20140729162110_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration 2 | def change 3 | create_table :users do |t| 4 | t.string :name 5 | t.integer :age 6 | t.integer :gender 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20140729170034_create_questions.rb: -------------------------------------------------------------------------------- 1 | class CreateQuestions < ActiveRecord::Migration 2 | def change 3 | create_table :questions do |t| 4 | t.text :content 5 | t.references :survey, index: true 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20140729170112_create_answers.rb: -------------------------------------------------------------------------------- 1 | class CreateAnswers < ActiveRecord::Migration 2 | def change 3 | create_table :answers do |t| 4 | t.text :content 5 | t.references :question, index: true 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/dummy/app/models/conference.rb: -------------------------------------------------------------------------------- 1 | class Conference < ActiveRecord::Base 2 | has_one :speaker, dependent: :destroy 3 | validates :name, uniqueness: true 4 | 5 | def photo=(val) 6 | @photo = val.original_filename 7 | end 8 | 9 | def photo 10 | @photo 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/dummy/test/fixtures/presentations.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | ruby_oop: 4 | topic: Ruby OOP 5 | duration: 1h 6 | speaker: peter 7 | 8 | ruby_closures: 9 | topic: Ruby Closures 10 | duration: 1h 11 | speaker: peter 12 | -------------------------------------------------------------------------------- /test/forms/poro_form_fixture.rb: -------------------------------------------------------------------------------- 1 | class Poro 2 | include ActiveModel::Model 3 | 4 | attr_accessor :name, :city 5 | 6 | def save 7 | true 8 | end 9 | end 10 | 11 | class PoroFormFixture < ActionForm::Base 12 | self.main_model = :conference 13 | attributes :name, :city, required: true 14 | end 15 | -------------------------------------------------------------------------------- /test/dummy/app/forms/song_form.rb: -------------------------------------------------------------------------------- 1 | class SongForm < ActionForm::Base 2 | attributes :title, :length, required: true 3 | 4 | association :artist do 5 | attribute :name, required: true 6 | 7 | association :producer do 8 | attributes :name, :studio, required: true 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20140806123008_create_project_tags.rb: -------------------------------------------------------------------------------- 1 | class CreateProjectTags < ActiveRecord::Migration 2 | def change 3 | create_table :project_tags do |t| 4 | t.references :project, index: true 5 | t.references :tag, index: true 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/rails/generators/form/templates/form.rb: -------------------------------------------------------------------------------- 1 | <% module_namespacing do -%> 2 | class <%= class_name %>Form < ActionForm::Base 3 | <%- if attributes.present? -%> 4 | attributes <%= attributes.map {|a| ":#{a.name}" }.join(", ") %> 5 | <%- else -%> 6 | # attributes :name, :email 7 | <%- end -%> 8 | end 9 | <% end -%> 10 | -------------------------------------------------------------------------------- /test/dummy/app/forms/survey_form.rb: -------------------------------------------------------------------------------- 1 | class SurveyForm < ActionForm::Base 2 | attribute :name, required: true 3 | 4 | association :questions do 5 | attribute :content, required: true 6 | 7 | association :answers, records: 2 do 8 | attribute :content, required: true 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20140729164713_create_producers.rb: -------------------------------------------------------------------------------- 1 | class CreateProducers < ActiveRecord::Migration 2 | def change 3 | create_table :producers do |t| 4 | t.string :name 5 | t.string :studio 6 | t.references :artist, index: true 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/dummy/app/views/songs/_artist.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= artist.label :name, "Artist Name" %>
3 | <%= artist.text_field :name %> 4 |
5 | 6 |

Producer Details

7 | <%= artist.fields_for :producer do |producer_fields| %> 8 | <%= render "producer", :producer => producer_fields%> 9 | <% end %> -------------------------------------------------------------------------------- /test/dummy/db/migrate/20140729162335_create_profiles.rb: -------------------------------------------------------------------------------- 1 | class CreateProfiles < ActiveRecord::Migration 2 | def change 3 | create_table :profiles do |t| 4 | t.string :twitter_name 5 | t.string :github_name 6 | t.references :user, index: true 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20140729165445_create_speakers.rb: -------------------------------------------------------------------------------- 1 | class CreateSpeakers < ActiveRecord::Migration 2 | def change 3 | create_table :speakers do |t| 4 | t.string :name 5 | t.string :occupation 6 | t.references :conference, index: true 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/dummy/test/fixtures/sub_tasks.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | name: MyString 5 | description: MyString 6 | done: false 7 | task_id: 8 | 9 | two: 10 | name: MyString 11 | description: MyString 12 | done: false 13 | task_id: 14 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20140729165506_create_presentations.rb: -------------------------------------------------------------------------------- 1 | class CreatePresentations < ActiveRecord::Migration 2 | def change 3 | create_table :presentations do |t| 4 | t.string :topic 5 | t.string :duration 6 | t.references :speaker, index: true 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20140806122735_create_tasks.rb: -------------------------------------------------------------------------------- 1 | class CreateTasks < ActiveRecord::Migration 2 | def change 3 | create_table :tasks do |t| 4 | t.string :name 5 | t.string :description 6 | t.boolean :done 7 | t.references :project, index: true 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20140806122834_create_people.rb: -------------------------------------------------------------------------------- 1 | class CreatePeople < ActiveRecord::Migration 2 | def change 3 | create_table :people do |t| 4 | t.string :name 5 | t.string :role 6 | t.string :description 7 | t.references :project, index: true 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20140806122805_create_sub_tasks.rb: -------------------------------------------------------------------------------- 1 | class CreateSubTasks < ActiveRecord::Migration 2 | def change 3 | create_table :sub_tasks do |t| 4 | t.string :name 5 | t.string :description 6 | t.boolean :done 7 | t.references :task, index: true 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/dummy/test/fixtures/tasks.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | rake: 4 | name: rake the leaves 5 | project: yard 6 | assignment: yard 7 | 8 | paint: 9 | name: paint the fence 10 | project: yard 11 | assignment: yard 12 | 13 | clean: 14 | name: clean the gutters 15 | assignment: yard -------------------------------------------------------------------------------- /test/dummy/app/forms/conference_form.rb: -------------------------------------------------------------------------------- 1 | class ConferenceForm < ActionForm::Base 2 | attributes :name, :city, required: true 3 | attributes :photo 4 | 5 | association :speaker do 6 | attribute :name, :occupation, required: true 7 | 8 | association :presentations, records: 2 do 9 | attribute :topic, :duration, required: true 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/fixtures/user_with_email_form_fixture.rb: -------------------------------------------------------------------------------- 1 | class UserWithEmailFormFixture < ActionForm::Base 2 | self.main_model = :user 3 | attributes :name, :age, :gender, required: true 4 | 5 | association :email do 6 | attribute :address, required: true 7 | end 8 | 9 | validates :name, length: { in: 6..20 } 10 | validates :age, numericality: { only_integer: true } 11 | end -------------------------------------------------------------------------------- /test/dummy/app/views/assignments/show.html.erb: -------------------------------------------------------------------------------- 1 |

2 | Assignment Name: 3 | <%= @assignment.name %> 4 |

5 | 6 |

Tasks

7 |
    8 | <% @assignment.tasks.each do |task| %> 9 |
  1. 10 | <%= task.name %> 11 |
  2. 12 | <% end %> 13 |
14 | 15 | <%= link_to 'Edit', edit_assignment_path(@assignment) %> | 16 | <%= link_to 'Back', assignments_path %> 17 | -------------------------------------------------------------------------------- /test/dummy/test/fixtures/people.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | peter: 4 | name: Peter Markou 5 | role: GSoC Student 6 | description: Working on adding Form Models 7 | project: gsoc 8 | 9 | carlos: 10 | name: Carlos Silva 11 | role: RoR Core Member 12 | description: Assisting Peter throughout GSoC 13 | project: gsoc -------------------------------------------------------------------------------- /test/dummy/app/views/conferences/_presentation_fields.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= f.label :topic %>
4 | <%= f.text_field :topic %> 5 |
6 | 7 |
8 | <%= f.label :duration %>
9 | <%= f.text_field :duration %> 10 |
11 | 12 | <%= link_to_remove_association "Delete", f %> 13 | 14 |
15 |
16 | -------------------------------------------------------------------------------- /test/dummy/app/views/projects/_project_tag_fields.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= f.association :tag, :collection => Tag.order(:name), :prompt => 'Choose an existing tag' %> 4 |
5 | <%= link_to_add_association 'or create a new tag', f, :tag, :render_options => { :wrapper => 'inline' } %> 6 | <%= link_to_remove_association "remove tag", f %> 7 |
-------------------------------------------------------------------------------- /test/dummy/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Precompile additional assets. 7 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 8 | # Rails.application.config.assets.precompile += %w( search.js ) 9 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] = "test" 2 | 3 | require File.expand_path("../dummy/config/environment.rb", __FILE__) 4 | require "rails/test_help" 5 | 6 | Rails.backtrace_cleaner.remove_silencers! 7 | 8 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } 9 | 10 | if ActiveSupport::TestCase.method_defined?(:fixture_path=) 11 | ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__) 12 | end 13 | -------------------------------------------------------------------------------- /test/dummy/lib/templates/erb/scaffold/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%%= simple_form_for(@<%= singular_table_name %>) do |f| %> 2 | <%%= f.error_notification %> 3 | 4 |
5 | <%- attributes.each do |attribute| -%> 6 | <%%= f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %> %> 7 | <%- end -%> 8 |
9 | 10 |
11 | <%%= f.button :submit %> 12 |
13 | <%% end %> 14 | -------------------------------------------------------------------------------- /test/dummy/app/forms/user_form.rb: -------------------------------------------------------------------------------- 1 | class UserForm < ActionForm::Base 2 | self.main_model = :user 3 | attributes :name, :age, :gender, required: true 4 | 5 | association :email do 6 | attribute :address, required: true 7 | end 8 | 9 | association :profile do 10 | attributes :twitter_name, :github_name, required: true 11 | end 12 | 13 | validates :name, length: { in: 6..20 } 14 | validates :age, numericality: { only_integer: true } 15 | end -------------------------------------------------------------------------------- /test/dummy/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 to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Action Form Demo (using Simple Form) 5 | <%= stylesheet_link_tag 'application', media: 'all' %> 6 | <%= javascript_include_tag 'application' %> 7 | <%= csrf_meta_tags %> 8 | 9 | 10 |

<%= link_to 'Action Form Demo', '#' %>

11 | 12 |
13 | <%= yield %> 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /test/dummy/app/views/conferences/_speaker.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.label :name, "Speaker Name" %>
3 | <%= f.text_field :name %> 4 |
5 | 6 |
7 | <%= f.label :occupation %>
8 | <%= f.text_field :occupation %> 9 |
10 | 11 |

Presentantions

12 | <%= f.fields_for :presentations do |presentations_fields| %> 13 | <%= render "presentation_fields", :f => presentations_fields %> 14 | <% end %> 15 | 16 | -------------------------------------------------------------------------------- /test/dummy/app/views/songs/show.html.erb: -------------------------------------------------------------------------------- 1 |

2 | Title: 3 | <%= @song.title %> 4 |

5 | 6 |

7 | Length: 8 | <%= @song.length %> 9 |

10 | 11 |

12 | Artist Name: 13 | <%= @song.artist.name %> 14 |

15 | 16 |

17 | Producer Name: 18 | <%= @song.artist.producer.name %> 19 |

20 | 21 |

22 | Studio: 23 | <%= @song.artist.producer.studio %> 24 |

25 | 26 | <%= link_to 'Edit', edit_song_path(@song) %> | 27 | <%= link_to 'Back', songs_path %> -------------------------------------------------------------------------------- /test/dummy/app/views/surveys/_question_fields.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= f.label :content, "Question" %>
4 | <%= link_to_remove_association "remove", f %>
5 | <%= f.text_area :content, :cols => 30, :rows => 2 %> 6 |
7 | 8 |

Answers

9 | <%= f.fields_for :answers do |answer_fields| %> 10 | <%= render "answer_fields", :f => answer_fields %> 11 | <% end %> 12 | 13 | 16 | 17 |
18 | -------------------------------------------------------------------------------- /test/dummy/app/views/surveys/show.html.erb: -------------------------------------------------------------------------------- 1 |

2 | Name: 3 | <%=h @survey.name %> 4 |

5 | 6 |
    7 | <% for question in @survey.questions %> 8 |
  1. 9 | <%=h question.content %> 10 | 15 |
  2. 16 | <% end %> 17 |
18 | 19 |

20 | <%= link_to "Edit", edit_survey_path(@survey) %> | 21 | <%= link_to "Destroy", @survey, :confirm => 'Are you sure?', :method => :delete %> | 22 | <%= link_to "View All", surveys_path %> 23 |

-------------------------------------------------------------------------------- /test/dummy/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /test/dummy/README.rdoc: -------------------------------------------------------------------------------- 1 | == README 2 | 3 | This README would normally document whatever steps are necessary to get the 4 | application up and running. 5 | 6 | Things you may want to cover: 7 | 8 | * Ruby version 9 | 10 | * System dependencies 11 | 12 | * Configuration 13 | 14 | * Database creation 15 | 16 | * Database initialization 17 | 18 | * How to run the test suite 19 | 20 | * Services (job queues, cache servers, search engines, etc.) 21 | 22 | * Deployment instructions 23 | 24 | * ... 25 | 26 | 27 | Please feel free to use a different markup language if you do not plan to run 28 | rake doc:app. 29 | -------------------------------------------------------------------------------- /lib/action_form.rb: -------------------------------------------------------------------------------- 1 | module ActionForm 2 | autoload :Base, 'action_form/base' 3 | autoload :Form, 'action_form/form' 4 | autoload :FormCollection, 'action_form/form_collection' 5 | autoload :FormDefinition, 'action_form/form_definition' 6 | autoload :TooManyRecords, 'action_form/too_many_records' 7 | autoload :ViewHelpers, 'action_form/view_helpers' 8 | autoload :FormHelpers, 'action_form/form_helpers' 9 | 10 | class Engine < ::Rails::Engine 11 | initializer "action_form.initialize" do |app| 12 | ActiveSupport.on_load :action_view do 13 | include ActionForm::ViewHelpers 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/dummy/app/forms/project_form.rb: -------------------------------------------------------------------------------- 1 | class ProjectForm < ActionForm::Base 2 | attributes :name, :description, :owner_id 3 | 4 | association :tasks do 5 | attributes :name, :description, :done 6 | 7 | association :sub_tasks do 8 | attributes :name, :description, :done 9 | end 10 | end 11 | 12 | association :contributors, records: 2 do 13 | attributes :name, :description, :role 14 | end 15 | 16 | association :project_tags do 17 | attribute :tag_id 18 | 19 | association :tag do 20 | attribute :name 21 | end 22 | end 23 | 24 | association :owner do 25 | attributes :name, :description, :role 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/dummy/app/views/surveys/index.html.erb: -------------------------------------------------------------------------------- 1 |

Listing surveys

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | <% @surveys.each do |survey| %> 13 | 14 | 15 | 16 | 17 | 18 | 19 | <% end %> 20 | 21 |
Name
<%= survey.name %><%= link_to 'Show', survey %><%= link_to 'Edit', edit_survey_path(survey) %><%= link_to 'Destroy', survey, method: :delete, data: { confirm: 'Are you sure?' } %>
22 | 23 |
24 | 25 | <%= link_to 'New Survey', new_survey_path %> 26 | -------------------------------------------------------------------------------- /test/dummy/app/views/users/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | 3 |

4 | Name: 5 | <%= @user.name %> 6 |

7 | 8 |

9 | Age: 10 | <%= @user.age %> 11 |

12 | 13 |

14 | Gender: 15 | <%= @user.gender_display_name %> 16 |

17 | 18 |

19 | Email: 20 | <%= @user.email.address %> 21 |

22 | 23 |

24 | Twitter: 25 | <%= @user.profile.twitter_name %> 26 |

27 | 28 |

29 | Github: 30 | <%= @user.profile.github_name %> 31 |

32 | <%= link_to 'Edit', edit_user_path(@user) %> | 33 | <%= link_to 'Back', users_path %> -------------------------------------------------------------------------------- /test/dummy/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: db/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: db/test.sqlite3 22 | 23 | production: 24 | <<: *default 25 | database: db/production.sqlite3 26 | -------------------------------------------------------------------------------- /test/dummy/app/views/assignments/index.html.erb: -------------------------------------------------------------------------------- 1 |

Listing assignments

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | <% @assignments.each do |assignment| %> 13 | 14 | 15 | 16 | 17 | 18 | 19 | <% end %> 20 | 21 |
Name
<%= assignment.name %><%= link_to 'Show', assignment %><%= link_to 'Edit', edit_assignment_path(assignment) %><%= link_to 'Destroy', assignment, method: :delete, data: { confirm: 'Are you sure?' } %>
22 | 23 |
24 | 25 | <%= link_to 'New Assignment', new_assignment_path %> 26 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | begin 2 | require 'bundler/setup' 3 | rescue LoadError 4 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks' 5 | end 6 | 7 | require 'rdoc/task' 8 | 9 | RDoc::Task.new(:rdoc) do |rdoc| 10 | rdoc.rdoc_dir = 'rdoc' 11 | rdoc.title = 'ActionForm' 12 | rdoc.options << '--line-numbers' 13 | rdoc.rdoc_files.include('README.rdoc') 14 | rdoc.rdoc_files.include('lib/**/*.rb') 15 | end 16 | 17 | 18 | 19 | 20 | Bundler::GemHelper.install_tasks 21 | 22 | require 'rake/testtask' 23 | 24 | Rake::TestTask.new(:test) do |t| 25 | t.libs << 'lib' 26 | t.libs << 'test' 27 | t.pattern = 'test/**/*_test.rb' 28 | t.verbose = false 29 | end 30 | 31 | 32 | task default: :test 33 | -------------------------------------------------------------------------------- /test/dummy/app/views/songs/index.html.erb: -------------------------------------------------------------------------------- 1 |

Listing songs

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | <% @songs.each do |song| %> 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | <% end %> 22 | 23 |
TitleLength
<%= song.title %><%= song.length %><%= link_to 'Show', song %><%= link_to 'Edit', edit_song_path(song) %><%= link_to 'Destroy', song, method: :delete, data: { confirm: 'Are you sure?' } %>
24 | 25 |
26 | 27 | <%= link_to 'New Song', new_song_path %> 28 | -------------------------------------------------------------------------------- /test/dummy/app/views/projects/index.html.erb: -------------------------------------------------------------------------------- 1 |

Listing projects

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | <% @projects.each do |project| %> 12 | 13 | 14 | 21 | 22 | <% end %> 23 | 24 |
NameActions
<%= project.name %> 15 | <%= link_to 'Show', project %> 16 | <%= '|' %> 17 | <%= link_to 'Edit', edit_project_path(project) %> 18 | <%= '|' %> 19 | <%= link_to 'Destroy', project, :confirm => 'Are you sure?', :method => :delete %> 20 |
25 | 26 | <%= link_to 'New Project', new_project_path %> -------------------------------------------------------------------------------- /test/dummy/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. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /test/generators/form_install_generator_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'rails/generators/test_case' 3 | require 'rails/generators/form/form_install_generator' 4 | 5 | class FormInstallGeneratorTest < Rails::Generators::TestCase 6 | tests Rails::Generators::FormInstallGenerator 7 | 8 | destination File.expand_path("../../tmp", __FILE__) 9 | setup :prepare_destination 10 | 11 | test "assert app folder contains forms sub-folder" do 12 | gen = generator 13 | gen.create_forms_app_directory 14 | assert_directory "app/forms" 15 | end 16 | 17 | test "assert test folder contains forms sub-folder" do 18 | gen = generator 19 | gen.create_forms_test_directory 20 | assert_directory "test/forms" 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/dummy/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /lib/rails/generators/form/form_generator.rb: -------------------------------------------------------------------------------- 1 | require 'rails/generators' 2 | require 'rails/generators/named_base' 3 | 4 | module Rails 5 | module Generators # :nodoc: 6 | class FormGenerator < Rails::Generators::NamedBase # :nodoc: 7 | desc 'This generator creates an action form file at app/forms' 8 | 9 | check_class_collision suffix: 'Form' 10 | 11 | hook_for :test_framework 12 | 13 | argument :attributes, type: :array, default: [], banner: "field1 field2 field3" 14 | 15 | def self.default_generator_root 16 | File.dirname(__FILE__) 17 | end 18 | 19 | def create_form_file 20 | template 'form.rb', File.join('app/forms', class_path, "#{file_name}_form.rb") 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/dummy/app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require action_form 16 | //= require_tree . 17 | -------------------------------------------------------------------------------- /lib/action_form/form_definition.rb: -------------------------------------------------------------------------------- 1 | module ActionForm 2 | class FormDefinition 3 | attr_accessor :assoc_name, :proc, :parent, :records 4 | 5 | def initialize(assoc_name, block, options={}) 6 | @assoc_name = assoc_name 7 | @proc = block 8 | @records = options[:records] 9 | end 10 | 11 | def to_form 12 | macro = association_reflection.macro 13 | 14 | case macro 15 | when :has_one, :belongs_to 16 | Form.new(assoc_name, parent, proc) 17 | when :has_many 18 | FormCollection.new(assoc_name, parent, proc, {records: records}) 19 | end 20 | end 21 | 22 | private 23 | 24 | def association_reflection 25 | parent.class.reflect_on_association(@assoc_name) 26 | end 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /test/dummy/app/views/users/index.html.erb: -------------------------------------------------------------------------------- 1 |

Listing users

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | <% @users.each do |user| %> 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | <% end %> 24 | 25 |
NameAgeGender
<%= user.name %><%= user.age %><%= user.gender %><%= link_to 'Show', user %><%= link_to 'Edit', edit_user_path(user) %><%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %>
26 | 27 |
28 | 29 | <%= link_to 'New User', new_user_path %> 30 | -------------------------------------------------------------------------------- /test/dummy/app/views/conferences/index.html.erb: -------------------------------------------------------------------------------- 1 |

Listing conferences

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | <% @conferences.each do |conference| %> 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | <% end %> 22 | 23 |
NameCity
<%= conference.name %><%= conference.city %><%= link_to 'Show', conference %><%= link_to 'Edit', edit_conference_path(conference) %><%= link_to 'Destroy', conference, method: :delete, data: { confirm: 'Are you sure?' } %>
24 | 25 |
26 | 27 | <%= link_to 'New Conference', new_conference_path %> 28 | -------------------------------------------------------------------------------- /test/dummy/app/views/conferences/show.html.erb: -------------------------------------------------------------------------------- 1 |

2 | Conference Name: 3 | <%= @conference.name %> 4 |

5 | 6 |

7 | City: 8 | <%= @conference.city %> 9 |

10 | 11 |

12 | Speaker Name: 13 | <%= @conference.speaker.name %> 14 |

15 | 16 |

17 | Occupation: 18 | <%= @conference.speaker.occupation %> 19 |

20 | 21 | <% @conference.speaker.presentations.each do |presentation| %> 22 |

23 | Topic: 24 | <%= presentation.topic %> 25 |

26 |

27 | Duration: 28 | <%= presentation.duration %> 29 |

30 | <% end %> 31 | 32 | <%= link_to 'Edit', edit_conference_path(@conference) %> | 33 | <%= link_to 'Back', conferences_path %> -------------------------------------------------------------------------------- /actionform.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path('../lib', __FILE__) 2 | 3 | require 'action_form/version' 4 | 5 | Gem::Specification.new do |s| 6 | s.name = 'actionform' 7 | s.version = ActionForm::VERSION 8 | s.authors = ['Petros Markou'] 9 | s.email = ['markoupetr@gmail.com'] 10 | s.homepage = 'https://github.com/rails/actionform' 11 | s.summary = 'Create nested forms with ease.' 12 | s.description = 'An alternative layer to accepts_nested_attributes_for by using Form Models.' 13 | s.license = 'MIT' 14 | 15 | s.files = Dir['{app,config,db,lib}/**/*', 'MIT-LICENSE', 'Rakefile', 'README.md'] 16 | s.test_files = Dir['test/**/*'] 17 | 18 | s.add_dependency 'rails', '~> 4.1' 19 | 20 | s.add_development_dependency 'sqlite3' 21 | s.add_development_dependency 'rake', '~> 10.3.2' 22 | end 23 | -------------------------------------------------------------------------------- /test/dummy/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any styles 10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new 11 | * file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | 17 | body { 18 | margin: 0 auto; 19 | width: 80%; 20 | } 21 | -------------------------------------------------------------------------------- /test/dummy/config/locales/simple_form.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | simple_form: 3 | "yes": 'Yes' 4 | "no": 'No' 5 | required: 6 | text: 'required' 7 | mark: '*' 8 | # You can uncomment the line below if you need to overwrite the whole required html. 9 | # When using html, text and mark won't be used. 10 | # html: '*' 11 | error_notification: 12 | default_message: "Please review the problems below:" 13 | # Labels and hints examples 14 | # labels: 15 | # defaults: 16 | # password: 'Password' 17 | # user: 18 | # new: 19 | # email: 'E-mail to sign in.' 20 | # edit: 21 | # email: 'E-mail.' 22 | # hints: 23 | # defaults: 24 | # username: 'User name to sign in.' 25 | # password: 'No special characters, please.' 26 | 27 | -------------------------------------------------------------------------------- /lib/rails/generators/form/form_install_generator.rb: -------------------------------------------------------------------------------- 1 | require 'rails/generators' 2 | require 'rails/generators/base' 3 | 4 | module Rails 5 | module Generators 6 | class FormInstallGenerator < Rails::Generators::Base 7 | 8 | desc "Creates a forms directory into your app and test directories and includes the necessary JS file." 9 | 10 | def create_forms_app_directory 11 | empty_directory "app/forms" 12 | end 13 | 14 | def create_forms_test_directory 15 | if File.directory?("spec") 16 | empty_directory "spec/forms" 17 | else 18 | empty_directory "test/forms" 19 | end 20 | end 21 | 22 | def include_js_file 23 | insert_into_file "app/assets/javascripts/application.js", "//= require action_form", :before => "//= require_tree ." 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/dummy/lib/act_as_gendered.rb: -------------------------------------------------------------------------------- 1 | module ActAsGendered 2 | MALE = { :value => 0, :display_name => "Male" } 3 | FEMALE = { :value => 1, :display_name => "Female" } 4 | 5 | def self.included(base) 6 | base.extend(ActAsGenderedMethods) 7 | end 8 | 9 | module ActAsGenderedMethods 10 | def act_as_gendered 11 | extend ClassMethods 12 | include InstanceMethods 13 | 14 | validates_inclusion_of :gender, :in => [MALE[:value], FEMALE[:value]] 15 | end 16 | end 17 | 18 | module ClassMethods 19 | def get_genders_dropdown 20 | { 21 | MALE[:display_name] => MALE[:value], 22 | FEMALE[:display_name] => FEMALE[:value] 23 | } 24 | end 25 | end 26 | 27 | module InstanceMethods 28 | def gender_display_name 29 | return gender == MALE[:value] ? 30 | MALE[:display_name] : 31 | FEMALE[:display_name] 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/dummy/app/views/assignments/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for @assignment_form do |f| %> 2 | <% if @assignment_form.errors.any? %> 3 |
4 |

<%= pluralize(@assignment_form.errors.count, "error") %> prohibited this project from being saved:

5 | 6 | 11 |
12 | <% end %> 13 | 14 |
15 | <%= f.label :name %>
16 | <%= f.text_field :name %> 17 |
18 | 19 |

Tasks Details

20 | <%= f.fields_for :tasks do |task_fields| %> 21 | <%= render "task_fields", :f => task_fields %> 22 | <% end %> 23 | 24 | 27 | 28 |
29 | <%= f.submit %> 30 |
31 | <% end %> -------------------------------------------------------------------------------- /test/dummy/app/views/songs/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for @song_form do |f| %> 2 | <% if @song_form.errors.any? %> 3 |
4 |

<%= pluralize(@song_form.errors.count, "error") %> prohibited this song from being saved:

5 | 6 | 11 |
12 | <% end %> 13 | 14 |
15 | <%= f.label :title %>
16 | <%= f.text_field :title %> 17 |
18 | 19 |
20 | <%= f.label :length %>
21 | <%= f.text_field :length %> 22 |
23 | 24 |

Artist Details

25 | 26 | <%= f.fields_for :artist do |artist_fields| %> 27 | <%= render "artist", :artist => artist_fields %> 28 | <% end %> 29 | 30 |
31 | <%= f.submit %> 32 |
33 | <% end %> 34 | -------------------------------------------------------------------------------- /test/forms/poro_form_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require_relative 'poro_form_fixture' 3 | 4 | class PoroFormTest < ActiveSupport::TestCase 5 | include ActiveModel::Lint::Tests 6 | 7 | setup do 8 | @poro = Poro.new 9 | @form = PoroFormFixture.new(@poro) 10 | @model = @form 11 | end 12 | 13 | 14 | test "main form validates itself" do 15 | params = { 16 | name: "Euruco", 17 | city: "Athens" 18 | } 19 | 20 | @form.submit(params) 21 | 22 | assert @form.valid? 23 | 24 | @form.submit({ name: nil, city: nil }) 25 | 26 | assert_not @form.valid? 27 | assert_includes @form.errors[:name], "can't be blank" 28 | assert_includes @form.errors[:city], "can't be blank" 29 | end 30 | 31 | test "save works" do 32 | params = { 33 | name: "Euruco", 34 | city: "Athens" 35 | } 36 | 37 | @form.submit(params) 38 | 39 | assert @form.save 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /test/dummy/app/views/surveys/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for @survey_form do |f| %> 2 | <% if @survey_form.errors.any? %> 3 |
4 |

<%= pluralize(@survey_form.errors.count, "error") %> prohibited this survey from being saved:

5 | 6 | 11 |
12 | <% end %> 13 | 14 |
15 | <%= f.label :name %>
16 | <%= f.text_field :name, :size => 40 %> 17 |
18 | 19 |

Questions

20 | <%= f.fields_for :questions do |question_fields| %> 21 | <%= render "question_fields", :f => question_fields %> 22 | <% end %> 23 | 24 | 27 | 28 |
29 | <%= f.submit %> 30 |
31 | <% end %> 32 | -------------------------------------------------------------------------------- /test/dummy/app/views/conferences/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for @conference_form do |f| %> 2 | <% if @conference_form.errors.any? %> 3 |
4 |

<%= pluralize(@conference_form.errors.count, "error") %> prohibited this conference from being saved:

5 | 6 | 11 |
12 | <% end %> 13 | 14 |

Conference Details

15 |
16 | <%= f.label :name, "Conference Name" %>
17 | <%= f.text_field :name %> 18 |
19 |
20 | <%= f.label :city %>
21 | <%= f.text_field :city %> 22 |
23 | 24 |

Speaker Details

25 | <%= f.fields_for :speaker do |speaker_fields| %> 26 | <%= render "speaker", :f => speaker_fields %> 27 | <% end %> 28 | 29 |
30 | <%= f.submit %> 31 |
32 | <% end %> 33 | -------------------------------------------------------------------------------- /test/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | Bundler.require(*Rails.groups) 6 | require "action_form" 7 | 8 | module Dummy 9 | class Application < Rails::Application 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 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 15 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 16 | # config.time_zone = 'Central Time (US & Canada)' 17 | 18 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 19 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 20 | # config.i18n.default_locale = :de 21 | config.autoload_paths += %W(#{Rails.root}/lib) 22 | end 23 | end 24 | 25 | -------------------------------------------------------------------------------- /test/dummy/config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: 4f652b992e258c03478b463380b23bca70cb9758a3b26c6dd3f5c31a46fd042548ff0176cc6ad3a5e1077c675c6f8bb305a14121ca5c68568318461507d2c94b 15 | 16 | test: 17 | secret_key_base: 8302ca4ce082fa7e37dc5f8c5915609ee6e7c451fb8451aa2c87258f1db6d68759a2cbf50f358bf26847b57525b2caec1d916a7c51ed710b314067b44f25f864 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014 YOURNAME 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 | -------------------------------------------------------------------------------- /test/generators/form_generator_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'rails/generators/test_case' 3 | require 'rails/generators/form/form_generator' 4 | 5 | class FormGeneratorTest < Rails::Generators::TestCase 6 | tests Rails::Generators::FormGenerator 7 | 8 | destination File.expand_path("../../tmp", File.dirname(__FILE__)) 9 | setup :prepare_destination 10 | 11 | def test_help 12 | content = run_generator ["--help"] 13 | assert_match(/creates an action form file/, content) 14 | end 15 | 16 | def test_form_is_created 17 | run_generator ["inquiry"] 18 | assert_file "app/forms/inquiry_form.rb", /class InquiryForm < ActionForm::Base/ 19 | end 20 | 21 | def test_form_with_attributes 22 | run_generator ["feedback", "name", "email", "phone"] 23 | assert_file "app/forms/feedback_form.rb", /class FeedbackForm < ActionForm::Base/ 24 | assert_file "app/forms/feedback_form.rb", /attributes :name, :email, :phone/ 25 | end 26 | 27 | def test_namespaced_forms 28 | run_generator ["admin/feedback"] 29 | assert_file "app/forms/admin/feedback_form.rb", /class Admin::FeedbackForm < ActionForm::Base/ 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/dummy/app/views/projects/_task_fields.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 9 | 10 | 15 | 16 | 19 | 20 | 21 | 22 |
5 | <%= f.input :name, :wrapper => 'inline', :label => false, :hint => 'name the task' do %> 6 | <%= f.input_field :name, :hint => 'type the name' %> 7 | <% end %> 8 | 11 | <%= f.input :description, :wrapper => 'inline', :label => false, :hint => 'describe the task' do %> 12 | <%= f.input_field :description, :hint => 'type the name' %> 13 | <% end %> 14 | 17 | <%= f.input :done, :as => :boolean %> 18 | <%= link_to_remove_association "remove task", f, :confirm => 'do you really want to do this?' %>
23 | 24 |
25 | <%= f.simple_fields_for :sub_tasks, :wrapper => 'inline' do |sub_task| %> 26 | <%= render "sub_task_fields", :f => sub_task %> 27 | <% end %> 28 | 31 |
32 |
-------------------------------------------------------------------------------- /test/dummy/app/views/users/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for @user_form do |f| %> 2 | <% if @user_form.errors.any? %> 3 |
4 |

<%= pluralize(@user_form.errors.count, "error") %> prohibited this user from being saved:

5 | 6 | 11 |
12 | <% end %> 13 | 14 |

User details

15 | 16 |
17 | <%= f.label :name %>
18 | <%= f.text_field :name %> 19 |
20 | 21 |
22 | <%= f.label :age %>
23 | <%= f.number_field :age %> 24 |
25 | 26 |
27 | <%= f.label :gender %>
28 | <%= f.select :gender, User.get_genders_dropdown %> 29 |
30 | 31 |

Email details

32 | <%= f.fields_for :email do |email_fields| %> 33 | <%= render "email", :email => email_fields %> 34 | <% end %> 35 | 36 |

Profile details

37 | <%= f.fields_for :profile do |profile_fields| %> 38 | <% render "profile", :profile => profile_fields %> 39 | <% end %> 40 | 41 |
42 | <%= f.submit %> 43 |
44 | <% end %> 45 | -------------------------------------------------------------------------------- /test/dummy/test/controllers/projects_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ProjectsControllerTest < ActionController::TestCase 4 | fixtures :projects 5 | 6 | setup do 7 | @project = projects(:yard) 8 | end 9 | 10 | test "should get index" do 11 | get :index 12 | assert_response :success 13 | assert_not_nil assigns(:projects) 14 | end 15 | 16 | test "should get new" do 17 | get :new 18 | assert_response :success 19 | end 20 | 21 | test "should create project" do 22 | assert_difference('Project.count') do 23 | post :create, project: { description: @project.description, name: @project.name } 24 | end 25 | 26 | assert_redirected_to project_path(assigns(:project)) 27 | end 28 | 29 | test "should show project" do 30 | get :show, id: @project 31 | assert_response :success 32 | end 33 | 34 | test "should get edit" do 35 | get :edit, id: @project 36 | assert_response :success 37 | end 38 | 39 | test "should update project" do 40 | patch :update, id: @project, project: { description: @project.description, name: @project.name } 41 | assert_redirected_to project_path(assigns(:project)) 42 | end 43 | 44 | test "should destroy project" do 45 | assert_difference('Project.count', -1) do 46 | delete :destroy, id: @project 47 | end 48 | 49 | assert_redirected_to projects_path 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /test/dummy/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = 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 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations. 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | # This option may cause significant delays in view rendering with a large 27 | # number of complex assets. 28 | config.assets.debug = true 29 | 30 | # Adds additional error checking when serving assets at runtime. 31 | # Checks for improperly declared sprockets dependencies. 32 | # Raises helpful error messages. 33 | config.assets.raise_runtime_errors = true 34 | 35 | # Raises error for missing translations 36 | # config.action_view.raise_on_missing_translations = true 37 | end 38 | -------------------------------------------------------------------------------- /test/dummy/app/views/projects/show.html.erb: -------------------------------------------------------------------------------- 1 |

2 | 3 |
4 |

5 | A project has a name, and an owner. It has many tasks, and many people as well. 6 | For sake of the example, we assume people only work on one project. While owners can have many projects. 7 | A project can have many tags. You can link an existing tag, or add a new tag and link to that. 8 |

9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
Name<%= @project.name %>
Owned by<%= (@project.owner ? @project.owner.try(:name) : 'no owner yet') %>
21 | 22 |

Tasks

23 | 24 |

25 | List all the tasks here 26 |

27 | 28 | 29 | <% @project.tasks.each do |task| %> 30 | 31 | 32 | 33 | 34 | 35 | <% end %> 36 |
<%= task.name %><%= task.description %><%= task.done %>
37 | 38 |

Contributors

39 | 40 | 41 | <% @project.contributors.each do |contributor| %> 42 | 43 | 44 | 45 | 46 | 47 | <% end %> 48 |
<%= contributor.name %><%= contributor.role %><%= contributor.description %>
49 | 50 |

Tags

51 | 52 | 53 | <% @project.tags.each do |tag| %> 54 | 55 | 56 | 57 | <% end %> 58 |
<%= tag.name %>
59 | 60 | <%= link_to 'Edit', edit_project_path(@project) %> 61 | <%= '|' %> 62 | <%= link_to 'Back', projects_path %> -------------------------------------------------------------------------------- /lib/action_form/form_helpers.rb: -------------------------------------------------------------------------------- 1 | module ActionForm 2 | module FormHelpers 3 | ATTRIBUTES_KEY_REGEXP = /^(.+)_attributes$/ 4 | 5 | def submit(params) 6 | params.each do |key, value| 7 | if nested_params?(value) 8 | fill_association_with_attributes(key, value) 9 | else 10 | send("#{key}=", value) 11 | end 12 | end 13 | end 14 | 15 | def valid? 16 | super 17 | model.valid? 18 | 19 | collect_errors_from(model) 20 | aggregate_form_errors 21 | 22 | errors.empty? 23 | end 24 | 25 | def nested_params?(value) 26 | value.is_a?(Hash) 27 | end 28 | 29 | def find_association_name_in(key) 30 | ATTRIBUTES_KEY_REGEXP.match(key)[1] 31 | end 32 | 33 | def fill_association_with_attributes(association, attributes) 34 | assoc_name = find_association_name_in(association).to_sym 35 | form = find_form_by_assoc_name(assoc_name) 36 | 37 | form.submit(attributes) 38 | end 39 | 40 | def find_form_by_assoc_name(assoc_name) 41 | forms.select { |form| form.represents?(assoc_name) }.first 42 | end 43 | 44 | def aggregate_form_errors 45 | forms.each do |form| 46 | form.valid? 47 | collect_errors_from(form) 48 | end 49 | end 50 | 51 | def collect_errors_from(validatable_object) 52 | validatable_object.errors.each do |attribute, error| 53 | key = if validatable_object.respond_to?(:association_name) 54 | "#{validatable_object.association_name}.#{attribute}" 55 | else 56 | attribute 57 | end 58 | 59 | errors.add(key, error) 60 | end 61 | end 62 | end 63 | end -------------------------------------------------------------------------------- /test/dummy/app/assets/javascripts/projects.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | $("#owner a.add_fields"). 3 | data("association-insertion-position", 'before'). 4 | data("association-insertion-node", 'this'); 5 | 6 | $('#owner').bind('after-insert', 7 | function() { 8 | $("#owner_from_list").hide(); 9 | $("#owner a.add_fields").hide(); 10 | }); 11 | $('#owner').bind("after-remove", 12 | function() { 13 | $("#owner_from_list").show(); 14 | $("#owner a.add_fields").show(); 15 | }); 16 | 17 | $("#tags a.add_fields"). 18 | data("association-insertion-position", 'before'). 19 | data("association-insertion-node", 'this'); 20 | 21 | $('#tags').bind('after-insert', 22 | function(e, tag) { 23 | $(".project-tag-fields a.add_fields"). 24 | data("association-insertion-position", 'before'). 25 | data("association-insertion-node", 'this'); 26 | $('.project-tag-fields').bind('after-insert', 27 | function() { 28 | $(this).children("#tag_from_list").remove(); 29 | $(this).children("a.add_fields").hide(); 30 | }); 31 | }); 32 | 33 | $('#tasks').bind('before-insert', function(e,task_to_be_added) { 34 | task_to_be_added.fadeIn('slow'); 35 | }); 36 | 37 | $('#tasks').bind('after-insert', function(e, added_task) { 38 | //added_task.css("background","red"); 39 | }); 40 | 41 | $('#tasks').bind('before-remove', function(e, task) { 42 | $(this).data('remove-timeout', 1000); 43 | task.fadeOut('slow'); 44 | }) 45 | 46 | $('body').tabs(); 47 | }); -------------------------------------------------------------------------------- /test/dummy/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /test/dummy/app/views/projects/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= simple_form_for @project_form, wrapper: 'inline' do |f| %> 2 | <%= f.input :name, :hint => 'The title of your project' %> 3 | 4 |

Tasks (:has_many --nested)

5 |
6 | <%= f.simple_fields_for :tasks, wrapper: 'inline' do |task| %> 7 | <%= render 'task_fields', :f => task %> 8 | <% end %> 9 | 12 |
13 | 14 |

People (:has_many)

15 |
16 | <%= f.simple_fields_for :contributors, wrapper: 'inline' do |contributor| %> 17 | <%= render 'contributor_fields', :f => contributor %> 18 | <% end %> 19 | 22 |
23 | 24 |

Owner (:belongs_to)

25 |
26 |
27 | <%= f.association :owner, :collection => Person.order(:name), :prompt => 'Choose an existing owner' %> 28 |
29 | <%= link_to_add_association 'add a new person as owner', f, :owner, :render_options => { :wrapper => 'inline' } %> 30 |
31 | 32 |

Tags (:has_many :through)

33 |
34 | <%= f.simple_fields_for :project_tags, wrapper: 'inline' do |project_tag| %> 35 | <%= render 'project_tag_fields', :f => project_tag %> 36 | <% end %> 37 | <%= link_to_add_association 'add a tag', f, :project_tags, :render_options => { :wrapper => 'inline' } %> 38 |
39 | 40 |
41 | <%= f.button :submit, :disable_with => 'Please wait ...' %> 42 |
43 | <% end %> 44 | -------------------------------------------------------------------------------- /test/dummy/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static asset server for tests with Cache-Control for performance. 16 | config.serve_static_assets = true 17 | config.static_cache_control = 'public, max-age=3600' 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Print deprecation notices to the stderr. 35 | config.active_support.deprecation = :stderr 36 | 37 | # Raises error for missing translations 38 | # config.action_view.raise_on_missing_translations = true 39 | end 40 | -------------------------------------------------------------------------------- /test/dummy/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

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

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /test/dummy/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

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

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /test/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resources :assignments 3 | 4 | resources :projects 5 | 6 | resources :surveys 7 | 8 | resources :conferences 9 | 10 | resources :songs 11 | 12 | resources :users 13 | 14 | # The priority is based upon order of creation: first created -> highest priority. 15 | # See how all your routes lay out with "rake routes". 16 | 17 | # You can have the root of your site routed with "root" 18 | # root 'welcome#index' 19 | 20 | # Example of regular route: 21 | # get 'products/:id' => 'catalog#view' 22 | 23 | # Example of named route that can be invoked with purchase_url(id: product.id) 24 | # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase 25 | 26 | # Example resource route (maps HTTP verbs to controller actions automatically): 27 | # resources :products 28 | 29 | # Example resource route with options: 30 | # resources :products do 31 | # member do 32 | # get 'short' 33 | # post 'toggle' 34 | # end 35 | # 36 | # collection do 37 | # get 'sold' 38 | # end 39 | # end 40 | 41 | # Example resource route with sub-resources: 42 | # resources :products do 43 | # resources :comments, :sales 44 | # resource :seller 45 | # end 46 | 47 | # Example resource route with more complex sub-resources: 48 | # resources :products do 49 | # resources :comments 50 | # resources :sales do 51 | # get 'recent', on: :collection 52 | # end 53 | # end 54 | 55 | # Example resource route with concerns: 56 | # concern :toggleable do 57 | # post 'toggle' 58 | # end 59 | # resources :posts, concerns: :toggleable 60 | # resources :photos, concerns: :toggleable 61 | 62 | # Example resource route within a namespace: 63 | # namespace :admin do 64 | # # Directs /admin/products/* to Admin::ProductsController 65 | # # (app/controllers/admin/products_controller.rb) 66 | # resources :products 67 | # end 68 | end 69 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/assignments_controller.rb: -------------------------------------------------------------------------------- 1 | class AssignmentsController < ApplicationController 2 | before_action :set_assignment, only: [:show, :edit, :update, :destroy] 3 | before_action :create_new_form, only: [:new, :create] 4 | before_action :create_edit_form, only: [:edit, :update] 5 | 6 | 7 | def index 8 | @assignments = Assignment.all 9 | end 10 | 11 | def show 12 | end 13 | 14 | def new 15 | end 16 | 17 | def edit 18 | end 19 | 20 | def create 21 | @assignment_form.submit(assignment_params) 22 | 23 | respond_to do |format| 24 | if @assignment_form.save 25 | format.html { redirect_to @assignment_form, notice: "Assignment: #{@assignment_form.name} was successfully created." } 26 | else 27 | format.html { render :new } 28 | end 29 | end 30 | end 31 | 32 | def update 33 | @assignment_form.submit(assignment_params) 34 | 35 | respond_to do |format| 36 | if @assignment_form.save 37 | format.html { redirect_to @assignment_form, notice: "Assignment: #{@assignment_form.name} was successfully updated." } 38 | else 39 | format.html { render :edit } 40 | end 41 | end 42 | end 43 | 44 | def destroy 45 | name = @assignment.name 46 | 47 | @assignment.destroy 48 | respond_to do |format| 49 | format.html { redirect_to assignments_url, notice: "Assignment: #{name} was successfully destroyed." } 50 | format.json { head :no_content } 51 | end 52 | end 53 | 54 | private 55 | # Use callbacks to share common setup or constraints between actions. 56 | def set_assignment 57 | @assignment = Assignment.find(params[:id]) 58 | end 59 | 60 | def create_new_form 61 | assignment = Assignment.new 62 | @assignment_form = AssignmentForm.new(assignment) 63 | end 64 | 65 | def create_edit_form 66 | @assignment_form = AssignmentForm.new(@assignment) 67 | end 68 | 69 | # Never trust parameters from the scary internet, only allow the white list through. 70 | def assignment_params 71 | params.require(:assignment).permit(:name, tasks_attributes: [:id, :name, :_destroy]) 72 | end 73 | end -------------------------------------------------------------------------------- /app/assets/javascripts/action_form.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 3 | var createNewResourceID = function() { 4 | return new Date().getTime(); 5 | } 6 | 7 | $(document).on('click', '.add_fields', function(e) { 8 | e.preventDefault(); 9 | 10 | var $link = $(this); 11 | var assoc = $link.data('association'); 12 | var content = $link.data('association-insertion-template'); 13 | var insertionMethod = $link.data('association-insertion-method') || $link.data('association-insertion-position') || 'before'; 14 | var insertionNode = $link.data('association-insertion-node'); 15 | var insertionTraversal = $link.data('association-insertion-traversal'); 16 | var newId = createNewResourceID(); 17 | var regex = new RegExp("new_" + assoc, "g"); 18 | var newContent = content.replace(regex, newId); 19 | 20 | if (insertionNode){ 21 | if (insertionTraversal){ 22 | insertionNode = $link[insertionTraversal](insertionNode); 23 | } else { 24 | insertionNode = insertionNode == "this" ? $link : $(insertionNode); 25 | } 26 | } else { 27 | insertionNode = $link.parent(); 28 | } 29 | 30 | var contentNode = $(newContent); 31 | insertionNode.trigger('before-insert', [contentNode]); 32 | 33 | var addedContent = insertionNode[insertionMethod](contentNode); 34 | 35 | insertionNode.trigger('after-insert', [contentNode]); 36 | }); 37 | 38 | $(document).on('click', '.remove_fields.dynamic, .remove_fields.existing', function(e) { 39 | e.preventDefault(); 40 | 41 | var $link = $(this); 42 | var wrapperClass = $link.data('wrapper-class') || 'nested-fields'; 43 | var nodeToDelete = $link.closest('.' + wrapperClass); 44 | var triggerNode = nodeToDelete.parent(); 45 | 46 | triggerNode.trigger('before-remove', [nodeToDelete]); 47 | 48 | var timeout = triggerNode.data('remove-timeout') || 0; 49 | 50 | setTimeout(function() { 51 | if ($link.hasClass('dynamic')) { 52 | nodeToDelete.remove(); 53 | } else { 54 | $link.prev("input[type=hidden]").val("1"); 55 | nodeToDelete.hide(); 56 | } 57 | triggerNode.trigger('after-remove', [nodeToDelete]); 58 | }, timeout); 59 | }); 60 | 61 | })(jQuery); 62 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | before_action :set_user, only: [:show, :edit, :update, :destroy] 3 | before_action :create_new_form, only: [:new, :create] 4 | before_action :create_edit_form, only: [:edit, :update] 5 | 6 | # GET /users 7 | # GET /users.json 8 | def index 9 | @users = User.all 10 | end 11 | 12 | # GET /users/1 13 | # GET /users/1.json 14 | def show 15 | end 16 | 17 | # GET /users/new 18 | def new 19 | end 20 | 21 | # GET /users/1/edit 22 | def edit 23 | end 24 | 25 | # POST /users 26 | # POST /users.json 27 | def create 28 | @user_form.submit(user_params) 29 | 30 | respond_to do |format| 31 | if @user_form.save 32 | format.html { redirect_to @user_form, notice: "User: #{@user_form.name} was successfully created." } 33 | else 34 | format.html { render :new } 35 | end 36 | end 37 | end 38 | 39 | # PATCH/PUT /users/1 40 | # PATCH/PUT /users/1.json 41 | def update 42 | @user_form.submit(user_params) 43 | 44 | respond_to do |format| 45 | if @user_form.save 46 | format.html { redirect_to @user_form, notice: "User: #{@user_form.name} was successfully updated." } 47 | else 48 | format.html { render :edit } 49 | end 50 | end 51 | end 52 | 53 | # DELETE /users/1 54 | # DELETE /users/1.json 55 | def destroy 56 | name = @user.name 57 | @user.destroy 58 | respond_to do |format| 59 | format.html { redirect_to users_url, notice: "User: #{name} was successfully destroyed." } 60 | end 61 | end 62 | 63 | private 64 | # Use callbacks to share common setup or constraints between actions. 65 | def set_user 66 | @user = User.find(params[:id]) 67 | end 68 | 69 | def create_new_form 70 | user = User.new 71 | @user_form = UserForm.new(user) 72 | end 73 | 74 | def create_edit_form 75 | @user_form = UserForm.new(@user) 76 | end 77 | 78 | # Never trust parameters from the scary internet, only allow the white list through. 79 | def user_params 80 | params.require(:user).permit(:name, :age, :gender, email_attributes: [:id, :address], 81 | profile_attributes: [:id, :twitter_name, :github_name]) 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/songs_controller.rb: -------------------------------------------------------------------------------- 1 | class SongsController < ApplicationController 2 | before_action :set_song, only: [:show, :edit, :update, :destroy] 3 | before_action :create_new_form, only: [:new, :create] 4 | before_action :create_edit_form, only: [:edit, :update] 5 | 6 | # GET /songs 7 | # GET /songs.json 8 | def index 9 | @songs = Song.all 10 | end 11 | 12 | # GET /songs/1 13 | # GET /songs/1.json 14 | def show 15 | end 16 | 17 | # GET /songs/new 18 | def new 19 | end 20 | 21 | # GET /songs/1/edit 22 | def edit 23 | end 24 | 25 | # POST /songs 26 | # POST /songs.json 27 | def create 28 | @song_form.submit(song_params) 29 | 30 | respond_to do |format| 31 | if @song_form.save 32 | format.html { redirect_to @song_form, notice: "Song: #{@song_form.title} was successfully created." } 33 | else 34 | format.html { render :new } 35 | end 36 | end 37 | end 38 | 39 | # PATCH/PUT /songs/1 40 | # PATCH/PUT /songs/1.json 41 | def update 42 | @song_form.submit(song_params) 43 | 44 | respond_to do |format| 45 | if @song_form.save 46 | format.html { redirect_to @song_form, notice: "Song: #{@song_form.title} was successfully updated." } 47 | else 48 | format.html { render :edit } 49 | end 50 | end 51 | end 52 | 53 | # DELETE /songs/1 54 | # DELETE /songs/1.json 55 | def destroy 56 | title = @song.title 57 | @song.destroy 58 | 59 | respond_to do |format| 60 | format.html { redirect_to songs_url, notice: "Song: #{title} was successfully destroyed." } 61 | format.json { head :no_content } 62 | end 63 | end 64 | 65 | private 66 | # Use callbacks to share common setup or constraints between actions. 67 | def set_song 68 | @song = Song.find(params[:id]) 69 | end 70 | 71 | def create_new_form 72 | song = Song.new 73 | @song_form = SongForm.new(song) 74 | end 75 | 76 | def create_edit_form 77 | @song_form = SongForm.new(@song) 78 | end 79 | 80 | # Never trust parameters from the scary internet, only allow the white list through. 81 | def song_params 82 | params.require(:song).permit(:title, :length, artist_attributes: 83 | [:name, producer_attributes: [ :name, :studio ] ] ) 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /lib/action_form/view_helpers.rb: -------------------------------------------------------------------------------- 1 | module ActionForm 2 | module ViewHelpers 3 | 4 | def link_to_remove_association(name, f, html_options={}) 5 | classes = [] 6 | classes << "remove_fields" 7 | 8 | is_existing = f.object.persisted? 9 | classes << (is_existing ? 'existing' : 'dynamic') 10 | 11 | wrapper_class = html_options.delete(:wrapper_class) 12 | html_options[:class] = [html_options[:class], classes.join(' ')].compact.join(' ') 13 | html_options[:'data-wrapper-class'] = wrapper_class if wrapper_class.present? 14 | 15 | if is_existing 16 | f.hidden_field(:_destroy) + link_to(name, '#', html_options) 17 | else 18 | link_to(name, '#', html_options) 19 | end 20 | end 21 | 22 | def render_association(association, f, new_object, render_options={}, custom_partial=nil) 23 | partial = get_partial_path(custom_partial, association) 24 | 25 | if f.respond_to?(:semantic_fields_for) 26 | method_name = :semantic_fields_for 27 | elsif f.respond_to?(:simple_fields_for) 28 | method_name = :simple_fields_for 29 | else 30 | method_name = :fields_for 31 | end 32 | 33 | f.send(method_name, association, new_object, {:child_index => "new_#{association}"}.merge(render_options)) do |builder| 34 | render(partial: partial, locals: {:f => builder}) 35 | end 36 | end 37 | 38 | def link_to_add_association(name, f, association, html_options={}) 39 | render_options = html_options.delete(:render_options) 40 | render_options ||= {} 41 | override_partial = html_options.delete(:partial) 42 | 43 | html_options[:class] = [html_options[:class], "add_fields"].compact.join(' ') 44 | html_options[:'data-association'] = association.to_s 45 | 46 | new_object = create_object(f, association) 47 | 48 | html_options[:'data-association-insertion-template'] = CGI.escapeHTML(render_association(association, f, new_object, render_options, override_partial).to_str).html_safe 49 | 50 | link_to(name, '#', html_options) 51 | end 52 | 53 | def create_object(f, association) 54 | f.object.get_model(association) 55 | end 56 | 57 | def get_partial_path(partial, association) 58 | partial ? partial : association.to_s.singularize + "_fields" 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/surveys_controller.rb: -------------------------------------------------------------------------------- 1 | class SurveysController < ApplicationController 2 | before_action :set_survey, only: [:show, :edit, :update, :destroy] 3 | before_action :create_new_form, only: [:new, :create] 4 | before_action :create_edit_form, only: [:edit, :update] 5 | 6 | # GET /surveys 7 | # GET /surveys.json 8 | def index 9 | @surveys = Survey.all 10 | end 11 | 12 | # GET /surveys/1 13 | # GET /surveys/1.json 14 | def show 15 | end 16 | 17 | # GET /surveys/new 18 | def new 19 | end 20 | 21 | # GET /surveys/1/edit 22 | def edit 23 | end 24 | 25 | # POST /surveys 26 | # POST /surveys.json 27 | def create 28 | @survey_form.submit(survey_params) 29 | 30 | respond_to do |format| 31 | if @survey_form.save 32 | format.html { redirect_to @survey_form, notice: "Survey: #{@survey_form.name} was successfully created." } 33 | else 34 | format.html { render :new } 35 | end 36 | end 37 | end 38 | 39 | # PATCH/PUT /surveys/1 40 | # PATCH/PUT /surveys/1.json 41 | def update 42 | @survey_form.submit(survey_params) 43 | 44 | respond_to do |format| 45 | if @survey_form.save 46 | format.html { redirect_to @survey_form, notice: "Survey: #{@survey_form.name} was successfully updated." } 47 | else 48 | format.html { render :edit } 49 | end 50 | end 51 | end 52 | 53 | # DELETE /surveys/1 54 | # DELETE /surveys/1.json 55 | def destroy 56 | name = @survey.name 57 | 58 | @survey.destroy 59 | respond_to do |format| 60 | format.html { redirect_to surveys_url, notice: "Survey: #{name} was successfully destroyed." } 61 | end 62 | end 63 | 64 | private 65 | # Use callbacks to share common setup or constraints between actions. 66 | def set_survey 67 | @survey = Survey.find(params[:id]) 68 | end 69 | 70 | def create_new_form 71 | survey = Survey.new 72 | @survey_form = SurveyForm.new(survey) 73 | end 74 | 75 | def create_edit_form 76 | @survey_form = SurveyForm.new(@survey) 77 | end 78 | 79 | # Never trust parameters from the scary internet, only allow the white list through. 80 | def survey_params 81 | params.require(:survey).permit(:name, questions_attributes: [:id, :_destroy, :content, 82 | answers_attributes: [:id, :_destroy, :content]]) 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | actionform (0.0.1) 5 | rails (~> 4.1) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | actionmailer (4.1.8) 11 | actionpack (= 4.1.8) 12 | actionview (= 4.1.8) 13 | mail (~> 2.5, >= 2.5.4) 14 | actionpack (4.1.8) 15 | actionview (= 4.1.8) 16 | activesupport (= 4.1.8) 17 | rack (~> 1.5.2) 18 | rack-test (~> 0.6.2) 19 | actionview (4.1.8) 20 | activesupport (= 4.1.8) 21 | builder (~> 3.1) 22 | erubis (~> 2.7.0) 23 | activemodel (4.1.8) 24 | activesupport (= 4.1.8) 25 | builder (~> 3.1) 26 | activerecord (4.1.8) 27 | activemodel (= 4.1.8) 28 | activesupport (= 4.1.8) 29 | arel (~> 5.0.0) 30 | activesupport (4.1.8) 31 | i18n (~> 0.6, >= 0.6.9) 32 | json (~> 1.7, >= 1.7.7) 33 | minitest (~> 5.1) 34 | thread_safe (~> 0.1) 35 | tzinfo (~> 1.1) 36 | arel (5.0.1.20140414130214) 37 | builder (3.2.2) 38 | erubis (2.7.0) 39 | hike (1.2.3) 40 | i18n (0.6.11) 41 | jquery-rails (3.1.1) 42 | railties (>= 3.0, < 5.0) 43 | thor (>= 0.14, < 2.0) 44 | json (1.8.1) 45 | mail (2.6.3) 46 | mime-types (>= 1.16, < 3) 47 | mime-types (2.4.3) 48 | minitest (5.5.0) 49 | multi_json (1.10.1) 50 | rack (1.5.2) 51 | rack-test (0.6.2) 52 | rack (>= 1.0) 53 | rails (4.1.8) 54 | actionmailer (= 4.1.8) 55 | actionpack (= 4.1.8) 56 | actionview (= 4.1.8) 57 | activemodel (= 4.1.8) 58 | activerecord (= 4.1.8) 59 | activesupport (= 4.1.8) 60 | bundler (>= 1.3.0, < 2.0) 61 | railties (= 4.1.8) 62 | sprockets-rails (~> 2.0) 63 | railties (4.1.8) 64 | actionpack (= 4.1.8) 65 | activesupport (= 4.1.8) 66 | rake (>= 0.8.7) 67 | thor (>= 0.18.1, < 2.0) 68 | rake (10.3.2) 69 | simple_form (3.0.2) 70 | actionpack (~> 4.0) 71 | activemodel (~> 4.0) 72 | sprockets (2.12.3) 73 | hike (~> 1.2) 74 | multi_json (~> 1.0) 75 | rack (~> 1.0) 76 | tilt (~> 1.1, != 1.3.0) 77 | sprockets-rails (2.2.4) 78 | actionpack (>= 3.0) 79 | activesupport (>= 3.0) 80 | sprockets (>= 2.8, < 4.0) 81 | sqlite3 (1.3.9) 82 | thor (0.19.1) 83 | thread_safe (0.3.4) 84 | tilt (1.4.1) 85 | tzinfo (1.2.2) 86 | thread_safe (~> 0.1) 87 | 88 | PLATFORMS 89 | ruby 90 | 91 | DEPENDENCIES 92 | actionform! 93 | jquery-rails 94 | rake (~> 10.3.2) 95 | simple_form 96 | sqlite3 97 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/conferences_controller.rb: -------------------------------------------------------------------------------- 1 | class ConferencesController < ApplicationController 2 | before_action :set_conference, only: [:show, :edit, :update, :destroy] 3 | before_action :create_new_form, only: [:new, :create] 4 | before_action :create_edit_form, only: [:edit, :update] 5 | 6 | # GET /conferences 7 | # GET /conferences.json 8 | def index 9 | @conferences = Conference.all 10 | end 11 | 12 | # GET /conferences/1 13 | # GET /conferences/1.json 14 | def show 15 | end 16 | 17 | # GET /conferences/new 18 | def new 19 | end 20 | 21 | # GET /conferences/1/edit 22 | def edit 23 | end 24 | 25 | # POST /conferences 26 | # POST /conferences.json 27 | def create 28 | @conference_form.submit(conference_params) 29 | 30 | respond_to do |format| 31 | if @conference_form.save 32 | format.html { redirect_to @conference_form, notice: "Conference: #{@conference_form.name} was successfully created." } 33 | else 34 | format.html { render :new } 35 | end 36 | end 37 | end 38 | 39 | # PATCH/PUT /conferences/1 40 | # PATCH/PUT /conferences/1.json 41 | def update 42 | @conference_form.submit(conference_params) 43 | 44 | respond_to do |format| 45 | if @conference_form.save 46 | format.html { redirect_to @conference_form, notice: "Conference: #{@conference_form.name} was successfully updated." } 47 | else 48 | format.html { render :edit } 49 | end 50 | end 51 | end 52 | 53 | # DELETE /conferences/1 54 | # DELETE /conferences/1.json 55 | def destroy 56 | name = @conference.name 57 | 58 | @conference.destroy 59 | respond_to do |format| 60 | format.html { redirect_to conferences_url, notice: "Conference: #{name} was successfully destroyed." } 61 | end 62 | end 63 | 64 | private 65 | # Use callbacks to share common setup or constraints between actions. 66 | def set_conference 67 | @conference = Conference.find(params[:id]) 68 | end 69 | 70 | def create_new_form 71 | conference = Conference.new 72 | @conference_form = ConferenceForm.new(conference) 73 | end 74 | 75 | def create_edit_form 76 | @conference_form = ConferenceForm.new(@conference) 77 | end 78 | 79 | # Never trust parameters from the scary internet, only allow the white list through. 80 | def conference_params 81 | params.require(:conference).permit(:name, :city, speaker_attributes: [:id, :name, :occupation, 82 | presentations_attributes: [:id, :_destroy, :topic, :duration]]) 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/projects_controller.rb: -------------------------------------------------------------------------------- 1 | class ProjectsController < ApplicationController 2 | before_action :set_project, only: [:show, :edit, :update, :destroy] 3 | 4 | # GET /projects 5 | # GET /projects.json 6 | def index 7 | @projects = Project.all 8 | end 9 | 10 | # GET /projects/1 11 | # GET /projects/1.json 12 | def show 13 | end 14 | 15 | # GET /projects/new 16 | def new 17 | @project = Project.new 18 | @project_form = ProjectForm.new(@project) 19 | end 20 | 21 | # GET /projects/1/edit 22 | def edit 23 | @project_form = ProjectForm.new(@project) 24 | end 25 | 26 | # POST /projects 27 | # POST /projects.json 28 | def create 29 | @project = Project.new 30 | @project_form = ProjectForm.new(@project) 31 | 32 | @project_form.submit(project_params) 33 | 34 | respond_to do |format| 35 | if @project_form.save 36 | format.html { redirect_to @project_form, notice: 'Project was successfully created.' } 37 | else 38 | format.html { render :new } 39 | end 40 | end 41 | end 42 | 43 | # PATCH/PUT /projects/1 44 | # PATCH/PUT /projects/1.json 45 | def update 46 | @project_form = ProjectForm.new(@project) 47 | 48 | @project_form.submit(project_params) 49 | 50 | respond_to do |format| 51 | if @project_form.save 52 | format.html { redirect_to @project_form, notice: 'Project was successfully updated.' } 53 | else 54 | format.html { render :edit } 55 | end 56 | end 57 | end 58 | 59 | # DELETE /projects/1 60 | # DELETE /projects/1.json 61 | def destroy 62 | @project.destroy 63 | respond_to do |format| 64 | format.html { redirect_to projects_url, notice: 'Project was successfully destroyed.' } 65 | end 66 | end 67 | 68 | private 69 | # Use callbacks to share common setup or constraints between actions. 70 | def set_project 71 | @project = Project.find(params[:id]) 72 | end 73 | 74 | # Never trust parameters from the scary internet, only allow the white list through. 75 | def project_params 76 | params.require(:project).permit(:name, :owner_id, tasks_attributes: [ :name, :description, :done, :id, :_destroy, 77 | sub_tasks_attributes: [ :name, :description, :done, :id, :_destroy ] ], 78 | owner_attributes: [ :name, :role, :description, :id, :_destroy ], 79 | contributors_attributes: [ :name, :role, :description, :id, :_destroy ], 80 | project_tags_attributes: [ :tag_id, :id, :_destroy, tag_attributes: 81 | [ :name, :id, :_destroy ] ]) 82 | end 83 | 84 | end -------------------------------------------------------------------------------- /lib/action_form/base.rb: -------------------------------------------------------------------------------- 1 | module ActionForm 2 | class Base 3 | include ActiveModel::Model 4 | include FormHelpers 5 | extend ActiveModel::Callbacks 6 | 7 | define_model_callbacks :save, only: [:after] 8 | after_save :update_form_models 9 | 10 | delegate :persisted?, :to_model, :to_key, :to_param, :to_partial_path, to: :model 11 | attr_reader :model, :forms 12 | 13 | def initialize(model) 14 | @model = model 15 | @forms = [] 16 | populate_forms 17 | end 18 | 19 | def get_model(assoc_name) 20 | form = find_form_by_assoc_name(assoc_name) 21 | form.get_model(assoc_name) 22 | end 23 | 24 | def save 25 | if valid? 26 | run_callbacks :save do 27 | ActiveRecord::Base.transaction do 28 | model.save 29 | end 30 | end 31 | else 32 | false 33 | end 34 | end 35 | 36 | class << self 37 | attr_writer :main_class, :main_model 38 | delegate :reflect_on_association, to: :main_class 39 | 40 | def attributes(*names) 41 | options = names.pop if names.last.is_a?(Hash) 42 | 43 | if options && options[:required] 44 | validates_presence_of(*names) 45 | end 46 | 47 | names.each do |attribute| 48 | delegate attribute, "#{attribute}=", to: :model 49 | end 50 | end 51 | 52 | def main_class 53 | @main_class ||= main_model.to_s.camelize.constantize 54 | end 55 | 56 | def main_model 57 | @main_model ||= name.sub(/Form$/, '').singularize 58 | end 59 | 60 | alias_method :attribute, :attributes 61 | 62 | def association(name, options={}, &block) 63 | forms << FormDefinition.new(name, block, options) 64 | macro = main_class.reflect_on_association(name).macro 65 | 66 | case macro 67 | when :has_one, :belongs_to 68 | class_eval "def #{name}; @#{name}; end" 69 | when :has_many 70 | class_eval "def #{name}; @#{name}.models; end" 71 | end 72 | 73 | class_eval "def #{name}_attributes=; end" 74 | end 75 | 76 | def forms 77 | @forms ||= [] 78 | end 79 | end 80 | 81 | private 82 | 83 | def update_form_models 84 | forms.each do |form| 85 | form.update_models 86 | end 87 | end 88 | 89 | def populate_forms 90 | self.class.forms.each do |definition| 91 | definition.parent = model 92 | nested_form = definition.to_form 93 | forms << nested_form 94 | name = definition.assoc_name 95 | instance_variable_set("@#{name}", nested_form) 96 | end 97 | end 98 | 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /test/dummy/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. 20 | # config.action_dispatch.rack_cache = true 21 | 22 | # Disable Rails's static asset server (Apache or nginx will already do this). 23 | config.serve_static_assets = false 24 | 25 | # Compress JavaScripts and CSS. 26 | config.assets.js_compressor = :uglifier 27 | # config.assets.css_compressor = :sass 28 | 29 | # Do not fallback to assets pipeline if a precompiled asset is missed. 30 | config.assets.compile = false 31 | 32 | # Generate digests for assets URLs. 33 | config.assets.digest = true 34 | 35 | # `config.assets.precompile` has moved to config/initializers/assets.rb 36 | 37 | # Specifies the header that your server uses for sending files. 38 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 39 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 40 | 41 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 42 | # config.force_ssl = true 43 | 44 | # Set to :debug to see everything in the log. 45 | config.log_level = :info 46 | 47 | # Prepend all log lines with the following tags. 48 | # config.log_tags = [ :subdomain, :uuid ] 49 | 50 | # Use a different logger for distributed setups. 51 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 52 | 53 | # Use a different cache store in production. 54 | # config.cache_store = :mem_cache_store 55 | 56 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 57 | # config.action_controller.asset_host = "http://assets.example.com" 58 | 59 | # Precompile additional assets. 60 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 61 | # config.assets.precompile += %w( search.js ) 62 | 63 | # Ignore bad email addresses and do not raise email delivery errors. 64 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 65 | # config.action_mailer.raise_delivery_errors = false 66 | 67 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 68 | # the I18n.default_locale when a translation cannot be found). 69 | config.i18n.fallbacks = true 70 | 71 | # Send deprecation notices to registered listeners. 72 | config.active_support.deprecation = :notify 73 | 74 | # Disable automatic flushing of the log to improve performance. 75 | # config.autoflush_log = false 76 | 77 | # Use default logging formatter so that PID and timestamp are not suppressed. 78 | config.log_formatter = ::Logger::Formatter.new 79 | 80 | # Do not dump schema after migrations. 81 | config.active_record.dump_schema_after_migration = false 82 | end 83 | -------------------------------------------------------------------------------- /test/dummy/test/controllers/assignments_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AssignmentsControllerTest < ActionController::TestCase 4 | fixtures :assignments, :tasks 5 | 6 | setup do 7 | @assignment = assignments(:yard) 8 | end 9 | 10 | test "should get index" do 11 | get :index 12 | assert_response :success 13 | assert_not_nil assigns(:assignments) 14 | end 15 | 16 | test "should get new" do 17 | get :new 18 | assert_response :success 19 | end 20 | 21 | test "should create assignment" do 22 | assert_difference('Assignment.count') do 23 | post :create, assignment: { 24 | name: "Life", 25 | 26 | tasks_attributes: { 27 | "0" => { name: "Eat" }, 28 | "1" => { name: "Pray" }, 29 | "2" => { name: "Love" }, 30 | } 31 | } 32 | end 33 | 34 | assignment_form = assigns(:assignment_form) 35 | 36 | assert assignment_form.valid? 37 | assert_redirected_to assignment_path(assignment_form) 38 | 39 | assert_equal "Life", assignment_form.name 40 | 41 | assert_equal "Eat", assignment_form.tasks[0].name 42 | assert_equal "Pray", assignment_form.tasks[1].name 43 | assert_equal "Love", assignment_form.tasks[2].name 44 | 45 | assignment_form.tasks.each do |task_form| 46 | assert task_form.persisted? 47 | end 48 | 49 | assert_equal "Assignment: Life was successfully created.", flash[:notice] 50 | end 51 | 52 | test "should not create assignment with invalid params" do 53 | assignment = assignments(:yard) 54 | 55 | assert_difference('Assignment.count', 0) do 56 | post :create, assignment: { 57 | name: assignment.name, 58 | 59 | tasks_attributes: { 60 | "0" => { name: nil }, 61 | "1" => { name: nil }, 62 | "2" => { name: nil }, 63 | } 64 | } 65 | end 66 | 67 | assignment_form = assigns(:assignment_form) 68 | 69 | assert_not assignment_form.valid? 70 | assert_includes assignment_form.errors.messages[:name], "has already been taken" 71 | 72 | assignment_form.tasks.each do |task_form| 73 | assert_includes task_form.errors.messages[:name], "can't be blank" 74 | end 75 | end 76 | 77 | test "should show assignment" do 78 | get :show, id: @assignment 79 | assert_response :success 80 | end 81 | 82 | test "should get edit" do 83 | get :edit, id: @assignment 84 | assert_response :success 85 | end 86 | 87 | test "should update assignment" do 88 | assert_difference('Assignment.count', 0) do 89 | patch :update, id: @assignment, assignment: { 90 | name: "Car service", 91 | 92 | tasks_attributes: { 93 | "0" => { name: "Wash tires", id: tasks(:rake).id }, 94 | "1" => { name: "Clean inside", id: tasks(:paint).id }, 95 | "2" => { name: "Check breaks", id: tasks(:clean).id }, 96 | } 97 | } 98 | end 99 | 100 | assignment_form = assigns(:assignment_form) 101 | 102 | assert_redirected_to assignment_path(assignment_form) 103 | 104 | assert_equal "Car service", assignment_form.name 105 | 106 | assert_equal "Wash tires", assignment_form.tasks[0].name 107 | assert_equal "Clean inside", assignment_form.tasks[1].name 108 | assert_equal "Check breaks", assignment_form.tasks[2].name 109 | 110 | assert_equal "Assignment: Car service was successfully updated.", flash[:notice] 111 | end 112 | 113 | test "should destroy assignment" do 114 | assert_difference('Assignment.count', -1) do 115 | delete :destroy, id: @assignment 116 | end 117 | 118 | assert_redirected_to assignments_path 119 | end 120 | end 121 | -------------------------------------------------------------------------------- /test/dummy/test/controllers/users_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class UsersControllerTest < ActionController::TestCase 4 | fixtures :users, :emails, :profiles 5 | 6 | setup do 7 | @user = users(:peter) 8 | end 9 | 10 | test "should get index" do 11 | get :index 12 | assert_response :success 13 | assert_not_nil assigns(:users) 14 | end 15 | 16 | test "should get new" do 17 | get :new 18 | assert_response :success 19 | end 20 | 21 | test "should create user" do 22 | assert_difference(['User.count', 'Email.count', 'Profile.count']) do 23 | post :create, user: { 24 | age: "23", 25 | gender: "0", 26 | name: "petrakos", 27 | 28 | email_attributes: { 29 | address: "petrakos@gmail.com" 30 | }, 31 | 32 | profile_attributes: { 33 | twitter_name: "t_peter", 34 | github_name: "g_peter" 35 | } 36 | } 37 | end 38 | 39 | user_form = assigns(:user_form) 40 | 41 | assert user_form.valid? 42 | assert_redirected_to user_path(user_form) 43 | 44 | assert_equal "petrakos", user_form.name 45 | assert_equal 23, user_form.age 46 | assert_equal 0, user_form.gender 47 | 48 | assert_equal "petrakos@gmail.com", user_form.email.address 49 | 50 | assert_equal "t_peter", user_form.profile.twitter_name 51 | assert_equal "g_peter", user_form.profile.github_name 52 | 53 | assert_equal "User: #{user_form.name} was successfully created.", flash[:notice] 54 | end 55 | 56 | test "should not create user with invalid params" do 57 | peter = users(:peter) 58 | 59 | assert_difference(['User.count', 'Email.count', 'Profile.count'], 0) do 60 | post :create, user: { 61 | name: peter.name, 62 | age: nil, 63 | gender: "0", 64 | 65 | email_attributes: { 66 | address: peter.email.address 67 | }, 68 | 69 | profile_attributes: { 70 | twitter_name: peter.profile.twitter_name, 71 | github_name: peter.profile.github_name 72 | } 73 | } 74 | end 75 | 76 | user_form = assigns(:user_form) 77 | 78 | assert_not user_form.valid? 79 | 80 | assert_includes user_form.errors.messages[:name], "has already been taken" 81 | assert_includes user_form.errors.messages[:age], "can't be blank" 82 | 83 | assert_includes user_form.email.errors.messages[:address], "has already been taken" 84 | 85 | assert_includes user_form.profile.errors.messages[:twitter_name], "has already been taken" 86 | assert_includes user_form.profile.errors.messages[:github_name], "has already been taken" 87 | end 88 | 89 | test "should show user" do 90 | get :show, id: @user 91 | assert_response :success 92 | end 93 | 94 | test "should get edit" do 95 | get :edit, id: @user 96 | assert_response :success 97 | end 98 | 99 | test "should update user" do 100 | assert_difference(['User.count', 'Email.count', 'Profile.count'], 0) do 101 | patch :update, id: @user, user: { 102 | age: @user.age, 103 | gender: @user.gender, 104 | name: "petrakos", 105 | 106 | email_attributes: { 107 | address: "petrakos@gmail.com" 108 | }, 109 | 110 | profile_attributes: { 111 | twitter_name: "t_peter", 112 | github_name: "g_peter" 113 | } 114 | } 115 | end 116 | 117 | user_form = assigns(:user_form) 118 | 119 | assert_redirected_to user_path(user_form) 120 | 121 | assert_equal "petrakos", user_form.name 122 | 123 | assert_equal "petrakos@gmail.com", user_form.email.address 124 | 125 | assert_equal "t_peter", user_form.profile.twitter_name 126 | assert_equal "g_peter", user_form.profile.github_name 127 | 128 | assert_equal "User: #{user_form.name} was successfully updated.", flash[:notice] 129 | end 130 | 131 | test "should destroy user" do 132 | assert_difference('User.count', -1) do 133 | delete :destroy, id: @user 134 | end 135 | 136 | assert_redirected_to users_path 137 | end 138 | end 139 | -------------------------------------------------------------------------------- /test/dummy/test/controllers/songs_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SongsControllerTest < ActionController::TestCase 4 | fixtures :songs, :artists, :producers 5 | 6 | setup do 7 | @song = songs(:lockdown) 8 | end 9 | 10 | test "should get index" do 11 | get :index 12 | assert_response :success 13 | assert_not_nil assigns(:songs) 14 | end 15 | 16 | test "should get new" do 17 | get :new 18 | assert_response :success 19 | end 20 | 21 | test "should create song" do 22 | assert_difference(['Song.count', 'Artist.count', 'Producer.count']) do 23 | post :create, song: { 24 | title: "Diamonds", 25 | length: "360", 26 | 27 | artist_attributes: { 28 | name: "Karras", 29 | 30 | producer_attributes: { 31 | name: "Phoebos", 32 | studio: "MADog" 33 | } 34 | } 35 | } 36 | end 37 | 38 | song_form = assigns(:song_form) 39 | 40 | assert song_form.valid? 41 | assert_redirected_to song_path(song_form) 42 | 43 | assert_equal "Diamonds", song_form.title 44 | assert_equal "360", song_form.length 45 | 46 | assert_equal "Karras", song_form.artist.name 47 | 48 | assert_equal "Phoebos", song_form.artist.producer.name 49 | assert_equal "MADog", song_form.artist.producer.studio 50 | 51 | assert song_form.artist.persisted? 52 | assert song_form.artist.producer.persisted? 53 | 54 | assert_equal "Song: Diamonds was successfully created.", flash[:notice] 55 | end 56 | 57 | test "should not create song with invalid params" do 58 | assert_difference(['Song.count', 'Artist.count', 'Producer.count'], 0) do 59 | post :create, song: { 60 | title: nil, 61 | length: nil, 62 | 63 | artist_attributes: { 64 | name: nil, 65 | 66 | producer_attributes: { 67 | name: nil, 68 | studio: nil 69 | } 70 | } 71 | } 72 | end 73 | 74 | song_form = assigns(:song_form) 75 | 76 | assert_not song_form.valid? 77 | 78 | assert_includes song_form.errors[:title], "can't be blank" 79 | assert_includes song_form.errors[:length], "can't be blank" 80 | 81 | assert_includes song_form.errors["artist.name"], "can't be blank" 82 | assert_includes song_form.artist.errors[:name], "can't be blank" 83 | 84 | assert_includes song_form.artist.producer.errors[:name], "can't be blank" 85 | assert_includes song_form.artist.producer.errors[:studio], "can't be blank" 86 | 87 | assert_includes song_form.errors["artist.producer.name"], "can't be blank" 88 | assert_includes song_form.errors["artist.producer.studio"], "can't be blank" 89 | end 90 | 91 | test "should show song" do 92 | get :show, id: @song 93 | assert_response :success 94 | end 95 | 96 | test "should get edit" do 97 | get :edit, id: @song 98 | assert_response :success 99 | end 100 | 101 | test "should update song" do 102 | assert_difference(['Song.count', 'Artist.count', 'Producer.count'], 0) do 103 | patch :update, id: @song, song: { 104 | title: "Run this town", 105 | length: "355", 106 | 107 | artist_attributes: { 108 | name: "Rihanna", 109 | 110 | producer_attributes: { 111 | name: "Eminem", 112 | studio: "Marshall" 113 | } 114 | } 115 | } 116 | end 117 | 118 | song_form = assigns(:song_form) 119 | 120 | assert_redirected_to song_path(song_form) 121 | 122 | assert_equal "Run this town", song_form.title 123 | assert_equal "355", song_form.length 124 | 125 | assert_equal "Rihanna", song_form.artist.name 126 | 127 | assert_equal "Eminem", song_form.artist.producer.name 128 | assert_equal "Marshall", song_form.artist.producer.studio 129 | 130 | assert_equal "Song: Run this town was successfully updated.", flash[:notice] 131 | end 132 | 133 | test "should destroy song" do 134 | assert_difference('Song.count', -1) do 135 | delete :destroy, id: @song 136 | end 137 | 138 | assert_redirected_to songs_path 139 | end 140 | end 141 | -------------------------------------------------------------------------------- /lib/action_form/form.rb: -------------------------------------------------------------------------------- 1 | module ActionForm 2 | class Form 3 | include ActiveModel::Validations 4 | include FormHelpers 5 | 6 | delegate :id, :_destroy, :persisted?, to: :model 7 | attr_reader :association_name, :parent, :model, :forms, :proc 8 | 9 | def initialize(assoc_name, parent, proc, model=nil) 10 | @association_name = assoc_name 11 | @parent = parent 12 | @model = assign_model(model) 13 | @forms = [] 14 | @proc = proc 15 | enable_autosave 16 | instance_eval &proc 17 | end 18 | 19 | def class 20 | model.class 21 | end 22 | 23 | def association(name, options={}, &block) 24 | macro = model.class.reflect_on_association(name).macro 25 | form_definition = FormDefinition.new(name, block, options) 26 | form_definition.parent = @model 27 | 28 | case macro 29 | when :has_one, :belongs_to 30 | class_eval "def #{name}; @#{name}; end" 31 | when :has_many 32 | class_eval "def #{name}; @#{name}.models; end" 33 | end 34 | 35 | nested_form = form_definition.to_form 36 | @forms << nested_form 37 | instance_variable_set("@#{name}", nested_form) 38 | 39 | class_eval "def #{name}_attributes=; end" 40 | end 41 | 42 | def attributes(*arguments) 43 | class_eval do 44 | options = arguments.pop if arguments.last.is_a?(Hash) 45 | 46 | if options && options[:required] 47 | validates_presence_of(*arguments) 48 | end 49 | 50 | arguments.each do |attribute| 51 | delegate attribute, "#{attribute}=", to: :model 52 | end 53 | end 54 | end 55 | 56 | alias_method :attribute, :attributes 57 | 58 | def method_missing(method_sym, *arguments, &block) 59 | if method_sym =~ /^validates?$/ 60 | class_eval do 61 | send(method_sym, *arguments, &block) 62 | end 63 | end 64 | end 65 | 66 | def update_models 67 | @model = parent.send("#{association_name}") 68 | end 69 | 70 | REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } } 71 | 72 | def call_reject_if(attributes) 73 | REJECT_ALL_BLANK_PROC.call(attributes) 74 | end 75 | 76 | def params_for_current_scope(attributes) 77 | attributes.dup.reject { |_, v| v.is_a? Hash } 78 | end 79 | 80 | def submit(params) 81 | reflection = association_reflection 82 | 83 | if reflection.macro == :belongs_to 84 | @model = parent.send("build_#{association_name}") unless call_reject_if(params_for_current_scope(params)) 85 | end 86 | 87 | super 88 | end 89 | 90 | def get_model(assoc_name) 91 | if represents?(assoc_name) 92 | Form.new(association_name, parent, proc) 93 | else 94 | form = find_form_by_assoc_name(assoc_name) 95 | form.get_model(assoc_name) 96 | end 97 | end 98 | 99 | def delete 100 | model.mark_for_destruction 101 | end 102 | 103 | def represents?(assoc_name) 104 | association_name.to_s == assoc_name.to_s 105 | end 106 | 107 | private 108 | 109 | def enable_autosave 110 | reflection = association_reflection 111 | reflection.autosave = true 112 | end 113 | 114 | def association_reflection 115 | parent.class.reflect_on_association(association_name) 116 | end 117 | 118 | def build_model 119 | macro = association_reflection.macro 120 | 121 | case macro 122 | when :belongs_to 123 | if parent.send("#{association_name}") 124 | parent.send("#{association_name}") 125 | else 126 | association_reflection.klass.new 127 | end 128 | when :has_one 129 | fetch_or_initialize_model 130 | when :has_many 131 | parent.send(association_name).build 132 | end 133 | end 134 | 135 | def fetch_or_initialize_model 136 | if parent.send("#{association_name}") 137 | parent.send("#{association_name}") 138 | else 139 | parent.send("build_#{association_name}") 140 | end 141 | end 142 | 143 | def assign_model(model) 144 | if model 145 | model 146 | else 147 | build_model 148 | end 149 | end 150 | 151 | end 152 | end 153 | -------------------------------------------------------------------------------- /lib/action_form/form_collection.rb: -------------------------------------------------------------------------------- 1 | module ActionForm 2 | class FormCollection 3 | include ActiveModel::Validations 4 | 5 | attr_reader :association_name, :records, :parent, :proc, :forms 6 | 7 | def initialize(assoc_name, parent, proc, options) 8 | @association_name = assoc_name 9 | @parent = parent 10 | @proc = proc 11 | @records = options[:records] || 1 12 | @forms = [] 13 | assign_forms 14 | end 15 | 16 | def update_models 17 | @forms = [] 18 | fetch_models 19 | end 20 | 21 | def submit(params) 22 | params.each do |key, value| 23 | if parent.persisted? 24 | create_or_update_record(value) 25 | else 26 | create_or_assign_record(key, value) 27 | end 28 | end 29 | end 30 | 31 | def get_model(assoc_name) 32 | Form.new(association_name, parent, proc) 33 | end 34 | 35 | def valid? 36 | aggregate_form_errors 37 | 38 | errors.empty? 39 | end 40 | 41 | def represents?(assoc_name) 42 | association_name.to_s == assoc_name.to_s 43 | end 44 | 45 | def models 46 | forms 47 | end 48 | 49 | def each(&block) 50 | forms.each do |form| 51 | block.call(form) 52 | end 53 | end 54 | 55 | private 56 | 57 | REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } } 58 | 59 | UNASSIGNABLE_KEYS = %w( id _destroy ) 60 | 61 | def call_reject_if(attributes) 62 | REJECT_ALL_BLANK_PROC.call(attributes) 63 | end 64 | 65 | def assign_to_or_mark_for_destruction(form, attributes) 66 | form.submit(attributes.except(*UNASSIGNABLE_KEYS)) 67 | 68 | if has_destroy_flag?(attributes) 69 | form.delete 70 | remove_form(form) 71 | end 72 | end 73 | 74 | def existing_record?(attributes) 75 | attributes[:id] != nil 76 | end 77 | 78 | def update_record(attributes) 79 | id = attributes[:id] 80 | form = find_form_by_model_id(id) 81 | assign_to_or_mark_for_destruction(form, attributes) 82 | end 83 | 84 | def create_record(attributes) 85 | new_form = create_form 86 | new_form.submit(attributes) 87 | end 88 | 89 | def create_or_update_record(attributes) 90 | if existing_record?(attributes) 91 | update_record(attributes) 92 | else 93 | create_record(attributes) 94 | end 95 | end 96 | 97 | def create_or_assign_record(key, attributes) 98 | i = key.to_i 99 | 100 | if dynamic_key?(i) 101 | create_record(attributes) 102 | else 103 | if call_reject_if(attributes) 104 | forms[i].delete 105 | end 106 | forms[i].submit(attributes) 107 | end 108 | end 109 | 110 | def has_destroy_flag?(attributes) 111 | attributes['_destroy'] == "1" 112 | end 113 | 114 | def assign_forms 115 | if parent.persisted? 116 | fetch_models 117 | else 118 | initialize_models 119 | end 120 | end 121 | 122 | def dynamic_key?(i) 123 | i > forms.size 124 | end 125 | 126 | def aggregate_form_errors 127 | forms.each do |form| 128 | form.valid? 129 | collect_errors_from(form) 130 | end 131 | end 132 | 133 | def fetch_models 134 | associated_records = parent.send(association_name) 135 | 136 | associated_records.each do |model| 137 | form = Form.new(association_name, parent, proc, model) 138 | forms << form 139 | end 140 | end 141 | 142 | def initialize_models 143 | records.times do 144 | form = Form.new(association_name, parent, proc) 145 | forms << form 146 | end 147 | end 148 | 149 | def collect_errors_from(model) 150 | model.errors.each do |attribute, error| 151 | errors.add(attribute, error) 152 | end 153 | end 154 | 155 | def check_record_limit!(limit, attributes_collection) 156 | if attributes_collection.size > limit 157 | raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead." 158 | end 159 | end 160 | 161 | def find_form_by_model_id(id) 162 | forms.select { |form| form.id == id.to_i }.first 163 | end 164 | 165 | def remove_form(form) 166 | forms.delete(form) 167 | end 168 | 169 | def create_form 170 | new_form = Form.new(association_name, parent, proc) 171 | forms << new_form 172 | new_form 173 | end 174 | end 175 | 176 | end 177 | -------------------------------------------------------------------------------- /test/forms/single_model_form_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require_relative '../fixtures/user_form_fixture' 3 | 4 | class SingleModelFormTest < ActiveSupport::TestCase 5 | include ActiveModel::Lint::Tests 6 | fixtures :users 7 | 8 | def setup 9 | @user = User.new 10 | @form = UserFormFixture.new(@user) 11 | @model = @form 12 | end 13 | 14 | test "accepts the model it represents" do 15 | assert_equal @user, @form.model 16 | end 17 | 18 | test "declares form attributes" do 19 | attributes = [:name, :name=, :age, :age=, :gender, :gender=] 20 | 21 | attributes.each do |attribute| 22 | assert_respond_to @form, attribute 23 | end 24 | end 25 | 26 | test "delegates attributes to the model" do 27 | @form.name = "Peter" 28 | @form.age = 23 29 | @form.gender = 0 30 | 31 | assert_equal "Peter", @user.name 32 | assert_equal 23, @user.age 33 | assert_equal 0, @user.gender 34 | end 35 | 36 | test "validates itself" do 37 | @form.name = nil 38 | @form.age = nil 39 | @form.gender = nil 40 | 41 | assert_not @form.valid? 42 | [:name, :age, :gender].each do |attribute| 43 | assert_includes @form.errors.messages[attribute], "can't be blank" 44 | end 45 | 46 | @form.name = "Peters" 47 | @form.age = 23 48 | @form.gender = 0 49 | 50 | assert @form.valid? 51 | end 52 | 53 | test "validates the model" do 54 | peter = users(:peter) 55 | @form.name = peter.name 56 | @form.age = 23 57 | @form.gender = 0 58 | 59 | assert_not @form.valid? 60 | assert_includes @form.errors.messages[:name], "has already been taken" 61 | end 62 | 63 | test "sync the model with submitted data" do 64 | params = { 65 | name: "Peters", 66 | age: "23", 67 | gender: "0" 68 | } 69 | 70 | @form.submit(params) 71 | 72 | assert_equal "Peters", @form.name 73 | assert_equal 23, @form.age 74 | assert_equal 0, @form.gender 75 | end 76 | 77 | test "sync the form with existing model" do 78 | peter = users(:peter) 79 | form = UserFormFixture.new(peter) 80 | 81 | assert_equal "m-peter", form.name 82 | assert_equal 23, form.age 83 | assert_equal 0, form.gender 84 | end 85 | 86 | test "saves the model" do 87 | params = { 88 | name: "Peters", 89 | age: "23", 90 | gender: "0" 91 | } 92 | 93 | @form.submit(params) 94 | 95 | assert_difference('User.count') do 96 | @form.save 97 | end 98 | 99 | assert_equal "Peters", @form.name 100 | assert_equal 23, @form.age 101 | assert_equal 0, @form.gender 102 | end 103 | 104 | test "does not save the model with invalid data" do 105 | peter = users(:peter) 106 | params = { 107 | name: peter.name, 108 | age: "23", 109 | gender: nil 110 | } 111 | 112 | @form.submit(params) 113 | 114 | assert_difference('User.count', 0) do 115 | @form.save 116 | end 117 | 118 | assert_not @form.valid? 119 | assert_includes @form.errors.messages[:name], "has already been taken" 120 | assert_includes @form.errors.messages[:gender], "can't be blank" 121 | end 122 | 123 | test "updates the model" do 124 | peter = users(:peter) 125 | form = UserFormFixture.new(peter) 126 | params = { 127 | name: "Petrakos", 128 | age: peter.age, 129 | gender: peter.gender 130 | } 131 | 132 | form.submit(params) 133 | 134 | assert_difference('User.count', 0) do 135 | form.save 136 | end 137 | 138 | assert_equal "Petrakos", form.name 139 | end 140 | 141 | test "responds to #persisted?" do 142 | assert_respond_to @form, :persisted? 143 | assert_not @form.persisted? 144 | 145 | assert save_user 146 | assert @form.persisted? 147 | end 148 | 149 | test "responds to #to_key" do 150 | assert_respond_to @form, :to_key 151 | assert_nil @form.to_key 152 | 153 | assert save_user 154 | assert_equal @user.to_key, @form.to_key 155 | end 156 | 157 | test "responds to #to_param" do 158 | assert_respond_to @form, :to_param 159 | assert_nil @form.to_param 160 | 161 | assert save_user 162 | assert_equal @user.to_param, @form.to_param 163 | end 164 | 165 | test "responds to #to_partial_path" do 166 | assert_respond_to @form, :to_partial_path 167 | assert_instance_of String, @form.to_partial_path 168 | end 169 | 170 | test "responds to #to_model" do 171 | assert_respond_to @form, :to_model 172 | assert_equal @user, @form.to_model 173 | end 174 | 175 | private 176 | 177 | def save_user 178 | @form.name = "Peters" 179 | @form.age = 23 180 | @form.gender = 0 181 | 182 | @form.save 183 | end 184 | end -------------------------------------------------------------------------------- /test/dummy/config/initializers/simple_form.rb: -------------------------------------------------------------------------------- 1 | # Use this setup block to configure all options available in SimpleForm. 2 | SimpleForm.setup do |config| 3 | # Wrappers are used by the form builder to generate a complete input. 4 | # You can remove any component from the wrapper, change the order or even 5 | # add your own to the stack. The options given to the wrappers method 6 | # are used to wrap the whole input (if any exists). 7 | 8 | config.wrappers :inline, class: 'clearfix', error_class: :error do |b| 9 | b.use :placeholder 10 | b.use :label 11 | 12 | b.wrapper tag: "div", class: "input" do |ba| 13 | ba.use :input 14 | ba.use :error, wrap_with: { tag: 'span', class: 'help-inline' } 15 | ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' } 16 | end 17 | end 18 | 19 | config.wrappers :stacked, class: "clearfix", error_class: :error do |b| 20 | b.use :placeholder 21 | b.use :label 22 | b.use :hint, tag: :span, class: :'help-block' 23 | 24 | b.wrapper tag: "div", class: "input" do |input| 25 | input.use :input 26 | input.use :error, wrap_with: { tag: :span, class: 'help-inline' } 27 | end 28 | end 29 | 30 | config.wrappers :prepend, class: "clearfix", error_class: :error do |b| 31 | b.use :placeholder 32 | b.use :label 33 | b.use :hint, tag: :span, class: :'help-block' 34 | 35 | b.wrapper tag: "div", class: "input" do |input| 36 | input.wrapper tag: "div", class: "input-prepend" do |prepend| 37 | prepend.use :input 38 | end 39 | input.use :error, wrap_with: { tag: :span, class: 'help-inline' } 40 | end 41 | end 42 | 43 | config.wrappers :append, class: "clearfix", error_class: :error do |b| 44 | b.use :placeholder 45 | b.use :label 46 | b.use :hint, tag: :span, class: :'help-block' 47 | 48 | b.wrapper tag: "div", class: "input" do |input| 49 | input.wrapper tag: "div", class: "input-append" do |append| 50 | append.use :input 51 | end 52 | input.use :error, wrap_with: { tag: :span, class: 'help-inline' } 53 | end 54 | end 55 | 56 | # Method used to tidy up errors. 57 | # config.error_method = :first 58 | 59 | # Default tag used for error notification helper. 60 | # config.error_notification_tag = :p 61 | 62 | # CSS class to add for error notification helper. 63 | # config.error_notification_class = :error_notification 64 | 65 | # ID to add for error notification helper. 66 | # config.error_notification_id = nil 67 | 68 | # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none. 69 | # config.collection_wrapper_tag = nil 70 | 71 | # You can wrap each item in a collection of radio/check boxes with a tag, defaulting to span. 72 | # config.item_wrapper_tag = :span 73 | 74 | # Series of attempts to detect a default label method for collection. 75 | # config.collection_label_methods = [ :to_label, :name, :title, :to_s ] 76 | 77 | # Series of attempts to detect a default value method for collection. 78 | # config.collection_value_methods = [ :id, :to_s ] 79 | 80 | # How the label text should be generated altogether with the required text. 81 | config.label_text = lambda { |label, required| "#{label} #{required}" } 82 | 83 | # You can define the class to use on all labels. Default is nil. 84 | # config.label_class = nil 85 | 86 | # You can define the class to use on all forms. Default is simple_form. 87 | # config.form_class = :simple_form 88 | 89 | # Whether attributes are required by default (or not). Default is true. 90 | # config.required_by_default = true 91 | 92 | # Tell browsers whether to use default HTML5 validations (novalidate option). 93 | # Default is enabled. 94 | config.browser_validations = false 95 | 96 | # Determines whether HTML5 types (:email, :url, :search, :tel) and attributes 97 | # (e.g. required) are used or not. True by default. 98 | # Having this on in non-HTML5 compliant sites can cause odd behavior in 99 | # HTML5-aware browsers such as Chrome. 100 | # config.html5 = true 101 | 102 | # Custom mappings for input types. This should be a hash containing a regexp 103 | # to match as key, and the input type that will be used when the field name 104 | # matches the regexp as value. 105 | # config.input_mappings = { /count/ => :integer } 106 | 107 | # Collection of methods to detect if a file type was given. 108 | # config.file_methods = [ :mounted_as, :file?, :public_filename ] 109 | 110 | # Default priority for time_zone inputs. 111 | # config.time_zone_priority = nil 112 | 113 | # Default priority for country inputs. 114 | # config.country_priority = nil 115 | 116 | # Default size for text inputs. 117 | # config.default_input_size = 50 118 | 119 | # When false, do not use translations for labels, hints or placeholders. 120 | # config.translate = true 121 | 122 | # Default class for buttons 123 | config.button_class = 'btn' 124 | end -------------------------------------------------------------------------------- /test/dummy/db/schema.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # This file is auto-generated from the current state of the database. Instead 3 | # of editing this file, please use the migrations feature of Active Record to 4 | # incrementally modify your database, and then regenerate this schema definition. 5 | # 6 | # Note that this schema.rb definition is the authoritative source for your 7 | # database schema. If you need to create the application database on another 8 | # system, you should be using db:schema:load, not running all the migrations 9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). 11 | # 12 | # It's strongly recommended that you check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(version: 20140821074917) do 15 | 16 | create_table "answers", force: true do |t| 17 | t.text "content" 18 | t.integer "question_id" 19 | t.datetime "created_at" 20 | t.datetime "updated_at" 21 | end 22 | 23 | add_index "answers", ["question_id"], name: "index_answers_on_question_id" 24 | 25 | create_table "artists", force: true do |t| 26 | t.string "name" 27 | t.integer "song_id" 28 | t.datetime "created_at" 29 | t.datetime "updated_at" 30 | end 31 | 32 | add_index "artists", ["song_id"], name: "index_artists_on_song_id" 33 | 34 | create_table "assignments", force: true do |t| 35 | t.string "name" 36 | t.datetime "created_at" 37 | t.datetime "updated_at" 38 | end 39 | 40 | create_table "conferences", force: true do |t| 41 | t.string "name" 42 | t.string "city" 43 | t.datetime "created_at" 44 | t.datetime "updated_at" 45 | end 46 | 47 | create_table "emails", force: true do |t| 48 | t.string "address" 49 | t.integer "user_id" 50 | t.datetime "created_at" 51 | t.datetime "updated_at" 52 | end 53 | 54 | add_index "emails", ["user_id"], name: "index_emails_on_user_id" 55 | 56 | create_table "people", force: true do |t| 57 | t.string "name" 58 | t.string "role" 59 | t.string "description" 60 | t.integer "project_id" 61 | t.datetime "created_at" 62 | t.datetime "updated_at" 63 | end 64 | 65 | add_index "people", ["project_id"], name: "index_people_on_project_id" 66 | 67 | create_table "presentations", force: true do |t| 68 | t.string "topic" 69 | t.string "duration" 70 | t.integer "speaker_id" 71 | t.datetime "created_at" 72 | t.datetime "updated_at" 73 | end 74 | 75 | add_index "presentations", ["speaker_id"], name: "index_presentations_on_speaker_id" 76 | 77 | create_table "producers", force: true do |t| 78 | t.string "name" 79 | t.string "studio" 80 | t.integer "artist_id" 81 | t.datetime "created_at" 82 | t.datetime "updated_at" 83 | end 84 | 85 | add_index "producers", ["artist_id"], name: "index_producers_on_artist_id" 86 | 87 | create_table "profiles", force: true do |t| 88 | t.string "twitter_name" 89 | t.string "github_name" 90 | t.integer "user_id" 91 | t.datetime "created_at" 92 | t.datetime "updated_at" 93 | end 94 | 95 | add_index "profiles", ["user_id"], name: "index_profiles_on_user_id" 96 | 97 | create_table "project_tags", force: true do |t| 98 | t.integer "project_id" 99 | t.integer "tag_id" 100 | t.datetime "created_at" 101 | t.datetime "updated_at" 102 | end 103 | 104 | add_index "project_tags", ["project_id"], name: "index_project_tags_on_project_id" 105 | add_index "project_tags", ["tag_id"], name: "index_project_tags_on_tag_id" 106 | 107 | create_table "projects", force: true do |t| 108 | t.string "name" 109 | t.string "description" 110 | t.datetime "created_at" 111 | t.datetime "updated_at" 112 | t.integer "owner_id" 113 | end 114 | 115 | create_table "questions", force: true do |t| 116 | t.text "content" 117 | t.integer "survey_id" 118 | t.datetime "created_at" 119 | t.datetime "updated_at" 120 | end 121 | 122 | add_index "questions", ["survey_id"], name: "index_questions_on_survey_id" 123 | 124 | create_table "songs", force: true do |t| 125 | t.string "title" 126 | t.string "length" 127 | t.datetime "created_at" 128 | t.datetime "updated_at" 129 | end 130 | 131 | create_table "speakers", force: true do |t| 132 | t.string "name" 133 | t.string "occupation" 134 | t.integer "conference_id" 135 | t.datetime "created_at" 136 | t.datetime "updated_at" 137 | end 138 | 139 | add_index "speakers", ["conference_id"], name: "index_speakers_on_conference_id" 140 | 141 | create_table "sub_tasks", force: true do |t| 142 | t.string "name" 143 | t.string "description" 144 | t.boolean "done" 145 | t.integer "task_id" 146 | t.datetime "created_at" 147 | t.datetime "updated_at" 148 | end 149 | 150 | add_index "sub_tasks", ["task_id"], name: "index_sub_tasks_on_task_id" 151 | 152 | create_table "surveys", force: true do |t| 153 | t.string "name" 154 | t.datetime "created_at" 155 | t.datetime "updated_at" 156 | end 157 | 158 | create_table "tags", force: true do |t| 159 | t.string "name" 160 | t.datetime "created_at" 161 | t.datetime "updated_at" 162 | end 163 | 164 | create_table "tasks", force: true do |t| 165 | t.string "name" 166 | t.string "description" 167 | t.boolean "done" 168 | t.integer "project_id" 169 | t.datetime "created_at" 170 | t.datetime "updated_at" 171 | t.integer "assignment_id" 172 | end 173 | 174 | add_index "tasks", ["assignment_id"], name: "index_tasks_on_assignment_id" 175 | add_index "tasks", ["project_id"], name: "index_tasks_on_project_id" 176 | 177 | create_table "users", force: true do |t| 178 | t.string "name" 179 | t.integer "age" 180 | t.integer "gender" 181 | t.datetime "created_at" 182 | t.datetime "updated_at" 183 | end 184 | 185 | end 186 | -------------------------------------------------------------------------------- /test/forms/nested_model_form_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require_relative '../fixtures/user_with_email_form_fixture' 3 | 4 | class NestedModelFormTest < ActiveSupport::TestCase 5 | include ActiveModel::Lint::Tests 6 | fixtures :users, :emails 7 | 8 | def setup 9 | @user = User.new 10 | @form = UserWithEmailFormFixture.new(@user) 11 | @email_form = @form.email 12 | @model = @form 13 | end 14 | 15 | test "declares association" do 16 | assert_respond_to UserWithEmailFormFixture, :association 17 | end 18 | 19 | test "contains a list of sub-forms" do 20 | assert_respond_to UserWithEmailFormFixture, :forms 21 | end 22 | 23 | test "forms list contains form definitions" do 24 | email_definition = UserWithEmailFormFixture.forms.first 25 | 26 | assert_equal :email, email_definition.assoc_name 27 | end 28 | 29 | test "contains getter for email sub-form" do 30 | assert_respond_to @form, :email 31 | assert_instance_of ActionForm::Form, @form.email 32 | end 33 | 34 | test "email sub-form contains association name and parent model" do 35 | assert_equal :email, @email_form.association_name 36 | assert_equal @user, @email_form.parent 37 | end 38 | 39 | test "email sub-form initializes model for new parent" do 40 | assert_instance_of Email, @email_form.model 41 | assert_equal @form.model.email, @email_form.model 42 | assert @email_form.model.new_record? 43 | end 44 | 45 | test "email sub-form fetches model for existing parent" do 46 | user = users(:peter) 47 | user_form = UserWithEmailFormFixture.new(user) 48 | email_form = user_form.email 49 | 50 | assert_instance_of Email, email_form.model 51 | assert_equal user_form.model.email, email_form.model 52 | assert email_form.persisted? 53 | 54 | assert_equal "m-peter", user_form.name 55 | assert_equal 23, user_form.age 56 | assert_equal 0, user_form.gender 57 | assert_equal "markoupetr@gmail.com", email_form.address 58 | end 59 | 60 | test "#represents? returns true if the argument matches the Form's association name, false otherwise" do 61 | assert @email_form.represents?("email") 62 | assert_not @email_form.represents?("profile") 63 | end 64 | 65 | test "email sub-form declares attributes" do 66 | [:address, :address=].each do |attribute| 67 | assert_respond_to @email_form, attribute 68 | end 69 | end 70 | 71 | test "email sub-form delegates attributes to model" do 72 | @email_form.address = "petrakos@gmail.com" 73 | 74 | assert_equal "petrakos@gmail.com", @email_form.address 75 | assert_equal "petrakos@gmail.com", @email_form.model.address 76 | end 77 | 78 | test "email sub-form validates itself" do 79 | @email_form.address = nil 80 | 81 | assert_not @email_form.valid? 82 | assert_includes @email_form.errors.messages[:address], "can't be blank" 83 | 84 | @email_form.address = "petrakos@gmail.com" 85 | 86 | assert @email_form.valid? 87 | end 88 | 89 | test "email sub-form validates the model" do 90 | existing_email = emails(:peters) 91 | @email_form.address = existing_email.address 92 | 93 | assert_not @email_form.valid? 94 | assert_includes @email_form.errors.messages[:address], "has already been taken" 95 | 96 | @email_form.address = "petrakos@gmail.com" 97 | 98 | assert @email_form.valid? 99 | end 100 | 101 | test "main form syncs its model and the models in nested sub-forms" do 102 | params = { 103 | name: "Petrakos", 104 | age: "23", 105 | gender: "0", 106 | 107 | email_attributes: { 108 | address: "petrakos@gmail.com" 109 | } 110 | } 111 | 112 | @form.submit(params) 113 | 114 | assert_equal "Petrakos", @form.name 115 | assert_equal 23, @form.age 116 | assert_equal 0, @form.gender 117 | assert_equal "petrakos@gmail.com", @email_form.address 118 | end 119 | 120 | test "main form saves its model and the models in nested sub-forms" do 121 | params = { 122 | name: "Petrakos", 123 | age: "23", 124 | gender: "0", 125 | 126 | email_attributes: { 127 | address: "petrakos@gmail.com" 128 | } 129 | } 130 | 131 | @form.submit(params) 132 | 133 | assert_difference(['User.count', 'Email.count']) do 134 | @form.save 135 | end 136 | 137 | assert_equal "Petrakos", @form.name 138 | assert_equal 23, @form.age 139 | assert_equal 0, @form.gender 140 | assert_equal "petrakos@gmail.com", @email_form.address 141 | 142 | assert @form.persisted? 143 | assert @email_form.persisted? 144 | end 145 | 146 | test "main form updates its model and the models in nested sub-forms" do 147 | user = users(:peter) 148 | form = UserWithEmailFormFixture.new(user) 149 | params = { 150 | name: "Petrakos", 151 | age: 24, 152 | gender: 0, 153 | 154 | email_attributes: { 155 | address: "cs3199@teilar.gr" 156 | } 157 | } 158 | 159 | form.submit(params) 160 | 161 | assert_difference(['User.count', 'Email.count'], 0) do 162 | form.save 163 | end 164 | 165 | assert_equal "Petrakos", form.name 166 | assert_equal 24, form.age 167 | assert_equal 0, form.gender 168 | assert_equal "cs3199@teilar.gr", form.email.address 169 | end 170 | 171 | test "main form collects all the model related errors" do 172 | peter = users(:peter) 173 | params = { 174 | name: peter.name, 175 | age: "23", 176 | gender: "0", 177 | 178 | email_attributes: { 179 | address: peter.email.address 180 | } 181 | } 182 | 183 | @form.submit(params) 184 | 185 | assert_difference(['User.count', 'Email.count'], 0) do 186 | @form.save 187 | end 188 | 189 | assert_includes @form.errors[:name], "has already been taken" 190 | assert_includes @form.errors["email.address"], "has already been taken" 191 | end 192 | 193 | test "main form collects all the form specific errors" do 194 | params = { 195 | name: nil, 196 | age: nil, 197 | gender: nil, 198 | 199 | email_attributes: { 200 | address: nil 201 | } 202 | } 203 | 204 | @form.submit(params) 205 | 206 | assert_not @form.valid? 207 | 208 | assert_includes @form.errors[:name], "can't be blank" 209 | assert_includes @form.errors[:age], "can't be blank" 210 | assert_includes @form.errors[:gender], "can't be blank" 211 | assert_includes @form.errors["email.address"], "can't be blank" 212 | end 213 | 214 | test "main form responds to writer method" do 215 | assert_respond_to @form, :email_attributes= 216 | end 217 | end 218 | -------------------------------------------------------------------------------- /test/forms/two_nesting_level_form_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TwoNestingLevelFormTest < ActiveSupport::TestCase 4 | include ActiveModel::Lint::Tests 5 | fixtures :songs, :artists, :producers 6 | 7 | def setup 8 | @song = Song.new 9 | @form = SongForm.new(@song) 10 | @producer_form = @form.artist.producer 11 | @model = @form 12 | end 13 | 14 | test "contains getter for producer sub-form" do 15 | assert_respond_to @form.artist, :producer 16 | assert_instance_of ActionForm::Form, @producer_form 17 | end 18 | 19 | test "producer sub-form contains association name and parent model" do 20 | assert_equal :producer, @producer_form.association_name 21 | assert_instance_of Producer, @producer_form.model 22 | assert_instance_of Artist, @producer_form.parent 23 | end 24 | 25 | test "producer sub-form initializes models for new parent" do 26 | assert_equal @form.artist.model.producer, @producer_form.model 27 | assert @producer_form.model.new_record? 28 | end 29 | 30 | test "producer sub-form fetches models for existing parent" do 31 | song = songs(:lockdown) 32 | form = SongForm.new(song) 33 | artist_form = form.artist 34 | producer_form = artist_form.producer 35 | 36 | assert_equal "Love Lockdown", form.title 37 | assert_equal "350", form.length 38 | assert form.persisted? 39 | 40 | assert_equal "Kanye West", artist_form.name 41 | assert artist_form.persisted? 42 | 43 | assert_equal "Jay-Z", producer_form.name 44 | assert_equal "Ztudio", producer_form.studio 45 | assert producer_form.persisted? 46 | end 47 | 48 | test "producer sub-form declares attributes" do 49 | attributes = [:name, :name=, :studio, :studio=] 50 | 51 | attributes.each do |attribute| 52 | assert_respond_to @producer_form, attribute 53 | end 54 | end 55 | 56 | test "producer sub-form delegates attributes to model" do 57 | @producer_form.name = "Phoebos" 58 | @producer_form.studio = "MADog" 59 | 60 | assert_equal "Phoebos", @producer_form.name 61 | assert_equal "MADog", @producer_form.studio 62 | 63 | assert_equal "Phoebos", @producer_form.model.name 64 | assert_equal "MADog", @producer_form.model.studio 65 | end 66 | 67 | test "main form syncs its model and the models in nested sub-forms" do 68 | params = { 69 | title: "Diamonds", 70 | length: "360", 71 | 72 | artist_attributes: { 73 | name: "Karras", 74 | 75 | producer_attributes: { 76 | name: "Phoebos", 77 | studio: "MADog" 78 | } 79 | } 80 | } 81 | 82 | @form.submit(params) 83 | 84 | assert_equal "Diamonds", @form.title 85 | assert_equal "360", @form.length 86 | assert_equal "Karras", @form.artist.name 87 | assert_equal "Phoebos", @producer_form.name 88 | assert_equal "MADog", @producer_form.studio 89 | end 90 | 91 | test "main form validates itself" do 92 | params = { 93 | title: nil, 94 | length: nil, 95 | 96 | artist_attributes: { 97 | name: nil, 98 | 99 | producer_attributes: { 100 | name: nil, 101 | studio: nil 102 | } 103 | } 104 | } 105 | 106 | @form.submit(params) 107 | 108 | assert_not @form.valid? 109 | assert_includes @form.errors[:title], "can't be blank" 110 | assert_includes @form.errors[:length], "can't be blank" 111 | assert_includes @form.errors["artist.name"], "can't be blank" 112 | assert_includes @form.errors["artist.producer.studio"], "can't be blank" 113 | 114 | @form.title = "Diamonds" 115 | @form.length = "355" 116 | @form.artist.name = "Karras" 117 | @producer_form.name = "Phoebos" 118 | @producer_form.studio = "MADog" 119 | 120 | assert @form.valid? 121 | end 122 | 123 | test "main form validates the models" do 124 | song = songs(:lockdown) 125 | params = { 126 | title: song.title, 127 | length: nil, 128 | 129 | artist_attributes: { 130 | name: song.artist.name, 131 | 132 | producer_attributes: { 133 | name: song.artist.producer.name, 134 | studio: song.artist.producer.studio 135 | } 136 | } 137 | } 138 | 139 | @form.submit(params) 140 | 141 | assert_not @form.valid? 142 | assert_includes @form.errors[:title], "has already been taken" 143 | assert_includes @form.errors["artist.name"], "has already been taken" 144 | assert_includes @form.errors["artist.producer.name"], "has already been taken" 145 | assert_includes @form.errors["artist.producer.studio"], "has already been taken" 146 | end 147 | 148 | test "main form saves its model and the models in nested sub-forms" do 149 | params = { 150 | title: "Diamonds", 151 | length: "360", 152 | 153 | artist_attributes: { 154 | name: "Karras", 155 | 156 | producer_attributes: { 157 | name: "Phoebos", 158 | studio: "MADog" 159 | } 160 | } 161 | } 162 | 163 | @form.submit(params) 164 | 165 | assert_difference(['Song.count', 'Artist.count', 'Producer.count']) do 166 | @form.save 167 | end 168 | 169 | assert_equal "Diamonds", @form.title 170 | assert_equal "360", @form.length 171 | assert_equal "Karras", @form.artist.name 172 | assert_equal "Phoebos", @producer_form.name 173 | assert_equal "MADog", @producer_form.studio 174 | 175 | assert @form.persisted? 176 | assert @form.artist.persisted? 177 | assert @producer_form.persisted? 178 | end 179 | 180 | test "main form updates its model and the models in nested sub-forms" do 181 | song = songs(:lockdown) 182 | params = { 183 | title: "Diamonds", 184 | length: "360", 185 | 186 | artist_attributes: { 187 | name: "Karras", 188 | 189 | producer_attributes: { 190 | name: "Phoebos", 191 | studio: "MADog" 192 | } 193 | } 194 | } 195 | form = SongForm.new(song) 196 | 197 | form.submit(params) 198 | 199 | assert_difference(['Song.count', 'Artist.count', 'Producer.count'], 0) do 200 | form.save 201 | end 202 | 203 | assert_equal "Diamonds", form.title 204 | assert_equal "360", form.length 205 | assert_equal "Karras", form.artist.name 206 | assert_equal "Phoebos", form.artist.producer.name 207 | assert_equal "MADog", form.artist.producer.studio 208 | 209 | assert form.persisted? 210 | assert form.artist.persisted? 211 | assert form.artist.producer.persisted? 212 | end 213 | 214 | test "main form responds to writer method" do 215 | assert_respond_to @form, :artist_attributes= 216 | assert_respond_to @form.artist, :producer_attributes= 217 | end 218 | end 219 | -------------------------------------------------------------------------------- /test/forms/nested_models_form_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class NestedModelsFormTest < ActiveSupport::TestCase 4 | include ActiveModel::Lint::Tests 5 | fixtures :users, :emails, :profiles 6 | 7 | def setup 8 | @user = User.new 9 | @form = UserForm.new(@user) 10 | @profile_form = @form.profile 11 | @model = @form 12 | end 13 | 14 | test "declares both sub-forms" do 15 | assert_equal 2, UserForm.forms.size 16 | assert_equal 2, @form.forms.size 17 | end 18 | 19 | test "forms list contains profile sub-form definition" do 20 | profile_definition = UserForm.forms.last 21 | 22 | assert_equal :profile, profile_definition.assoc_name 23 | end 24 | 25 | test "profile sub-form contains association name and parent" do 26 | assert_equal :profile, @profile_form.association_name 27 | assert_equal @user, @profile_form.parent 28 | end 29 | 30 | test "profile sub-form declares attributes" do 31 | attributes = [:twitter_name, :twitter_name=, :github_name, :github_name=] 32 | 33 | attributes.each do |attribute| 34 | assert_respond_to @profile_form, attribute 35 | end 36 | end 37 | 38 | test "profile sub-form delegates attributes to model" do 39 | @profile_form.twitter_name = "twitter_peter" 40 | @profile_form.github_name = "github_peter" 41 | 42 | assert_equal "twitter_peter", @profile_form.twitter_name 43 | assert_equal "twitter_peter", @profile_form.model.twitter_name 44 | 45 | assert_equal "github_peter", @profile_form.github_name 46 | assert_equal "github_peter", @profile_form.model.github_name 47 | end 48 | 49 | test "profile sub-form initializes model for new parent" do 50 | assert_instance_of Profile, @profile_form.model 51 | assert_equal @form.model.profile, @profile_form.model 52 | assert @profile_form.model.new_record? 53 | end 54 | 55 | test "profile sub-form fetches model for existing parent" do 56 | user = users(:peter) 57 | user_form = UserForm.new(user) 58 | profile_form = user_form.profile 59 | 60 | assert_instance_of Profile, profile_form.model 61 | assert_equal user_form.model.profile, profile_form.model 62 | assert profile_form.persisted? 63 | 64 | assert_equal "m-peter", user_form.name 65 | assert_equal 23, user_form.age 66 | assert_equal 0, user_form.gender 67 | assert_equal "twitter_peter", profile_form.model.twitter_name 68 | assert_equal "github_peter", profile_form.model.github_name 69 | end 70 | 71 | test "profile sub-form validates itself" do 72 | @profile_form.twitter_name = nil 73 | @profile_form.github_name = nil 74 | 75 | assert_not @profile_form.valid? 76 | [:twitter_name, :github_name].each do |attribute| 77 | assert_includes @profile_form.errors.messages[attribute], "can't be blank" 78 | end 79 | 80 | @profile_form.twitter_name = "t-peter" 81 | @profile_form.github_name = "g-peter" 82 | 83 | assert @profile_form.valid? 84 | end 85 | 86 | test "main form syncs its model and the models in nested sub-forms" do 87 | params = { 88 | name: "Petrakos", 89 | age: "23", 90 | gender: "0", 91 | 92 | email_attributes: { 93 | address: "petrakos@gmail.com" 94 | }, 95 | 96 | profile_attributes: { 97 | twitter_name: "t_peter", 98 | github_name: "g_peter" 99 | } 100 | } 101 | 102 | @form.submit(params) 103 | 104 | assert_equal "Petrakos", @form.name 105 | assert_equal 23, @form.age 106 | assert_equal 0, @form.gender 107 | assert_equal "petrakos@gmail.com", @form.email.address 108 | assert_equal "t_peter", @profile_form.twitter_name 109 | assert_equal "g_peter", @profile_form.github_name 110 | end 111 | 112 | test "main form saves its model and the models in nested sub-forms" do 113 | params = { 114 | name: "Petrakos", 115 | age: "23", 116 | gender: "0", 117 | 118 | email_attributes: { 119 | address: "petrakos@gmail.com" 120 | }, 121 | 122 | profile_attributes: { 123 | twitter_name: "t_peter", 124 | github_name: "g_peter" 125 | } 126 | } 127 | 128 | @form.submit(params) 129 | 130 | assert_difference(['User.count', 'Email.count', 'Profile.count']) do 131 | @form.save 132 | end 133 | 134 | assert_equal "Petrakos", @form.name 135 | assert_equal 23, @form.age 136 | assert_equal 0, @form.gender 137 | assert_equal "petrakos@gmail.com", @form.email.address 138 | assert_equal "t_peter", @profile_form.twitter_name 139 | assert_equal "g_peter", @profile_form.github_name 140 | 141 | assert @form.persisted? 142 | assert @form.email.persisted? 143 | assert @profile_form.persisted? 144 | end 145 | 146 | test "main form updates its model and the models in nested sub-forms" do 147 | user = users(:peter) 148 | form = UserForm.new(user) 149 | params = { 150 | name: "Petrakos", 151 | age: 24, 152 | gender: 0, 153 | 154 | email_attributes: { 155 | address: "cs3199@teilar.gr" 156 | }, 157 | 158 | profile_attributes: { 159 | twitter_name: "peter_t", 160 | github_name: "peter_g" 161 | } 162 | } 163 | 164 | form.submit(params) 165 | 166 | assert_difference(['User.count', 'Email.count'], 0) do 167 | form.save 168 | end 169 | 170 | assert_equal "Petrakos", form.name 171 | assert_equal 24, form.age 172 | assert_equal 0, form.gender 173 | assert_equal "cs3199@teilar.gr", form.email.address 174 | assert_equal "peter_t", form.profile.twitter_name 175 | assert_equal "peter_g", form.profile.github_name 176 | end 177 | 178 | test "main form collects all the model related errors" do 179 | peter = users(:peter) 180 | params = { 181 | name: peter.name, 182 | age: "23", 183 | gender: "0", 184 | 185 | email_attributes: { 186 | address: peter.email.address 187 | }, 188 | 189 | profile_attributes: { 190 | twitter_name: peter.profile.twitter_name, 191 | github_name: peter.profile.github_name 192 | } 193 | } 194 | 195 | @form.submit(params) 196 | 197 | assert_difference(['User.count', 'Email.count', 'Profile.count'], 0) do 198 | @form.save 199 | end 200 | 201 | assert_includes @form.errors[:name], "has already been taken" 202 | assert_includes @form.errors["email.address"], "has already been taken" 203 | assert_includes @form.errors["profile.twitter_name"], "has already been taken" 204 | assert_includes @form.errors["profile.github_name"], "has already been taken" 205 | end 206 | 207 | test "main form collects all the form specific errors" do 208 | params = { 209 | name: nil, 210 | age: nil, 211 | gender: nil, 212 | 213 | email_attributes: { 214 | address: nil 215 | }, 216 | 217 | profile_attributes: { 218 | twitter_name: nil, 219 | github_name: nil 220 | } 221 | } 222 | 223 | @form.submit(params) 224 | 225 | assert_not @form.valid? 226 | 227 | assert_includes @form.errors[:name], "can't be blank" 228 | assert_includes @form.errors[:age], "can't be blank" 229 | assert_includes @form.errors[:gender], "can't be blank" 230 | assert_includes @form.errors["email.address"], "can't be blank" 231 | assert_includes @form.errors["profile.twitter_name"], "can't be blank" 232 | assert_includes @form.errors["profile.github_name"], "can't be blank" 233 | end 234 | 235 | test "main form responds to writer method" do 236 | assert_respond_to @form, :email_attributes= 237 | assert_respond_to @form, :profile_attributes= 238 | end 239 | end 240 | -------------------------------------------------------------------------------- /test/dummy/test/controllers/conferences_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ConferencesControllerTest < ActionController::TestCase 4 | fixtures :conferences, :speakers, :presentations 5 | 6 | setup do 7 | @conference = conferences(:ruby) 8 | end 9 | 10 | test "should get index" do 11 | get :index 12 | assert_response :success 13 | assert_not_nil assigns(:conferences) 14 | end 15 | 16 | test "should get new" do 17 | get :new 18 | assert_response :success 19 | end 20 | 21 | test "should create conference" do 22 | assert_difference('Conference.count') do 23 | post :create, conference: { 24 | name: "Euruco", 25 | city: "Athens", 26 | 27 | speaker_attributes: { 28 | name: "Petros Markou", 29 | occupation: "Developer", 30 | 31 | presentations_attributes: { 32 | "0" => { topic: "Ruby OOP", duration: "1h" }, 33 | "1" => { topic: "Ruby Closures", duration: "1h" }, 34 | } 35 | } 36 | } 37 | end 38 | 39 | conference_form = assigns(:conference_form) 40 | 41 | assert conference_form.valid? 42 | assert_redirected_to conference_path(conference_form) 43 | 44 | assert_equal "Euruco", conference_form.name 45 | assert_equal "Athens", conference_form.city 46 | 47 | assert_equal "Petros Markou", conference_form.speaker.name 48 | assert_equal "Developer", conference_form.speaker.occupation 49 | 50 | assert_equal "Ruby OOP", conference_form.speaker.presentations[0].topic 51 | assert_equal "1h", conference_form.speaker.presentations[0].duration 52 | assert_equal "Ruby Closures", conference_form.speaker.presentations[1].topic 53 | assert_equal "1h", conference_form.speaker.presentations[1].duration 54 | 55 | assert conference_form.speaker.persisted? 56 | conference_form.speaker.presentations.each do |presentation| 57 | presentation.persisted? 58 | end 59 | 60 | assert_equal "Conference: #{conference_form.name} was successfully created.", flash[:notice] 61 | end 62 | 63 | test "should create dynamically added presentation to speaker" do 64 | assert_difference('Conference.count') do 65 | post :create, conference: { 66 | name: "Euruco", 67 | city: "Athens", 68 | 69 | speaker_attributes: { 70 | name: "Petros Markou", 71 | occupation: "Developer", 72 | 73 | presentations_attributes: { 74 | "0" => { topic: "Ruby OOP", duration: "1h" }, 75 | "1" => { topic: "Ruby Closures", duration: "1h" }, 76 | "12312" => { topic: "Ruby Metaprogramming", duration: "2h" } 77 | } 78 | } 79 | } 80 | end 81 | 82 | conference_form = assigns(:conference_form) 83 | 84 | assert conference_form.valid? 85 | assert_redirected_to conference_path(conference_form) 86 | 87 | assert_equal "Euruco", conference_form.name 88 | assert_equal "Athens", conference_form.city 89 | 90 | assert_equal "Petros Markou", conference_form.speaker.name 91 | assert_equal "Developer", conference_form.speaker.occupation 92 | 93 | assert_equal 3, conference_form.speaker.presentations.size 94 | 95 | assert_equal "Ruby OOP", conference_form.speaker.presentations[0].topic 96 | assert_equal "1h", conference_form.speaker.presentations[0].duration 97 | assert_equal "Ruby Closures", conference_form.speaker.presentations[1].topic 98 | assert_equal "1h", conference_form.speaker.presentations[1].duration 99 | assert_equal "Ruby Metaprogramming", conference_form.speaker.presentations[2].topic 100 | assert_equal "2h", conference_form.speaker.presentations[2].duration 101 | 102 | assert conference_form.speaker.persisted? 103 | conference_form.speaker.presentations.each do |presentation| 104 | presentation.persisted? 105 | end 106 | 107 | assert_equal "Conference: #{conference_form.name} was successfully created.", flash[:notice] 108 | end 109 | 110 | test "should not create conference with invalid params" do 111 | conference = conferences(:ruby) 112 | 113 | assert_difference(['Conference.count', 'Speaker.count'], 0) do 114 | post :create, conference: { 115 | name: conference.name, 116 | city: nil, 117 | 118 | speaker_attributes: { 119 | name: conference.speaker.name, 120 | occupation: "Developer", 121 | 122 | presentations_attributes: { 123 | "0" => { topic: nil, duration: "1h" }, 124 | "1" => { topic: "Ruby Closures", duration: nil }, 125 | } 126 | } 127 | } 128 | end 129 | 130 | conference_form = assigns(:conference_form) 131 | 132 | assert_not conference_form.valid? 133 | 134 | assert_includes conference_form.errors.messages[:name], "has already been taken" 135 | assert_includes conference_form.errors.messages[:city], "can't be blank" 136 | 137 | assert_includes conference_form.speaker.errors.messages[:name], "has already been taken" 138 | 139 | assert_includes conference_form.speaker.presentations[0].errors.messages[:topic], "can't be blank" 140 | assert_includes conference_form.speaker.presentations[1].errors.messages[:duration], "can't be blank" 141 | end 142 | 143 | test "should show conference" do 144 | get :show, id: @conference 145 | assert_response :success 146 | end 147 | 148 | test "should get edit" do 149 | get :edit, id: @conference 150 | assert_response :success 151 | end 152 | 153 | test "should update conference" do 154 | assert_difference('Conference.count', 0) do 155 | patch :update, id: @conference, conference: { 156 | name: "GoGaruco", 157 | city: "Golden State", 158 | 159 | speaker_attributes: { 160 | name: "John Doe", 161 | occupation: "Developer", 162 | 163 | presentations_attributes: { 164 | "0" => { topic: "Rails OOP", duration: "1h", id: presentations(:ruby_oop).id }, 165 | "1" => { topic: "Rails Patterns", duration: "1h", id: presentations(:ruby_closures).id }, 166 | } 167 | } 168 | } 169 | end 170 | 171 | conference_form = assigns(:conference_form) 172 | 173 | assert_redirected_to conference_path(conference_form) 174 | 175 | assert_equal "GoGaruco", conference_form.name 176 | assert_equal "Golden State", conference_form.city 177 | 178 | assert_equal "John Doe", conference_form.speaker.name 179 | assert_equal "Developer", conference_form.speaker.occupation 180 | 181 | assert_equal "Rails Patterns", conference_form.speaker.presentations[0].topic 182 | assert_equal "1h", conference_form.speaker.presentations[0].duration 183 | assert_equal "Rails OOP", conference_form.speaker.presentations[1].topic 184 | assert_equal "1h", conference_form.speaker.presentations[1].duration 185 | 186 | assert_equal "Conference: #{conference_form.name} was successfully updated.", flash[:notice] 187 | end 188 | 189 | test "should destroy dynamically removed presentation from speaker" do 190 | assert_difference('Conference.count', 0) do 191 | patch :update, id: @conference, conference: { 192 | name: "GoGaruco", 193 | city: "Golden State", 194 | 195 | speaker_attributes: { 196 | name: "John Doe", 197 | occupation: "Developer", 198 | 199 | presentations_attributes: { 200 | "0" => { topic: "Rails OOP", duration: "1h", id: presentations(:ruby_oop).id }, 201 | "1" => { topic: "Rails Patterns", duration: "1h", id: presentations(:ruby_closures).id, _destroy: "1" }, 202 | } 203 | } 204 | } 205 | end 206 | 207 | conference_form = assigns(:conference_form) 208 | 209 | assert_redirected_to conference_path(conference_form) 210 | 211 | assert_equal "GoGaruco", conference_form.name 212 | assert_equal "Golden State", conference_form.city 213 | 214 | assert_equal "John Doe", conference_form.speaker.name 215 | assert_equal "Developer", conference_form.speaker.occupation 216 | 217 | assert_equal "Rails OOP", conference_form.speaker.presentations[0].topic 218 | assert_equal "1h", conference_form.speaker.presentations[0].duration 219 | 220 | assert_equal 1, conference_form.speaker.presentations.size 221 | 222 | assert_equal "Conference: #{conference_form.name} was successfully updated.", flash[:notice] 223 | end 224 | 225 | test "should destroy conference" do 226 | assert_difference('Conference.count', -1) do 227 | delete :destroy, id: @conference 228 | end 229 | 230 | assert_redirected_to conferences_path 231 | end 232 | end 233 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Action Form 2 | 3 | # DISCLAIMER: This project is an experiment and should not be used in applications. 4 | 5 | [![Build Status](https://api.travis-ci.org/railsgsoc/actionform.svg?branch=master)](https://travis-ci.org/railsgsoc/actionform) 6 | 7 | Set your models free from the `accepts_nested_attributes_for` helper. Action Form provides an object-oriented approach to represent your forms by building a form object, rather than relying on Active Record internals for doing this. Form objects provide an API to describe the models involved in the form, their attributes and validations. A form object deals with create/update actions of nested objects in a more seamless way. 8 | 9 | ## Installation 10 | 11 | Add this line to your `Gemfile`: 12 | 13 | ```ruby 14 | gem 'actionform' 15 | ``` 16 | 17 | ## Defining Forms 18 | 19 | Consider an example where you want to create/update a conference that can have many speakers which can present a single presentation with one form submission. You start by defining a form to represent the root model, `Conference`: 20 | 21 | ```ruby 22 | class ConferenceForm < ActionForm::Base 23 | self.main_model = :conference 24 | 25 | attributes :name, :city 26 | 27 | validates :name, :city, presence: true 28 | end 29 | ``` 30 | 31 | Your form object has to subclass `ActionForm::Base` in order to gain the necessary API. When defining the form, you have to specify the main_model the form represents with the following line: 32 | 33 | ```ruby 34 | self.main_model = :conference 35 | ``` 36 | 37 | To add fields to the form, use the `attributes` or `attribute` class method. The form can also define validation rules for the model it represents. For the `presence` validation rule there is a short inline syntax: 38 | 39 | ```ruby 40 | class ConferenceForm < ActionForm::Base 41 | attributes :name, :city, required: true 42 | end 43 | ``` 44 | 45 | ## The API 46 | 47 | The `ActionForm::Base` class provides a simple API with only a few instance/class methods. Below are listed the instance methods: 48 | 49 | 1. `initialize(model)` accepts an instance of the model that the form represents. 50 | 2. `submit(params)` updates the main form's model and nested models with the posted parameters. The models are not saved/updated until you call `save`. 51 | 3. `errors` returns validation messages in a classy Active Model style. 52 | 4. `save` will call `save` on the model and nested models. This method will validate the model and nested models and if no error arises then it will save them and return true. 53 | 54 | The following are the class methods: 55 | 56 | 1. `attributes` accepts the names of attributes to define on the form. If you want to declare a presence validation rule for the given attributes, you can pass in the `required: true` option as showcased above. The `attribute` method is aliased to the `attributes` method. 57 | 2. `association(name, options={}, &block)` defines a nested form for the `name` model. If the model is a `has_many` association you can pass in the `records: x` option and fields to create `x` objects will be rendered. If you pass a block, you can define another nested form the same way. 58 | 59 | In addition to the main API, forms expose accessors to the defined attributes. This is used for rendering or manual operations. 60 | 61 | ## Setup 62 | 63 | In your controller you create a form instance and pass in the model you want to work on. 64 | 65 | ```ruby 66 | class ConferencesController 67 | def new 68 | conference = Conference.new 69 | @conference_form = ConferenceForm.new(conference) 70 | end 71 | end 72 | ``` 73 | 74 | You can also setup the form for editing existing items. 75 | 76 | ```ruby 77 | class ConferencesController 78 | def edit 79 | conference = Conference.find(params[:id]) 80 | @conference_form = ConferenceForm.new(conference) 81 | end 82 | end 83 | ``` 84 | 85 | Action Form will read property values from the model in setup. Given the following form class. 86 | 87 | ```ruby 88 | class ConferenceForm < ActionForm::Base 89 | attribute :name 90 | end 91 | ``` 92 | 93 | Internally, this form will call `conference.name` to populate the name field. 94 | 95 | ## Rendering Forms 96 | 97 | Your `@conference_form` is now ready to be rendered, either do it yourself or use something like Rails' `form_for`, `simple_form` or `formtastic`. 98 | 99 | ```erb 100 | <%= form_for @conference_form do |f| %> 101 | <%= f.text_field :name %> 102 | <%= f.text_field :city %> 103 | <% end %> 104 | ``` 105 | 106 | Nested forms and collections can be easily rendered with `fields_for`, etc. Just use Action Form as if it would be an Active Model instance in the view layer. 107 | 108 | ## Syncing Back 109 | 110 | After setting up your form object, you can populate the models with the submitted parameters. 111 | 112 | ```ruby 113 | class ConferencesController 114 | def create 115 | conference = Conference.new 116 | @conference_form = ConferenceForm.new(conference) 117 | @conference_form.submit(conference_params) 118 | end 119 | end 120 | ``` 121 | 122 | This will write all the properties back to the model. In a nested form, this works recursively, of course. 123 | 124 | ## Saving Forms 125 | 126 | After the form is populated with the posted data, you can save the model by calling `save`. 127 | 128 | ```ruby 129 | class ConferencesController 130 | def create 131 | conference = Conference.new 132 | @conference_form = ConferenceForm.new(conference) 133 | @conference_form.submit(conference_params) 134 | 135 | if @conference_form.save 136 | redirect_to @conference_form, notice: "Conference: #{@conference_form.name} was successfully created." } 137 | else 138 | render :new 139 | end 140 | end 141 | end 142 | ``` 143 | 144 | If the `save` method returns false due to validation errors defined on the form, you can render it again with the data that has been submitted and the errors found. 145 | 146 | ## Nesting Forms: 1-n Relations 147 | 148 | Action Form also gives you nested collections. 149 | 150 | Let's define the `has_many :speakers` collection association on the `Conference` model. 151 | 152 | ```ruby 153 | class Conference < ActiveRecord::Base 154 | has_many :speakers 155 | validates :name, uniqueness: true 156 | end 157 | ``` 158 | 159 | The form should look like this. 160 | 161 | ```ruby 162 | class ConferenceForm < ActionForm::Base 163 | attributes :name, :city, required: true 164 | 165 | association :speakers do 166 | attributes :name, :occupation, required: true 167 | end 168 | end 169 | ``` 170 | 171 | By default, the `association :speakers` declaration will create a single `Speaker` object. You can specify how many objects you want in your form to be rendered with the `new` action as follows: `association: speakers, records: 2`. This will create 2 new `Speaker` objects, and of course fields to create 2 `Speaker` objects. There are also some link helpers to dynamically add/remove objects from collection associations. Read below. 172 | 173 | This basically works like a nested `property` that iterates over a collection of speakers. 174 | 175 | ### has_many: Rendering 176 | 177 | Action Form will expose the collection using the `speakers` method. 178 | 179 | ```erb 180 | <%= form_for @conference_form |f| %> 181 | <%= f.text_field :name %> 182 | <%= f.text_field :city %> 183 | 184 | <%= f.fields_for :speakers do |s| %> 185 | <%= s.text_field :name %> 186 | <%= s.text_field :occupation %> 187 | <% end %> 188 | <% end %> 189 | ``` 190 | 191 | ## Nesting Forms: 1-1 Relations 192 | 193 | Speakers are allowed to have 1 Presentation. 194 | 195 | ```ruby 196 | class Speaker < ActiveRecord::Base 197 | has_one :presentation 198 | belongs_to :conference 199 | validates :name, uniqueness: true 200 | end 201 | ``` 202 | 203 | The full form should look like this: 204 | 205 | ```ruby 206 | class ConferenceForm < ActionForm::Base 207 | attributes :name, :city, required: true 208 | 209 | association :speakers do 210 | attribute :name, :occupation, required: true 211 | 212 | association :presentation do 213 | attribute :topic, :duration, required: true 214 | end 215 | end 216 | end 217 | ``` 218 | 219 | ### has_one: Rendering 220 | 221 | Use `fields_for` in a Rails environment to correctly setup the structure of params. 222 | 223 | ```erb 224 | <%= form_for @conference_form |f| %> 225 | <%= f.text_field :name %> 226 | <%= f.text_field :city %> 227 | 228 | <%= f.fields_for :speakers do |s| %> 229 | <%= s.text_field :name %> 230 | <%= s.text_field :occupation %> 231 | 232 | <%= s.fields_for :presentation do |p| %> 233 | <%= p.text_field :topic %> 234 | <%= p.text_field :duration %> 235 | <% end %> 236 | <% end %> 237 | <% end %> 238 | ``` 239 | 240 | ## Dynamically Adding/Removing Nested Objects 241 | 242 | Action Form comes with two helpers to deal with this functionality: 243 | 244 | 1. `link_to_add_association` will display a link that renders fields to create a new object. 245 | 2. `link_to_remove_association` will display a link to remove a existing/dynamic object. 246 | 247 | In order to use it you have to insert this line: `//= require action_form` to your `app/assets/javascript/application.js` file. 248 | 249 | In our `ConferenceForm` we can dynamically create/remove `Speaker` objects. To do that we would write in the `app/views/conferences/_form.html.erb` partial: 250 | 251 | ```erb 252 | <%= form_for @conference_form do |f| %> 253 | <% if @conference_form.errors.any? %> 254 |
255 |

<%= pluralize(@conference_form.errors.count, "error") %> prohibited this conference from being saved:

256 | 257 | 262 |
263 | <% end %> 264 | 265 |

Conference Details

266 |
267 | <%= f.label :name, "Conference Name" %>
268 | <%= f.text_field :name %> 269 |
270 |
271 | <%= f.label :city %>
272 | <%= f.text_field :city %> 273 |
274 | 275 |

Speaker Details

276 | <%= f.fields_for :speakers do |speaker_fields| %> 277 | <%= render "speaker_fields", :f => speaker_fields %> 278 | <% end %> 279 | 280 | 283 | 284 |
285 | <%= f.submit %> 286 |
287 | <% end %> 288 | ``` 289 | 290 | Our `app/views/conferences/_speaker_fields.html.erb` would be: 291 | 292 | ```erb 293 |
294 |
295 | <%= f.label :name, "Speaker Name" %>
296 | <%= f.text_field :name %> 297 |
298 | 299 |
300 | <%= f.label :occupation %>
301 | <%= f.text_field :occupation %> 302 |
303 | 304 |

Presentantions

305 | <%= f.fields_for :presentation do |presentations_fields| %> 306 | <%= render "presentation_fields", :f => presentations_fields %> 307 | <% end %> 308 | 309 | <%= link_to_remove_association "Delete", f %> 310 |
311 | ``` 312 | 313 | And `app/views/conferences/_presentation_fields.html.erb` would be: 314 | 315 | ```erb 316 |
317 | <%= f.label :topic %>
318 | <%= f.text_field :topic %> 319 |
320 | 321 |
322 | <%= f.label :duration %>
323 | <%= f.text_field :duration %> 324 |
325 | ``` 326 | 327 | ## Plain Old Ruby Object Forms 328 | 329 | ActionForm also can accept `ActiveModel::Model` instances as a model. 330 | 331 | ```ruby 332 | class Feedback 333 | include ActiveModel::Model 334 | 335 | attr_accessor :name, :body, :email 336 | 337 | def save 338 | FeedbackMailer.send_email(email, name, body) 339 | end 340 | end 341 | ``` 342 | 343 | The form should look like this. 344 | 345 | ```ruby 346 | class FeedbackForm < ActionForm::Base 347 | attributes :name, :body, :email, required: true 348 | end 349 | ``` 350 | 351 | And then in controller: 352 | 353 | ```ruby 354 | class FeedbacksController 355 | def create 356 | feedback = Feedback.new 357 | @feedback_form = FeedbackForm.new(feedback) 358 | @feedback_form.submit(feedback_params) 359 | 360 | if @feedback_form.save 361 | head :ok 362 | else 363 | render json: @feedback_form.errors 364 | end 365 | end 366 | ``` 367 | 368 | ## Demos 369 | 370 | You can find a list of applications using this gem in this repository: https://github.com/m-Peter/nested-form-examples . 371 | All the examples are implemented in before/after pairs. The before is using the `accepts_nested_attributes_for`, while the after uses this gem to achieve the same functionality. 372 | 373 | ## Credits 374 | 375 | Special thanks to the owners of the great gems that inspired this work: 376 | 377 | * [Nick Sutterer](https://github.com/apotonick) - creator of [reform](https://github.com/apotonick/reform) 378 | * [Nathan Van der Auwera](https://github.com/nathanvda) - creator of [cocoon](https://github.com/nathanvda/cocoon) 379 | --------------------------------------------------------------------------------