Note that there are some explanatory texts on larger screens.

plurals
  1. PORails validation count limit on has_many :through
    text
    copied!<p>I've got the following models: Team, Member, Assignment, Role</p> <p>The Team model has_many Members. Each Member has_many roles through assignments. Role assignments are Captain and Runner. I have also installed devise and CanCan using the Member model.</p> <p>What I need to do is limit each Team to have a max of 1 captain and 5 runners.</p> <p>I found this <a href="https://stackoverflow.com/questions/9932445/rails-accepts-nested-attributes-count-validation">example</a>, and it seemed to work after some customization, but on update ('teams/1/members/4/edit'). It doesn't work on create ('teams/1/members/new'). But my other validation (validates :role_ids, :presence => true ) does work on both update and create. Any help would be appreciated.</p> <p><strong>Update:</strong> I've found this <a href="https://stackoverflow.com/questions/1270663/validate-max-amount-af-associated-objects">example</a> that would seem to be similar to my problem but I can't seem to make it work for my app.</p> <p>It seems that the root of the problem lies with how the count (or size) is performed before and during validation. </p> <p>For Example:</p> <p>When updating a record... It checks to see how many runners there are on a team and returns a count. (i.e. 5) Then when I select a role(s) to add to the member it takes the known count from the database (i.e. 5) and adds the proposed changes (i.e. 1), and then runs the validation check. (Team.find(self.team_id).members.runner.count > 5) This works fine because it returns a value of 6 and 6 > 5 so the proposed update fails without saving and an error is given.</p> <p><strong>But</strong> when I try to create a new member on the team... It checks to see how many runners there are on a team and returns a count. (i.e. 5) Then when I select a role(s) to add to the member it takes the known count from the database (i.e. 5) and then runs the validation check <strong>WITHOUT</strong> factoring in the proposed changes. This doesn't work because it returns a value of 5 known runner and 5 = 5 so the proposed update passes and the new member and role is saved to the database with no error.</p> <p>Member Model:</p> <pre><code>class Member &lt; ActiveRecord::Base devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable attr_accessible :password, :password_confirmation, :remember_me attr_accessible :age, :email, :first_name, :last_name, :sex, :shirt_size, :team_id, :assignments_attributes, :role_ids belongs_to :team has_many :assignments, :dependent =&gt; :destroy has_many :roles, through: :assignments accepts_nested_attributes_for :assignments scope :runner, joins(:roles).where('roles.title = ?', "Runner") scope :captain, joins(:roles).where('roles.title = ?', "Captain") validate :validate_runner_count validate :validate_captain_count validates :role_ids, :presence =&gt; true def validate_runner_count if Team.find(self.team_id).members.runner.count &gt; 5 errors.add(:role_id, 'Error - Max runner limit reached') end end def validate_captain_count if Team.find(self.team_id).members.captain.count &gt; 1 errors.add(:role_id, 'Error - Max captain limit reached') end end def has_role?(role_sym) roles.any? { |r| r.title.underscore.to_sym == role_sym } end end </code></pre> <p>Member Controller:</p> <pre><code>class MembersController &lt; ApplicationController load_and_authorize_resource :team load_and_authorize_resource :member, :through =&gt; :team before_filter :get_team before_filter :initialize_check_boxes, :only =&gt; [:create, :update] def get_team @team = Team.find(params[:team_id]) end def index respond_to do |format| format.html # index.html.erb format.json { render json: @members } end end def show respond_to do |format| format.html # show.html.erb format.json { render json: @member } end end def new respond_to do |format| format.html # new.html.erb format.json { render json: @member } end end def edit end def create respond_to do |format| if @member.save format.html { redirect_to [@team, @member], notice: 'Member was successfully created.' } format.json { render json: [@team, @member], status: :created, location: [@team, @member] } else format.html { render action: "new" } format.json { render json: @member.errors, status: :unprocessable_entity } end end end def update respond_to do |format| if @member.update_attributes(params[:member]) format.html { redirect_to [@team, @member], notice: 'Member was successfully updated.' } format.json { head :no_content } else format.html { render action: "edit" } format.json { render json: @member.errors, status: :unprocessable_entity } end end end def destroy @member.destroy respond_to do |format| format.html { redirect_to team_members_url } format.json { head :no_content } end end # Allow empty checkboxes # http://railscasts.com/episodes/17-habtm-checkboxes def initialize_check_boxes params[:member][:role_ids] ||= [] end end </code></pre> <p>_Form Partial</p> <pre><code>&lt;%= form_for [@team, @member], :html =&gt; { :class =&gt; 'form-horizontal' } do |f| %&gt; #... # testing the count... &lt;ul&gt; &lt;li&gt;Captain - &lt;%= Team.find(@member.team_id).members.captain.size %&gt;&lt;/li&gt; &lt;li&gt;Runner - &lt;%= Team.find(@member.team_id).members.runner.size %&gt;&lt;/li&gt; &lt;li&gt;Driver - &lt;%= Team.find(@member.team_id).members.driver.size %&gt;&lt;/li&gt; &lt;/ul&gt; &lt;div class="control-group"&gt; &lt;div class="controls"&gt; &lt;%= f.fields_for :roles do %&gt; &lt;%= hidden_field_tag "member[role_ids][]", nil %&gt; &lt;% Role.all.each do |role| %&gt; &lt;%= check_box_tag "member[role_ids][]", role.id, @member.role_ids.include?(role.id), id: dom_id(role) %&gt; &lt;%= label_tag dom_id(role), role.title %&gt; &lt;% end %&gt; &lt;% end %&gt; &lt;/div&gt; &lt;/div&gt; #... &lt;% end %&gt; </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload