2

what I want to do is to let a user endorse 3 other users a month only (and perhaps clear the limit with each new month if possible.

I've tried following this ror limiting users to 2 posts per day

With no luck. I've created the this_month method in my user model

def this_month
   where(:created_at => (Time.zone.now.beginning_of_month..Time.zone.now))
end

and in my Endorsements model:

class Endorsement < ActiveRecord::Base
  belongs_to :endorser, class_name: "User"
  belongs_to :endorsed, class_name: "User"
  validates :endorser_id, presence: true
  validates :endorsed_id, presence: true
  validates :comment, presence: true, length: { maximum: 140}
  validate :endorsement_count_within_limit?, :on => :create

  private
    def endorsement_count_within_limit?
      if endorser.endorsing.this_month.count >= 3
        errors.add(:base, "Exceeded endorse limit (3) this month")
      end
    end

end

And I get an error when trying to endorse

NoMethodError (undefined method `this_month' for #<User::ActiveRecord_Associations_CollectionProxy:0x007fe6bd957fa8>):
  app/models/endorsement.rb:11:in `endorsement_count_within_limit?'
  app/models/user.rb:113:in `endorse'
  app/controllers/endorsements_controller.rb:7:in `create'

What am I missing in here?

This is my user model

class User < ActiveRecord::Base
  has_many :active_endorsements, class_name: "Endorsement",
                                 foreign_key: "endorser_id",
                                 dependent: :destroy
  has_many :passive_endorsements, class_name:  "Endorsement",
                                  foreign_key: "endorsed_id",
                                  dependent:   :destroy
.
.
.
  has_many :endorsing, through: :active_endorsements, source: :endorsed
  has_many :endorsers, through: :passive_endorsements, source: :endorser

  attr_accessor :remember_token, :activation_token, :reset_token
  before_save :downcase_email
  before_create :create_activation_digest
  validates :name, presence: true, length: { maximum: 50}
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
.
.
.

  # Endorses a user.
  def endorse(other_user, comment)
    active_endorsements.create(endorsed_id: other_user.id, comment: comment)
  end

  # Unendorses a user.
  def unendorse(other_user)
    active_endorsements.find_by(endorsed_id: other_user.id).destroy
  end

  # Returns true if the current user is endorsing the other user.
  def endorsing?(other_user)
    endorsing.include?(other_user)
  end

  def this_month
    where(:created_at => (Time.zone.now.beginning_of_month..Time.zone.now))
  end
  private

.
.
.
end

And endorsement controller:

class EndorsementsController < ApplicationController
  before_action :logged_in_user

  def create
    @user = User.find(params[:endorsed_id])
    comment = params[:endorsement][:comment]
    current_user.endorse(@user, comment)
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end

  def destroy
    @user = Endorsement.find(params[:id]).endorsed
    current_user.unendorse(@user)
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end

end

I've cut out the unnecessary (i think) things. If needed the rest of the code (without some additions) is present at https://bitbucket.org/kramarz/pracainzynierska

Community
  • 1
  • 1
Helban
  • 81
  • 12

2 Answers2

0

it looks like endorsing is a collection not a single model, so it doesnt have that this_month method that you built on your two models

try changing to

def endorsement_count_within_limit?
  if endorser.endorsing.where(:created_at => (Time.zone.now.beginning_of_month..Time.zone.now)).count >= 3
    errors.add(:base, "Exceeded endorse limit (3) this month")
  end
end

and see if it gets rid of the issue. this might be solvable by defining it as a scope instead of a method

PhilVarg
  • 4,762
  • 2
  • 19
  • 37
  • Please check my answer below – Helban Jan 25 '16 at 19:13
  • I can confirm this solves the issue as well as does my answer below. Not sure tho which way is the "good" way to go :P But it indeed does solve it, rest of the questions remain unanswered if you could take a look at the answer below? – Helban Jan 25 '16 at 19:19
  • i'm not sure what other issues its not solving? it will reset itself when the month changes because that query will no longer grab any endorsements from the previous month – PhilVarg Jan 25 '16 at 19:22
  • Im stupid, hah! You're completely right! Thank you! Do you have an idea about how could i display the error in a nice way? I've tried a couple of ideas around, but none of them seem to work. Or should i just post a new question for that? – Helban Jan 25 '16 at 19:30
  • if youre still getting that error below, i'd say try to at least debug it first. Look through those two erb files in the stack trace and see where an argument is nil. if you can't figure it out, then make another post and ensure to include those erb files with their relative paramters – PhilVarg Jan 25 '16 at 19:36
  • I've managed to deal with it, Thank you! But got a problem with the time limits actually... I'm running the server on local host, and manually changed the date to the next month and restarted the server, and something is defo not right in here. I wanted to make sure the user can endorse each user once a month and 3 users in total a month, and when changing the month to feb, i can endorse without any limit for some strange reason. to accomplish that i've tried to change my method to check if the user endorsed already another user, to check if he did it this month... – Helban Jan 25 '16 at 21:01
  • `def endorsing?(other_user) endorsing.where(:created_at => (Time.zone.now.beginning_of_month..Time.zone.now)).include?(other_user) end` and the form rendering the button is using that function. `<% if current_user.endorsing?(@user) %>` in the server logs tho i can see: `BETWEEN '2016-02-01 00:00:00.000000' AND '2016-02-25 17:50:27.605859')` and then BETWEEN '2016-02-01 00:00:00.000000' AND '2016-02-25 17:50:27.608963')` and after that it renders the endorse form instead of unendorse, not sure if that matters. – Helban Jan 25 '16 at 21:04
0

I actually found a reason.

It should look like this in the user model:

has_many :endorsing, through: :active_endorsements, source: :endorsed do
    def this_month
      where(:created_at => (Time.zone.now.beginning_of_month..Time.zone.now))
    end
  end

I was missing the do..end at the other post and placed the method at the end of the model. It seems to work now, produces an error

ArgumentError (First argument in form cannot contain nil or be empty):
  app/views/shared/_unendorse.html.erb:1:in `_app_views_shared__unendorse_html_erb__1506472371249770260_70025812987440'
  app/views/endorsements/create.js.erb:1:in `_app_views_endorsements_create_js_erb__2132303159922640595_70025812908380'
  app/controllers/endorsements_controller.rb:8:in `create'

In the server log. I belive thats a valid error (already got 3 endorsed users) - but how can i display it on the website? So it looks like in the method

"Exceeded endorse limit (3) this month"

instead of what the error produces?

Edit:

I've managed to solve the error displaying correctly :)

It boiled down to have this method in my endorsement model:

  def endorsement_count_within_limit?
    if endorser.endorsing.where(:created_at => (Time.zone.now.beginning_of_month..Time.zone.now)).count >= 3
      errors.add(:base, "Exceeded endorse limit (3) this month")
    end
  end

and having my endorse partial that render the correct form depending if user is endorsing other user or not:

<%= form_for(current_user.active_endorsements.build, remote: true) do |f| %>
  <% if f.object.endorsement_count_within_limit? %>
    <div id="error_explanation">
      <div class="alert alert-danger">
        The form contains <%= pluralize(f.object.errors.count, "error") %>.
      </div>
      <ul>
      <% f.object.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% else %>
    <div><%= hidden_field_tag :endorsed_id, @user.id %></div>
    <%= f.submit "Endorse", class: "btn btn-primary" %>
    <%= f.text_field :comment, class: 'form-control' %>
  <% end %>
<% end %>
Helban
  • 81
  • 12