before validation do something, no method error - ruby-on-rails

I have an model for notifications.
An notification can be posted by an User or an Contact.
and the notification can go to either a business or a notification_area.
people has to be filled, so when there is no user added it has to fill in the Contact that is logged in.
the notification_to has to be filled to so when there is no business added it has to take the latitude and the longitude and add the right area.
I have written the code but it won't work.
First I added it in the controller. But after looking around on google and this site I found I had to add it to the model.
But it still won't work.
What do I do wrong?
I get an error
class Notification < ActiveRecord::Base
belongs_to :people, :polymorphic => true
belongs_to :notification_to, :polymorphic => true
belongs_to :App_of_area
belongs_to :kind
before_validation :if_empty_add_the_other
#validates :photo, presence: true
validates :message, :status, :people_id, :people_type, :notification_to_id, :notification_to_type, :App_of_area_id, :kinds_id, presence: true
def if_empty_add_the_other
unless self.people_type.present?
self.people = current_contact
end
unless self.notification_to_id.present?
if self.longitude && self.latitude
#noa = NotificationArea.where( "latitude_A <= #{self.latitude} AND latitude_B >= #{self.latitude} AND longitude_A <= #{self.longitude} AND longitude_B >= #{self.longitude}")
self.notification_to = #noa.first
end
end
end
end
end

First thing first you should DRY up your code:
belongs_to :business
belongs_to :app_of_area
has_many :people, source_type: "Notification"
has_many :api_keys
before_validation :if_empty_add_the_other
has_secure_password
validates :name, :email, :rights, :password_confirmation, :app_of_area_id, :business_id, presence: true
validates_format_of :email, :with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
validates :email, uniqueness: true
validates :password, confirmation: true, length: { minimum: 6 }
:as is used for polymorphic associations, I believe you want to use :source_type as shown above. This would allow you to perform self.people << current_contact, but I address this more below.
Why was :App_of_area capitalized? Same with :App_of_area_id?
Your if_empty_add_the_other validation method has a lot wrong with it.
Use unless rather than if not.
Can the two if statements testing latitude and longitude be combined to if self.longitude && self.latitude?
You have to ask yourself, how is current_contact being passed to this function? Also, you're trying to set self.people equal to this phantom current_contact; self.people would contain multiple records, an array if you will, so setting an array equal to an object won't work, hence the self.people << current_contact above.

Related

validating attribute presence with condition

I am trying to introduce a functionality in my application in which the users can suggest an event to the organizers.To suggest an event the user will have to fill a form. I want to validate the presence of few fields(the attribute of the events) with a condition to be true, i.e. the fields should not be left blank if the user(the submitter of the form) is not an admin.However if the submitter of the form is an admin, the fields can be left blank.
validates :attribute_name, presence: { :if => :user.is_admin? }
undefined method `is_admin?' for :user:Symbol Did you mean? is_haml?
I have also tried :user.is_admin()==True , It also throws error:
undefined method `is_admin' for :user:Symbol Did you mean? is_a?
I have this attribute in the users table:
t.boolean "is_admin", default: false
I have the following associations defined in my events model:
has_many :event_users, dependent: :destroy
has_many :users, through: :event_users
has_one :submitter_event_user, -> { where(event_role: 'submitter') }, class_name: 'EventUser'
has_one :submitter, through: :submitter_event_user, source: :user
In the controllers I have this code:
#event.submitter = current_user
The issue is you are trying to call is_admin? on the Symbol :user as the error suggests.
If I understand correctly this attribute should be present unless user.is_admin? returns true
This can be done in multiple ways:
validates :attribute_name, presence: true, unless: -> {|u| u.is_admin?}
#Or
validates :attribute_name, presence: true, unless: :is_admin?
# or using presence directly as you tried to originally
validates :attribute_name, presence: {unless: -> {|u| u.is_admin?} }
# or
validates :attribute_name, presence: {unless: :is_admin? }
I generally prefer the first option as, IMO, it is the least ambiguous and the most readable but all of them should result in the same function so choose the one you prefer as long as you remain consistent.
In the block form the instance is yielded to the block and the block's return value is used to determine whether or not the validation should run.
When using the symbol the symbol is sent to the instance via the send message transmission e.g. self.send(:is_admin? and again the return value is used to determine if the validation should be applied
ActiveModel::Validations::ClassMethods#validates
Update based on revised question:
Since the Event is related to the User via submitter and this is already being set to the instance of a User you can validate in a very similar fashion via
validates :attribute_name, presence: true,
unless: ->(event) { event.submitter&.is_admin?}
Or make a separate method such as
def admin_submitter?
self.submitter&.is_admin?
end
validates :attribute_name, presence: {unless: :admin_submitter?}

Rails - new User builds dependent Email and validates both?

I'm building a quick Rails project that allows users to manage their email addresses. Users can have many emails, but one (and only one) of those emails has to be marked as 'primary' (for login), and a user cannot exist without a primary email.
I've been struggling to get this to work right - it seems so circular to me. I need to build a User, and then the Email, but I don't want to save the User into the database unless the Email is valid, which it won't be until the User is saved (because of the validates :user, presence: true constraint).
Accepts nested resources for doesn't seem to work with .new (works fine with .create), and if my Email fails its validations, the User still shows as valid.
Been having a difficult time trying to find good resources (or SO questions) for building/validating multiple/dependent models from a single form.
What's the most Rails way to do this?
User
has_many :emails
has_one :primary_email, -> { where(primary: true) }, class_name: "Email"
accepts_nested_attributes_for :primary_email
validates :first_name, presence: true
validates :last_name, presence: true
validates :birthday, presence: true
validates :password_digest, presence: true
Email
belongs_to :user
validates :user, presence: true
validates :address, presence: true, uniqueness: {
case_sensitive: false
}
UsersController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
# do something
else
# show #user.errors
end
end
private
def user_params
params.require(:user).permit(
:first_name,
:last_name,
:birthday,
:password,
:password_confirmation,
:primary_email_attributes => [:address]
)
end
EDIT
The Email model also contains the following fields:
label = string, eg. 'Personal', 'Work', etc
primary = boolean, whether it's marked as primary email or not
confirmation_code = autogenerated on creation, used to confirm ownership
confirmed = boolean, whether it's been confirmed or not
class User
user has_many :emails
user has_one :primary_email, -> { where(primary: true) }, class_name: "Email", autosave: true
after_initialize {
build_primary_email if new_record?
}
end
class Email
# use gem https://github.com/balexand/email_validator
validates :my_email_attribute, :email => true
end
So after a user initialized its building a primary_email so that record is already associated, or at least it will be if it can be saved. the autosave is working pretty cool - if the primary-email can't be saved due validation error, the user can't neither. should work out of the box, im in a bus right now, can't check it. cheers
futher information: http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html
If validations for any of the associations fail, their error messages will be applied to the parent. That means, the Parent Model (in your case User) is having errors, and thats why the saving is not possible! that's what you are looking for.
I would store a primary email as a common field and additional emails some another way. I would prefer to store additional emails in another field too that is Array rather than in an associated table. You shouldn't store a primary email in another table. Just imagine, every time you need authorize user or just get his email you will perform an extra request to db.
Meant to post this months ago.
The solution, keeping users and emails normalized across different models without storing a primary email as an attribute on the user, is to use inverse_of:
User.rb
class User < ActiveRecord::Base
has_many :emails, inverse_of: :user, dependent: :destroy
accepts_nested_attributes_for :emails
validates :emails, presence: true
end
Email.rb
class Email < ActiveRecord::Base
belongs_to :user, inverse_of: :emails
validates :user, presence: true
end
This allows validations to be performed using in-memory objects, rather than via database calls (ie the object associations are being validated, rather than the presence of an id/record in the database). Therefore they both pass validation and can both be saved in the same transaction.
See: https://viget.com/extend/exploring-the-inverse-of-option-on-rails-model-associations

Rails: Looking up foreign key id using virtual attribute failing

My app allows users to add words from a master words table into their own custom list. So, a word list contains multiple custom words each of which link to a master word.
In my view, I have a field called word_text (virtual attribute) where I let users enter a word, and in my model I am trying to look up the master_word_id and set it on the custom word table. I am unable to access the #word_text value in the model. I always seem to get an error that the master word is a required field (because the look up is failing).
class CustomWord < ActiveRecord::Base
attr_accessible :master_word_id, :word_list_id, :word_text
attr_accessor :word_text
belongs_to :word_list
belongs_to :master_word
validates :word_list, presence: true
validates :master_word, presence: true
before_validation :set_master_word
private
def set_master_word
logger.debug "Received word text #{#word_text}"
_mw_id = nil
if !#word_text.nil?
master_word = MasterWord.find_word(#word_text)
if master_word.nil?
errors.add("#{#word_text} is not a valid word")
else
_mw_id = master_word.id
end
end
self.master_word_id = _mw_id
end
end
I sincerely appreciate any suggestions as to how I can set the value of the master_word_id correctly.
There are several things to fix:
class CustomWord < ActiveRecord::Base
attr_accessible :master_word_id, :word_list_id, :word_text
attr_accessor :word_text
belongs_to :word_list
belongs_to :master_word
validates :word_list, presence: true
#validates :master_word, presence: true <= This will always throw error
validates :word_text, presence: true
validates :master_word_id, presence: true
before_validation :set_master_word
private
def set_master_word
logger.debug "Received word text #{self.word_text}"
self.master_word_id = MasterWord.find_by_word_text(self.word_text).id
end
end
Not sure if it will work because I don't know the rest of your app but I hope it points you in the right direction.

Validate whether foreign key exists if not nil

I have a many-to-one relationship defined in Rails 4:
class Event < ActiveRecord::Base
belongs_to :user
end
How do I check, that the :user key exists if it is set?
The following correctly checks, that the key exists but does not allow for `nil? values:
validates :user, presence: true
The following allows any value, even non-existing IDs:
validates :user, presence: true, allow_nil: true
How do you do such kind of validation.
You can use the before_save callback to check that the user is valid if it is supplied:
class Event < ActiveRecord::Base
belongs_to :user
before_save: :validate_user unless: Proc.new { |event| event.user.nil? }
private
def validate_user
if User.where(id: user_id).empty?
errors[:user] << "must be valid"
end
end
end
You must keep the presence validation in Event and add in User model:
has_many :events, inverse_of: :user
Just faced this problem and here are the 2 solutions
1.
validates :user, presence: true, if: 'user_id.present?'
2.
. https://github.com/perfectline/validates_existence gem
can be used like this
validates : user, existence: { allow_nil: true, both: false }
both option is for the error messages: false will show 1 error(association only), true will show 2 errors (association and foreign key)

Model has_many OtherModel but localized, so different OtherModel for same Model based on locale: how?

I'm having hard time with this, it's not a direct problem of implementation but I don't understand which is the right way to do it, I have two options, but first, these are my models:
class Boat < ActiveRecord::Base
validates :name, presence: true, uniqueness: { case_sensitive: false }
has_many :tech_specs, order: 'position'
def visible?
self.visible
end
end
class TechSpec < ActiveRecord::Base
validates :boat_id, presence: true
validates :tech_spec_name_id, presence: true, uniqueness: { scope: :boat_id }
belongs_to :boat
belongs_to :tech_spec_name
before_destroy :destroy_name_if_required
acts_as_list scope: :boat
def as_json(options = {})
super(options.except!(:tech_spec_name_id).merge!(methods: [self.name]))
end
def name
self.tech_spec_name.try(:name) || ''
end
def name=(value)
self.tech_spec_name = TechSpecName.find_or_create_by_name(value)
end
def destroy_name_if_required
self.tech_spec_name.destroy if self.tech_spec_name.tech_specs.size <= 1
end
end
class TechSpecName < ActiveRecord::Base
validates :name, presence: true, uniqueness: { case_sensitive: false }
has_many :tech_specs
def self.with_name_like(str)
where('lower(name) LIKE lower(?)', "%#{ str }%")
end
end
The problem is that I want a page for a boat showing some tech specs when with a locale and when on a different locale, showing other tech specs.
Idea #1
My basic idea is to add to TechSpec globalize3 on tech_spec.value and on TechSpecName for field tech_spec_name.name
Idea #2
The other idea is to remove TechSpecName and, instead, use a field (tech_spec.name) that will "replace" completely TechSpecName. Notice that in this case, I'll still need to fetch names for autocomplete, but I will filter them in TechSpec instead of fetching all from TechSpecName. This field will use globalize3 again obviusly.
I don't know the downside of both approaches, so I need a suggestion.
Seems like idea #1 is ok, it works correctly and reduce the amount of repeated text inside Db.
I18n.with_locale helps a lot too, also Globalize.with_locale is helpful

Resources