Rails How make a conditional inside a method on the model - ruby-on-rails

I'm trying to make that just the user can feedback on a feedback method in the feedback model. but in the logs shows
NoMethodError (undefined method `total_feedbacks' for nil:NilClass)
so show to make a conditional in the model?
def feedback_product
user = User.find_by_id(attributes['user_id'])
if user
product.total_feedbacks += 1
product.average_rating = product.feedbacks.where('buyer_feedback_date IS NOT NULL').rated(Feedback::FROM_BUYERS).average(:buyer_rating)
product.save
end
end
the feedback
belongs_to :user
and user
has_many :feedbacks

Your question is not totally clear. You didn't mention in which model this code is inside and your condition seems okay.
It seems product is nil, so maybe you are missing something that was not mentioned in the question, like instantiating that product. Or maybe you want your condition to be:
if user && product

The problem is that this method assumes there is an existing product for the model in question. However if no record has been created at, then product will be nil.
This can be addressed in a few ways, which you use depends on your use-case.
making sure that this model creates a product upon save, e.g. through before_save:
before_save :ensure_product_exists
def ensure_product_exists
product ||= Product.create(foo_id: self.id) # replace with correct foreign key
end
making this method create the product if it does not yet exist, e.g.
product ||= Product.new(foo_id: self.id) # replace with correct foreign key
making the method return early if the product does not exist, e.g.
return unless product
raising an error unless the product exists
raise(RuntimeError, "product doesn't exist") unless product

Related

ActiveRecord uniqueness validation prevents update

I'm writing a web app using Rails, part of which includes giving users the ability to leave reviews for things. I wanted to put a validation in the review model to ensure that one user can't leave multiple reviews of the same item, so I wrote this:
class NoDuplicateReviewValidator < ActiveModel::Validator
def validate(record)
dup_reviews = Review.where({user_id: record.user,
work_id: record.work})
unless dup_reviews.length < 1
record.errors[:duplicate] << "No duplicate reviews!"
end
end
end
This validator has the desired behavior, i.e. it guarantees that a user can't review a work twice. However, it has the undesired side-effect that a user can't update an already existing review that he/she left. I'm using a really simple
def update
#review.update(review_params)
respond_with(#work)
end
in the reviews controller. How can I change either the validator or the update method so that duplicate reviews are prevented but updates are allowed?
I'm very new to Rails and web development, so I'm sure I've done something goofy here. I didn't use one of the built-in unique validators because what is unique is the user/work pair; there can more than one review by the same user of different works, and there can be more than one review of the same work by different users.
You can use validates_uniqueness_of on multiple attributes, like this:
validates_uniqueness_of :user_id, :scope => :work_id
Then a user would not be allowed to review a already reviewed work.
#Sharvy Ahmed's answer is definitely the best, as long as the case is simple enough – the OP's case seems like one of them.
However, if the conditions are more complex, you may need/want to write your custom validation. For that purpose, here's an example (checked with Rails 6.0).
class NoDuplicateReviewValidator < ActiveModel::Validator
def validate(record)
dup_reviews = Review.where(user_id: record.user,
work_id: record.work)
dup_reviews = dup_reviews.where.not(id: record.id) unless record.new_record?
if dup_reviews.count > 0
record.errors[:duplicate] << "No duplicate reviews!"
end
end
end
The idea is,
In create, all the relevant DB records retrieved with where can and should be used to judge the uniqueness. In the example new_record? is used to check it out, but it is actually redundant (because nil id matches no records).
In update, the DB row of the record to update must be excluded from the unique comparison. Otherwise, the update would always fail in the validation.
The count method is slightly more efficient in terms of DB transaction.

Updating an ActiveAdmin nested resource using inputs not located on the table

I currently have a form for a nested resource in one of my activeadmin pages:
f.inputs "Courses" do
f.has_many :registrations, :allow_destroy => true, new_record: true do |tc|
tc.input :course
tc.input :semester
end
end
This is for a student resource. A student has many course_offerings through registrations. However, I didn't want users to select the course_offering directly from a menu (Since there will be many repeats of the same course each year). Instead there is an input for course and semester. course and semester are instance variables on the registration model. They are set in the form, then the correct course_offering is found in an after save hook and associated with the registration. The code is as follows:
def semester=(s)
#semester = s.to_i
end
def semester
self.course_offering.semester
end
def course=(co)
#course = co.to_i
end
def course
self.course_offering.course
end
before_save :set_course_offering
def set_course_offering
self.course_offering = CourseOffering.where(semester_id: #semester, course_id: #course).first
#TODO: Handle case where no course offering is found
end
I am having two problems. The first is that I get a nil pointer error when registrations table is empty.
undefined method `course' for nil:NilClass
I have accepts_nested_attributes call in my student model.
accepts_nested_attributes_for :registrations, :allow_destroy => true
which is the only suggestion I get when looking up the error but still get it despite having that piece of code. It seems to work fine when I remove the course and semester and replace it with a course_offering instead.
The next problem I have is that the student record does not save after hitting update. I assume this is because I don't make any changes that write to the database when I only update the two course and semester instance variables only. I either need to update another input or add the call to the semester= method.
You get an undefined method error because when the registration table is empty there are no course offerings associated with your student, so in the accessor method for course you get nil for self.course_offerings. You could try this instead which takes the nil value into account:
def course
self.course_offering.try(:course)
end
You don't need the accept_nested_attributes_for because you don't want to create or modify course offerings through students.
For your second problem: you're right about the dirty tracking. Your model is not saved because your student model has not been modified from the perspective of ActiveRecord. You need to flag an attribute (eg course_offering) as dirty by hand with the course_attribute_will_change! method before saving the model.
Although this situation looks like a good example to introduce form objects. There is a great library for that called reform.

how to run a one-time database change on a single user

I have Customer and each customer has_many Properties. Customers belong to a Company.
I'm trying to add a certain Property to each one of a single Company's Customers. I only want this change to happen once.
I'm thinking about using a migration but it doesn't seem right to create a migration for a change that I only ever want to happen once, and only on one of my users.
Is there a right way to do this?
You can just use rails console.
In rails c:
Company.where(conditions).last.customers.each do |customer|
customer.properties << Property.where(condition)
customer.save!
end
Validation
Depending on how you're changing the Customer model, I'd include a simple vaidation on the before_update callback to see if the attribute is populated or not:
#app/models/Customer.rb
class Customer < ActiveRecord::Base
before_update :is_valid?
private
def is_valid?
return if self.attribute.present?
end
end
This will basically check if the model has the attribute populated. If it does, it means you'll then be able to update it, else it will break
--
Strong_Params
An alternative will be to set the strong_params so that the attribute you want to remain constant will not be changed when you update / create the element:
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
...
private
def strong_params
params.require(:model).permit(:only, :attributes, :to, :update)
end
end
It would be much more helpful if you explained the context as to why you need this type of functionality - that will give people the ability to create a real solution, instead of proposing ideas

Maintain associations with act_as_paranoid

I have two models one of them is a User, and another Comments. Comments belong to User.
class User < ActiveRecord::Base
act_as_paranoid
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :user
end
When I do user.delete in my controller I get the expected result of the deleted_at column being set and the record being hidden.
My problem is the Comment associations for the user are set to null. So now on the site it shows no user owning the comment. I'd like the comment to still show the user's name not be "None" or "Anonymous" etc.
Looking at the source on github https://github.com/goncalossilva/rails3_acts_as_paranoid/blob/rails3.2/lib/acts_as_paranoid/core.rb it seems to call run_callbacks which in turn results in Rails 3 falling back to Nullify default for associations.
In my case I just want the user account to be closed off when deleted. Not showing up in queries anymore so that Authlogic will deny them and the User index page won't show them. But still allowing everything a user owns to still be owned by them (since they may come back, etc.).
Is there a better way to do this then acts_as_paranoid?
Rather then go to the trouble of overriding the destroy method I created a close method that simply sets closed_at to a timestamp. If you set default scope to something like:
default_scope { where("closed_at IS NULL") }
Then the model won't show up to any queries including User.All. You can delete the scope to get a full query essentially I took these ideas from act_as_paranoid but much more simplified. The problem is that then even though the Comments still have user_id set, the default scope runs with any association load. So say
c = Comment.first
c.user
That will output nil if user_id is a closed account. In my case the easiest solusion was to remove default scoping and modify my Authlogic function to:
def self.find_by_username_or_email(login)
u = User.find(:first, :conditions => ["lower(username) = ?", login.downcase]) || User.find_by_email(login)
return u unless u.closed_at
end
This way closed accounts can't login. Anywhere I list out users in my views I used a hide_closed scope.
Not sure if this was the best most elegant solution. But for my purposes it works.

What is the best way of preventing the last record in a has_many collection being removed?

I have two ActiveRecord classes. A simplified view of these classes:
class Account < ActiveRecord::Base
has_many :user_account_roles
end
class UserAccountRole < ActiveRecord::Base
belongs_to :account
# Has a boolean attribute called 'administrator'.
end
What I'm struggling with is that I'd like to be able to apply two validation rules to this:
* Ensuring that the last UserAccountRole cannot be removed.
* Ensuring that the last UserAccountRole that is an administrator cannot be removed.
I'm really struggling to understand the best way of achieving this kind of structural validation. I've tried adding a before_remove callback to the association, but I don't like that this has to throw an error which would need to be caught by the controller. I'd rather this be treated as 'just another validation'.
class Account < ActiveRecord::Base
has_many :user_account_roles, :before_remove => check_remove_role_ok
def check_remove_relationship_ok(relationship)
if self.user_account_relationships.size == 1
errors[:base] << "Cannot remove the last user from this account."
raise RuntimeError, "Cannot remove the last user from this account."
end
end
end
I don't think this makes any difference, but I'm also using accepts_nested_attributes_for.
Why not use a simple validation on Account?
class Account < ActiveRecord::Base
has_many :user_account_roles
validate :at_least_one_user_account_role
validate :at_least_one_administrator_role
private
def at_least_one_user_account_role
if user_account_roles.size < 1
errors.add_to_base('At least one role must be assigned.')
end
end
def at_least_one_administrator_role
if user_account_roles.none?(&:administrator?)
errors.add_to_base('At least one administrator role must be assigned.')
end
end
end
This way nothing needs to be raised, and the record won't be saved unless there's at least one role, and at least one administrator role. Thus when you re-render your edit form on error, this message will show up.
You could place the validation on UserAccountRole. If it is the only UserAccountRole associated with the Account, then it can't be deleted.
An easier solution may be to question an underlying assumption of your design. Why have UserAccountRole be an AR backed model? Why not just make it a plain ruby class? Is the end user going to dynamically define roles? If not, then you could greatly simplify your dilemma by making it a regular ruby class.

Resources