0

私は、実際のケース (スポーツ クラブ メンバーの個人データの自己管理) で Slick の持ち上げられた埋め込みアプローチを使用しようとしています。私はすでにデータベースから情報を取得し、レコードを更新することができました (以下に示すように、Member をケース クラスとして実装し、ケース クラスのコピー メソッドを使用します)。自然な方法。

私は2つの可能性を考えました:1)クラスインスタンスの不変性を維持し、一般的なセッターを実装する(コードを参照)

オプション1に固執して、次のコードを思いつきました(ソース全体ではなく抜粋):

    case class Member(id: Int, name: String, firstname: Option[String] = None,
      birthDate: Option[Date] = None, gender: Option[String] = None, country: Option[String] = None,
      healthNotes: Option[String]) {

      // Just a try so far
      def set(n: String = name, fn: Option[String] = firstname, bd : Option[Date] = birthDate)
        (implicit session: Session) = { 
        val m = this.copy(id,n,fn,bd)
        Members.update(m)
        m
      }
    }

    object Members extends Table[Member]("clubmembers") 
        with CayenneAutoPKSupport {

      def id = column[Int]("Member_ID", O.PrimaryKey) // This is the primary key column
      def name = column[String]("Name")
      def firstname = column[Option[String]]("Firstname")
      def birthDate = column[Option[Date]]("BirthDate")
      def gender = column[Option[String]]("Gender")
      def country = column[Option[String]]("Country")
      def healthNotes = column[Option[String]]("HealthNotes")

      // Every table needs a * projection with the same type as the table's type parameter
      def * = id ~ name ~ firstname ~ birthDate ~ gender ~ country ~ healthNotes <> (Member.apply _, Member.unapply _)
}

これは意図したとおりに機能しますが、 set メソッドの名前付きパラメーターに同じ名前を付けたいと思います (呼び出しをより「自然」にするもの)。私は次のことを試しました(役に立たなかった)

def set( name: String = this.name, …

これはコンパイルされず、コンパイラが満足していない理由を想像できます (OTOH 現在の実装は機能しているようです) が、機能する可能性があることも想像できます。とにかく:誰かがこれを達成する方法を見ていますか?

または、Slick で永続化されたオブジェクトの変更可能なクラス インスタンスを実装するためのベスト プラクティスとして、何をお勧めしますか?

ヒントをお寄せいただきありがとうございます。

よろしく

4

2 に答える 2

1

実際には、パラメーターとデフォルト値に同じ名前を使用すると、元のコードが機能します。理解していないように見えるのは Scala IDE の構文ハイライターだけです。名前が異なる場合、デフォルトは (正しく) クラス メンバーとして強調表示されますが、名前が同じ場合はパラメーター自体として表示されるだけです。

現在のバージョン (意図したとおりに動作しますが、構文が正しく強調表示されていません):

 def set(name: String = name, firstname: String = firstname, birthDate : Option[Date] = birthDate,
        gender: String = gender, country: String = country, 
        addressBlockId: Option[Int] = addressBlockId, healthNotes: String = healthNotes)
        (implicit session: Session) = { 
    val m = this.copy(id,name,Option(firstname),birthDate,
        Option(gender),Option(country),addressBlockId,Option(healthNotes))
    m
  }

注: 文字列パラメータは Option[String] としても渡される方がよいでしょう。

コメントや提案を歓迎します。

よろしく

于 2013-11-10T12:54:39.163 に答える
0

これがあなたが達成しようとしていたものである場合、これは私のためにコンパイルされます:

  def set(name: String = name, firstname: Option[String] = firstname, birthDate : Option[Date] = birthDate)(implicit session: Session) = {
    val m = Member(id, name, firstname, birthDate, gender, country, healthNotes)
    Members.update(m)
    m
  }
...
member.set(firstname = Some("name"))   

ケースクラス内でデータベース関数を実行することはお勧めしません。通常、ケース クラスは単純なデータ格納オブジェクトとして使用し、そのデータの管理に役立つ関数のみを定義します。

Member の更新は Member DAO クラスで行う必要があります。おそらく最も簡単な解決策は次のようなものです。

object MemberDao {
  def updateMemberDetails(member: Member, name: String, firstname: Option[String], birthdate : Option[Date]) = {
    val updatedMember = member.copy(name = name, firstname = firstname, birthDate = birthdate)
    Members.update(updatedMember)
  }
}

変更可能なクラスを処理する別の解決策は、次のようなパターンを使用することです。

case class Member(id: Int, name: String, firstname: Option[String] = None, birthDate: Option[Date] = None, gender: Option[String] = None, country: Option[String] = None, healthNotes: Option[String]) {
  def updateName(n: String) = this.copy(name = n)
  def updateFirstName(fn: Option[String]) = this.copy(firstname = fn)
  def updateBirthDate(bd: Option[Date]) = this.copy(birthDate = bd)
}

最後に、保存関数が必要な場合は、暗黙的な構文を介して挿入された流暢なスタイルの構文を実行できます。これにより、保存関数が定義された新しいメンバーが常に返されます。

case class Member(id: Int, name: String, firstname: Option[String] = None, birthDate: Option[Date] = None, gender: Option[String] = None, country: Option[String] = None, healthNotes: Option[String])

object Updatable {
  implicit class UpdatableMember(member: Member) {
    def updateName(n: String) = member.copy(name = n)
    def updateFirstName(fn: Option[String]) = member.copy(firstname = fn)
    def updateBirthDate(bd: Option[Date]) = member.copy(birthDate = bd)
    def save(implicit session: Session) = {
      ???
    }
  }
}

object MemberDao {
  def updateMember(member: Member) = {
    import Updatable._
    DB withSession { implicit session =>
      member.updateFirstName(Some("First Name")).updateBirthDate(Some(new Date(123456789))).save
    }
  }
}

これらのオプションのいくつかが役立つことを願っています。あなたの要件が何であるかを誤解している場合は、コメントしてください!

アップデート

メンバーごとに必ず 1 つの更新メソッドを用意する必要はありませんが、そこで他のロジックを実行できる可能性があります。シンプルなソリューションが必要な場合は、既に行ったように、Scala の組み込みのコピー機能を使用してください。

val member = MemberDao.getMember(123)
val updatedMember = member.copy(birthDate = Some(new Date(123456789)), name = "Updated name")
MemberDao.persist(updatedMember)

これがうまくいかない場合は、その理由を説明してください。

本質的にデータを格納するクラスに save メソッドを使用することに関する私の問題は、 Seperation of Concernsが考慮されていないことです。また、 set 関数には副作用があり、他の開発者を混乱させる可能性があります (機能的ではありません)。名のみを設定し、それを別のクラスに渡して生年月日を設定したい場合はどうすればよいでしょうか? 2 つのデータベース トランザクションを実行しますか? 私はそうではないと思います。

ここで、データ アクセス オブジェクトとその利点が発揮されます。ケース クラスのすべてのデータベース操作を 1 つのクラスに配置して、懸念事項を分離します。ケース クラスはデータを保持し、データに対する操作のみを実行する関数を持ち、DAO にはクエリ/永続化/更新/削除するメソッドがあります。ケースクラス。これにより、ケース クラスの保存関数よりも DAO をモックする方が簡単なため、アプリケーションのテストも容易になります。

多くの人が DAO について異なる意見を持っていますが、これらの記述は単なる私の意見です。

DAOを使用しない

更新可能なケース クラスのトレイトを定義することをお勧めします。

trait DatabaseAccess[T] {
  def create(implicit session: Session): T
  def update(implicit session: Session): T
  def delete(implicit session: Session): Boolean
}

case class Member(id: Int, name: String, firstname: Option[String] = None, birthDate: Option[Date] = None, gender: Option[String] = None, country: Option[String] = None, healthNotes: Option[String]) extends DatabaseAccess[Member] {
  def create(implicit session: Session): Member = ???
  def delete(implicit session: Session): Boolean = ???
  def update(implicit session: Session): Member = ???
}

これらは単なるアイデアであり、正しい解決策も間違った解決策もないと思います。あなたにとって意味があり、必要な使いやすさと柔軟性の比率を提供するものを選択してください。

ベストプラクティス

あなたは Scala のベスト プラクティスについて尋ねました。あなたの質問はソフトウェア設計の問題であるため、一般的な OO ソフトウェア設計を読むことが役に立ちます。Scala のケース クラスは、POJO を記述するためのはるかに簡単な方法です。そのため、新しい関数をケース クラスに追加する前に、「これを POJO に入れますか?」と自問してください。はいの場合は、先に進んでください:)

于 2013-11-09T23:18:45.843 に答える