Ruby on Railsのプロジェクトの複雑さの管理。 パート3

前のパートでは、コントローラーとルーティングについて説明しました。 次に、フォームについて説明します。 多くの場合、モデルが一致しないフォームを実装する必要があります。 または、特定のビジネスプロセスでのみ意味のある検証を追加します。







フォームオブジェクトとタイプの2種類のフォームについて説明します。







フォームオブジェクトは、操作にデータが必要な場合にユーザー入力を処理および検証するために使用されます。 たとえば、ユーザーログインやデータフィルタリング。







タイプは、モデルの動作を拡張する場合に使用されます。 たとえば、プロジェクトでは、ユーザーはvkontakteと通常のフォームの両方で登録できます。 通常のユーザーには電子メールが必要ですが、vkユーザーには必要ありません。 この動作は、型を使用すると簡単に解決できます。







フォームオブジェクト



RoRプロジェクトでは、フォームはモデルに厳密に結び付けられています。 オブジェクトモデルなしで複雑な形状をレンダリングすることはほとんど不可能であり、便利ではありません。 したがって、フォームオブジェクトはActiveModel :: Modelを使用して展開されます。 したがって、フォームは永続性サポートのないモデルです(データベースに保存されません)。 したがって、フォームビルダー、検証、ローカリゼーションとのシームレスな統合を実現します。







便宜上、フォームオブジェクトもgem virtusを使用します。 彼はキャストを引き継ぎ、デフォルト値を設定します。 たとえば、日付が文字列表現のフォームからのものである場合、virtusは自動的に日付に変換します。







#      # app/forms/base_form.rb class BaseForm include Virtus.model(strict: true) include ActiveModel::Model end # app/forms/user/statistics_filter_form.rb class User::StatisticsFilterForm < BaseForm attribute :start_date, ActiveSupport::TimeWithZone, default: ->(*) { DateTime.current.beginning_of_month } attribute :end_date, ActiveSupport::TimeWithZone, default: ->(model, _) { model.start_date.next_month } end # app/controllers/web/users/statistics_controller.rb class Web::Users::StatisticsController < Web::Users::ApplicationController def show #     permits, ..     active_record  @filter_form = User::StatisticsFilterForm.new params[:user_statistics_filter_form] @statistics = UserStatisticsQuery.perform resource_user, @filter_form.start_date, @filter_form.end_date end end = simple_form_for @filter_form, method: :get, url: {} do |f| = f.input :start_date, as: :datetime_picker = f.input :end_date, as: :datetime_picker = f.button :submit
      
      





より複雑な状況を考えてください。 メールとパスワードの2つのフィールドがあるログインフォームがあります。 フィールドは必須です。 また、ユーザーが見つからないか、パスワードが一致しない場合、対応するエラーが表示されます。







 # app/forms/session_form.rb class SessionForm < BaseForm attribute :email attribute :password validates :email, email: true validates :password, presence: true #    ,         validate do errors.add(:base, :wrong_email_or_password) unless user.try(:authenticate, password) end def user @user ||= User.find_by email: email end end # app/controllers/web/sessions_controller.rb class Web::SessionsController < Web::ApplicationController def new @session_form = SessionForm.new end def create @session_form = SessionForm.new session_form_params #       if @session_form.valid? sign_in @session_form.user redirect_to root_path else render :new end end private def session_form_params params.require(:session_form).permit(:email, :password) end end
      
      





この例では、フォームが入力データの検証を処理し、コントローラーに不要なロジックが含まれず、モデルはパスワードのみをチェックします。







このアプローチでは、追加機能を実装するのは非常に簡単です。「私を記憶する」チェックボックスを表示し、ユーザーをブラックリストからブロックします。







種類



私が間違っていなければ、Typesはsymfonyから来ました。 タイプはモデルの後継モデルで、親になりすまし、新しい機能を追加します。







このタスクを検討してください。アプリケーションのユーザーは、自分よりも低いランクのユーザーのみを招待できます。 また、招待者は招待者のパスワードを知る必要はありません。 新しいユーザーに割り当てることができるユーザーロールのリストは、ポリシーによって決定されます。 ACLについては、これについて詳しく説明します。







 module BaseType extend ActiveSupport::Concern class_methods do def model_name superclass.model_name end end end class InviteType < User include BaseType after_initialize :generate_password, if: :new_record? validates :role, inclusion: { in: :available_roles } validates :inviter, presence: true #  def policy InvitePolicy.new(inviter, self) end def available_roles policy.available_roles end def available_role_options User.role.options.select{ |option| option.last.in? available_roles } end private def generate_password self.password = SecureRandom.urlsafe_base64(6) end end
      
      





InviteTypeは招待者を確認し、パスワードを生成し、利用可能なロールのリストを制限します。







BaseTypeについて詳しく説明します。 typeが親として扱われるように、model_nameメソッドをオーバーライドします。 次のように、nameメソッドをオーバーライドしないでください。 このため、ルビーは屋根を吹きます。 STIを使用する場合、微妙な点があります。sti_nameメソッドをさらに再定義する必要があります。







フォームのオブジェクトと型があるため、フォームからのデータを変換すると便利です。 たとえば、フォームには、費やした時間、費やした時間の2つのフィールドがあり、モデルは費やした時間を秒単位で保存します。







 class CommentType < Comment include BaseType # some code def elapsed_time_hours TimeConverter.convert_to_time(elapsed_time.to_i)[:hours] end def elapsed_time_hours=(v) update_elapsed_time v.to_i, elapsed_time_minutes end def elapsed_time_minutes TimeConverter.convert_to_time(elapsed_time.to_i)[:minutes] end def elapsed_time_minutes=(v) update_elapsed_time elapsed_time_hours, v.to_i end private def update_elapsed_time(hours, minutes) self.elapsed_time = TimeConverter.convert_to_seconds(hours: hours, minutes: minutes) end end
      
      





PS。 この記事は1年以上前に書かれたもので、単にアーカイブに収められていました。 この間、私はこのシリーズの記事が書かれたプロジェクトを投稿しました。 私はいくつかのことを別の方法で行いますが、興味深い関連ソリューションがあります。








All Articles