Make query with association and search by custom model method rails - ruby-on-rails

I have this problem, i tried to get all Beauty Salons when the service has a promotion, but service has a custom method and return true or false, this is the structure of my code
class BeautySalon < ActiveRecord::Base
has_many :services
end
class Service < ActiveRecord::Base
belongs_to :beauty_salon
has_many :service_promotions
has_many :promotions, through: :service_promotions
def has_promotion?
## consult another tables and return true or false if found a promotion
end
end
iam tried to make the query like this
BeautySalon.all.includes(:services).select('services.*').select{|service| service.has_promotion?}
but rails return this error
NoMethodError (undefined method `has_promotion?' for #BeautySalon:0x0055a1119d1f40)
Any advice for this?
UPDATE
the method has_promotion do this
def has_promotion?
if promotions.exists?
if get_promotions(Date.today).exists?
return true
else
return false
end
end
return false
end
def get_promotions(date)
if promotions.exists?
promotions.where('start_date <= ? and end_date >= ?',date,date)
end
end
and another tables are there
class Promotion < ActiveRecord::Base
validates :discount, presence: true
validates :start_date, presence: true
validates :end_date, presence: true
has_many :service_promotions
has_many :services, through: :service_promotions
end
class ServicePromotion < ActiveRecord::Base
validates :service_id, presence:true
validates :promotion_id, presence:true
belongs_to :service
belongs_to :promotion
end
thanks for all the advises

BeautySalon.joins(:services).select { |beauty_salon| beauty_salon.services.any?(&:has_promotion?) }
This is an option. You are now calling has_promotion? on the wrong object (e.g. beauty salon instead of service) and the error is raised because the instance method is defined in Service.rb.
It would be better in my opinion to add a database column has_promotion (boolean) to your services table.
BeautySalon.joins(:services).where(has_promotion: true)
If has_promotion? simply retrieves data from another associated model (other_association, with db boolean column promotion) you can also do something like this:
BeautySalon.joins(services: other_association).where(other_association: { promotion: true })
Update:
BeautySalon.joins(services: :promotions).where("promotions.start_date <= :date AND promotions.end_date >= :date", date: Date.current )
This will return all beauty salons with running service promotions (today).

Related

Rails 5 Model.where(user_id) - Two levels up

Terribly worded, but I'm confusing it.
I have a User model who has_many Clients and has_many statements, through: :clients and then statements which belongs_to clients and belongs to user
In Console I can do all the queries I want. User.statements User.client.first.statements etc - What I'm struggling on is Controller restrictions
For now it's simple - A user should only be able to see Clients and Statements in which they own.
For Clients I did
Client Controller
def index
#clients = Client.where(user_id: current_user.id)
end
Which seems to work perfectly. Client has a field for user_id
I'm kind of stuck on how to emulate this for Statements. Statements do -not- have a user_id field. I'm not quite sure I want them too since in the very-soon-future I want clients to belongs_to_many :users and Statements to not be bound.
Statement Controller
def index
#clients = Client.where(user_id: current_user.id)
#statements = Statement.where(params[:client_id])
end
I'm just genuinely not sure what to put - I know the params[:client_id] doesn't make sense, but what is the proper way to fulfill this? Am I going about it an unsecure way?
Client Model
class Client < ApplicationRecord
has_many :statements
has_many :client_notes, inverse_of: :client
belongs_to :user
validates :name, presence: true
validates :status, presence: true
accepts_nested_attributes_for :client_notes, reject_if: :all_blank, allow_destroy: true
end
Statement Model
class Statement < ApplicationRecord
belongs_to :client
belongs_to :user
validates :name, presence: true
validates :statement_type, presence: true
validates :client_id, presence: true
validates :start_date, presence: true
validates :end_date, presence: true
end
User Model
class User < ApplicationRecord
has_many :clients
has_many :statements, through: :clients
end
Based on the reply provided below I am using
def index
if params[:client][:user_id] == #current_user.id
#clients = Client.includes(:statements).where(user_id: params[:client][:user_id])
#statements = #clients.statements
else
return 'error'
end
end
Unsure if this logic is proper
Use includes to avoid [N+1] queries.
And regarding "A user should only be able to see Clients and Statements in which they own".
if params[:client][:user_id] == #current_user.id
#clients = Client.includes(:statements).where(user_id: params[:client][:user_id])
# do more
else
# Type your error message
end
Additionally, you might need to use strong params and scope.
The best way to do it is using includes:
#clients = Client.where(user_id: current_user.id)
#statements = Statement.includes(clients: :users}).where('users.id = ?', current_user.id)
You can take a look in here: https://apidock.com/rails/ActiveRecord/QueryMethods/includes
In this case, thanks to the reminder that current_user is a helper from Devise, and the relational structure I showed, it was actually just as simple as
def index
#statements = current_user.statements
end
resolved my issue.
Due to the [N+1] Queries issue that #BigB has brought to my attention, while this method works, I wouldn't suggest it for a sizable transaction.

Prevent from raising ActiveRecord::RecordInvalid or adding twice on has_many association

I want to change has_many association behaviour
considering this basic data model
class Skill < ActiveRecord::Base
has_many :users, through: :skills_users
has_many :skills_users
end
class User < ActiveRecord::Base
has_many :skills, through: :skills_users, validate: true
has_many :skills_users
end
class SkillsUser < ActiveRecord::Base
belongs_to :user
belongs_to :skill
validates :user, :skill, presence: true
end
For adding a new skill we can easily do that :
john = User.create(name: 'John Doe')
tidy = Skill.create(name: 'Tidy')
john.skills << tidy
but if you do this twice we obtain a duplicate skill for this user
An possibility to prevent that is to check before adding
john.skills << tidy unless john.skills.include?(tidy)
But this is quite mean...
We can as well change ActiveRecord::Associations::CollectionProxy#<< behaviour like
module InvalidModelIgnoredSilently
def <<(*records)
super(records.to_a.keep_if { |r| !!include?(r) })
end
end
ActiveRecord::Associations::CollectionProxy.send :prepend, InvalidModelIgnoredSilently
to force CollectionProxy to ignore transparently adding duplicate records.
But I'm not happy with that.
We can add a validation on extra validation on SkillsUser
class SkillsUser < ActiveRecord::Base
belongs_to :user
belongs_to :skill
validates :user, :skill, presence: true
validates :user, uniqueness: { scope: :skill }
end
but in this case adding twice will raise up ActiveRecord::RecordInvalid and again we have to check before adding
or make a uglier hack on CollectionProxy
module InvalidModelIgnoredSilently
def <<(*records)
super(valid_records(records))
end
private
def valid_records(records)
records.with_object([]).each do |record, _valid_records|
begin
proxy_association.dup.concat(record)
_valid_records << record
rescue ActiveRecord::RecordInvalid
end
end
end
end
ActiveRecord::Associations::CollectionProxy.send :prepend, InvalidModelIgnoredSilently
But I'm still not happy with that.
To me the ideal and maybe missing methods on CollectionProxy are :
john.skills.push(tidy)
=> false
and
john.skills.push!(tidy)
=> ActiveRecord::RecordInvalid
Any idea how I can do that nicely?
-- EDIT --
A way I found to avoid throwing Exception is throwing an Exception!
class User < ActiveRecord::Base
has_many :skills, through: :skills_users, before_add: :check_presence
has_many :skills_users
private
def check_presence(skill)
raise ActiveRecord::Rollback if skills.include?(skill)
end
end
Isn't based on validations, neither a generic solution, but can help...
Perhaps i'm not understanding the problem but here is what I'd do:
Add a constraint on the DB level to make sure the data is clean, no matter how things are implemented
Make sure that skill is not added multiple times (on the client)
Can you show me the migration that created your SkillsUser table.
the better if you show me the indexes of SkillsUser table that you have.
i usually use has_and_belongs_to_many instead of has_many - through.
try to add this migration
$ rails g migration add_id_to_skills_users id:primary_key
# change the has_many - through TO has_and_belongs_to_many
no need for validations if you have double index "skills_users".
hope it helps you.

Ruby on Rails - Get polymorphic_id before_save

I have a two models:
Polymorphic
class Custo < ActiveRecord::Base
belongs_to :custavel, polymorphic: true
accepts_nested_attributes_for :custavel, allow_destroy: true
validates_presence_of :numero, :serie
validates_uniqueness_of :numero, scope: :serie
end
Another class
class Pagamento < ActiveRecord::Base
has_one :custo, as: :custavel
end
In general numero and serie are filled by user input, but only when the polymorphic is a Pagamento i need to fill the column numero with custavel_id.
Then, i tried it:
before_save :set_numero
def set_numero
return unless custavel_type == 'Pagamento'
write_attribute(:numero, custavel_id)
end
But it fails on validation because custavel_id doesn't exists yet. How to do it?
Modify the callback method
before_save :set_numero
def set_numero
write_attribute(:numero, custavel_id) if custavel_type == 'Pagamento'
end

Add new record to a new record

I'm using rails 4.1.6
I have a problem creating new records and then saving them. Here are the models:
class Function < ActiveRecord::Base
belongs_to :theater
has_many :showtimes, dependent: :destroy
belongs_to :parsed_show
validates :theater, presence: :true
validates :date, presence: :true
end
class Theater < ActiveRecord::Base
has_many :functions, :dependent => :destroy
validates :name, :presence => :true
accepts_nested_attributes_for :functions
end
class Showtime < ActiveRecord::Base
belongs_to :function
validates :time, presence: true
validates :function, presence: true
end
showtime = Showtime.new time: Time.current
theater = Theater.first # read a Theater from the database
function = Function.new theater: theater, date: Date.current
function.showtimes << showtime
function.showtimes.count # => 0
why is the showtime not being added to the function's showtimes? I need to save the function later with the showtimes with it.
Your Function object hasn't been persisted. You need to make sure that it's persisted (which of course also requires that it be valid) before you can add things to its list of showtimes.
Try saving the function beorehand, and if it succeeds (that is if function.persisted? is true), it should allow you to << directly into function.showtimes, as you like. Alternatively, you can use the Function#create class method instead of the Function#new class method, since the former automatically persists the record.
You can use Function#create
theater = Theater.first # read a Theater from the database
function = Function.new theater: theater, date: Date.current
function.showtimes.create(time: Time.current)
I forgot to check if saving the function also saved the theaters, and even though function.showtimes.count returns 0, the showtimes are saved to the database anyway.
function.showtimes.count returns:
=> 0
but
function.showtimes returns:
=> #<ActiveRecord::Associations::CollectionProxy [#<Showtime id: nil, time: "2015-01-09 04:46:50", function_id: nil>]>

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)

Resources