Rails 4 scope has_many - ruby-on-rails

I have two models
class Portfolio < ActiveRecord::Base
has_many :project_types, dependent: :destroy
end
class ProjectType < ActiveRecord::Base
belongs_to :portfolio
end
ProjectType model has field ptype. It can be 'web' or 'mobile', etc.
How can I get all 'web' or 'mobile' portfolios using scopes?

Add an appropriate scope to your model:
# in models/project_type.rb
class ProjectType < ActiveRecord::Base
belongs_to :portfolio
scope :web, -> { where(ptype: 'web') }
end
To load portfolios with type web only use that scope with a join in the controller:
# in the controller
#web_portfolios = Portfolio.joins(:project_types).merge(ProjectType.web)

You can have explicit scopes for both, or you may have one scope that selects for you based on ptype, or you may metaprogram a scope for each unique ptype in the database. If you are adding a variety of different "ptypes" you can do any of the following which will give you a scope for handling any "ptypes":
scope :ptype, -> (ptype) { where(ptype: ptype) }
called as:
ProjectType.ptype('web')
or
class ProjectType < ActiveRecord::Base
self.pluck(:ptype).each do |ptype|
scope ptype.gsub(/\s+/,"_").downcase.to_sym, -> { where(ptype: ptype) }
end
end
I don't recommend this and I also don't recommend individual scopes for each "string" (e.g. ProjectType.web or ProjectType.mobile) in there. The best balance is to pass a string value into a scope which retrieves what you are looking for. Just my opinion, I'm sure others feel differently about it.
To be honest, I think the ptype field is ripe for an enumerator -- this will give some clarity in your code and define what that field actually expects as opposed to allowing any string to be placed in there at random. So something like:
class ProjectType < ActiveRecord::Base
enum ptype: [:web, :mobile]
scope :ptype, -> (ptype) { where(ptype: self.ptypes[ptype] }
end
and called like so:
ProjectType.ptype(:web)
I don't think any one of these solutions presented here or above is any more "right", except the meta-programming solution that's generating a scope for each string in the ptype field stored in the database.
Finally, here is a scope for your Portfolio:
class Portfolio < ActiveRecord::Base
scope :by_ptype, -> (ptype) { joins(:project_type).merge(ProjectType.ptype(ptype) }
end
And called like so:
Portfolio.by_ptype(:web)

You can do it this way
class Portfolio < ActiveRecord::Base
has_many :project_types, dependent: :destroy
scope :ptype, -> (p_type) { includes(:project_types).where(project_types: {ptype: p_type}) }
end
And you can call
Portfolio.ptype('web')

Following a Rails 4.1 approach I would consider my ptype field an enum and write this:
class ProjectType < ActiveRecord::Base
belongs_to :portfolio
enum ptype: { web: 'web', mobile: 'mobile' }
end
This will give you:
ProjectType.web
ProjectType.mobile
scopes to fetch objects based on ptype, but will give you also other useful methods like:
#projectType.web?
#projectType.web!
Please refer to docs: http://api.rubyonrails.org/v4.1/classes/ActiveRecord/Enum.html
In here you ask how to retrieve "web" Portfolio and I will suppose you mean "All Portfolio with at least a ProjectType of ptype 'Web'.
Then I would write something like:
Portfolio.joins(:project_types).merge(ProjectType.web)
see docs: http://apidock.com/rails/ActiveRecord/SpawnMethods/merge
In a final refinement you can then create a scope out of it:
class Portfolio
has_many :project_types
scope :web, -> { joins(:project_types).merge(ProjectType.web) }
end
and call just
Portfolio.web

Related

Specifying custom serializer for a relationship has_one json-api-rb

I have serializers:
class SerializableContact < JSONAPI::Serializable::Resource
type :contacts
has_one :email_event, class: 'SerializableEmailEvent' # class is not working
has_many :geofence_notifications
attributes :email,
:order_id,
:frequent_order_id
end
class SerializableEmailEvent < JSONAPI::Serializable::Resource
...
end
Models:
class Contact < ApplicationRecord
has_one :email_event, -> { email }, class_name: 'Event'
end
class Event < ApplicationRecord
end
I am trying to make SerializableContact to use SerializableEmailEvent as the serializer for email_event relationship. But I cant figure out how to do that. It always error out with:
JsonapiCompliable::Errors::MissingSerializer - Could not find serializer for class 'Event'!
Looked for 'SerializableEvent' but doesn't appear to exist.
Use a custom Inferrer if you'd like different lookup logic.
I am not sure how to use a custom inferrer for the different lookup logic
Maybe I am a bit late responding to your issue, but here you have how to do it:
has_one :email_event do
linkage(always: true) do
{ type: "email_event", id: #object.email_event_id } if #object.email_event_id
end
end
They type has to match with the type on your serilizable resource class. So I was expecting it to be:
class SerializableEmailEvent < JSONAPI::Serializable::Resource
type "email_event"
...
end

Scopes and associations not working

I'm trying to return records where the association is either present or not:
I tried these scopes:
class Booking < ActiveRecord::Base
has_one :availability
scope :with_availability, -> {where{availability.not_eq nil}}
scope :without_availability, -> {where{availability.eq nil}}
end
Try this:
class Booking < ActiveRecord::Base
has_one :availability
scope :with_availability, -> { joins{availability} }
scope :without_availability, -> { joins{availability.outer}.where{availability.id.eq nil} }
end
Use instance methods instead
def with_availability
availability.present?
end
def without_availability
availability.blank?
end
I know there is better way but this should work as well:
class Booking < ActiveRecord::Base
has_one :availability
scope :with_availability, -> {where(id: Availability.pluck(:booking_id))}
scope :without_availability, -> {where.not(id: Availability.pluck(:booking_id))}
end
Also I tried to reproduce the solution by the link below but I didn't manage to do this (but it could be helpful):
Rails - has_one relationship : scopes for associated and non-associated objects

Rails use scopes on includes(:children)

models
class User
has_many :pictures
class Picture
belongs_to User
mount_uploader :picture, UserPictureUploader
def self.profile
find_by(is_profile: true)
end
controller
User.includes(:pictures).where(...
view
=user.pictures.profile.picture_url
This is causing the following Problem, that each picture will be queried (again).
if we use user.pictures.where(profile: true).picture_url it won't make any new sql-queries.
question:
How can we use scopes on the already included result?
With a little digging I found this question
What is the equivalent of the has_many 'conditions' option in Rails 4?
It looks like you can put conditions on your has_many relation to essentially be the scoped config you're looking for. It'd be something like:
has_many :old_pictures, :class_name => 'Picture',
:foreign_key => 'picture_id', -> { where('created_at < ?', Time.now - 7.days) }
Something like below will do
class User
has_many :pictures
scope :some_name, -> { includes(:pictures).where(your_query_here) }
end
Also, scopes are used only on a Class(in other words they are class methods), if you want to use it on the instance, then you need define an instance method something like below
class User
has_many :pictures
def some_name
self.includes(:pictures).where(your_query_here)
end
end

scope and an instance method

I have a model
class User < ActiveRecord::Base
has_many :articles
def good_publisher?
self.articles.size > 50
end
scope :good_publishers, -> where { ...???? }
end
How do I express the scope through good_publisher?? Something like
scope :good_publishers, -> where { x.good_publisher? }
The scope has to be a SQL statement, your best bet here is to use a counter cache for the association.
At the Article class, include this:
belongs_to :user, counter_cache: true # you should already have this belongs_to, all that needs to happen is include the counter_cache
Then, create a migration that includes the articles_count field to user:
add_column :users, :articles_count
Once you have this, the scope can be written as:
scope :good_publishers, where("articles_count > 50")

Rails 3 joining a related model with it's default scope

I'm trying to query across models with the following setup
Class Scorecard < AR::Base
default_scope where(:archived => false)
belongs_to :user
has_many :scorecard_metrics
end
Class ScorecardMetric < AR::Base
belongs_to :scorecard
end
Class User < AR::Base
has_many :scorecards
end
I am trying to query from scorecard metrics with a named scope that joins scorecard and I want it to include the default scope for scorecard, my current implementation (which works) looks like this
# on ScorecardMetric
scope :for_user, lambda {
|user| joins(:scorecard).
where("scorecards.user_id = ? and scorecards.archived = ?", user.id, false)
}
This just seems messy to me, is there any way to join and include the default scope of the joined association?
Looks like I found the answer I was looking for, I just did this
scope :for_user, lambda { |user| joins(:scorecard).where('scorecards.user_id = ?', user.id) & Scorecard.scoped }
which is much nicer without the duplicated logic

Resources