Rails scope or def based on related table field - ruby-on-rails

I have a Rails 3 application that contains a table called worequests.
worequest.rb model contains:
belongs_to :statuscode
Statuscodes has a boolean field called closed.
I want to be able to get a list of all worequests where worequest.statuscode.closed == true.
For example:
def index2
#search = Worequest.closed.search(params[:q])
#worequests = #search.result
end
OR
worequests.notclosed.count
I tried these in the worequest model:
scope :closed, joins(:statuscode).where(:statuscode.closed => true)
scope :closed, joins(:statuscode).& Statuscode.closed
scope :closed, joins(:statuscode) & Statuscode.closed
def self.closed
joins(:statuscode) & Statuscode.closed
end
def self.notclosed
joins(:statuscode) & Statuscode.notclosed
end
Thanks for the help!

Since Rails 4, named scopes must be given a callable, such as a lambda:
scope :closed, -> { joins(:statuscode).where(statuscodes: { closed: true }) }
should do the trick (note the singular :statuscode in the join and the plural statuscodes table name in the where condition).

I'm not sure I understand the question correctly.
Do you just want a scope on your class so you get all closed ones?
class StatusCode < ActiveRecord:Base
belongs_to :worequest
end
class Worequest < ActiveRecord:Base
has_one :status_code do
def closed
where(closed: true)
end
end
end
closed_quests: Worequest.all.closed
I've based my answer on the question asked here: How do you scope ActiveRecord associations in Rails 3?
As this is how I interpreted your question.
For rails 4, I recommend using the other answer.

Related

How to check if associated model has entries in Rails 5?

I have a model RegularOpeningHour(dayOfWeek: integer) that is associated to a model OpeningTime(opens: time, closes: time). RegularOpeningHour has an 1:n relation to OpeningTime, so that a specific day can have many opening times.
(I know that I simply could have one entry with 'opens' and 'closes' included in RegularOpeningHour but for other reasons I need this splitting)
Now I want a open?-Method, that returns whether the business is opened or not. I tried the following in my model file regular_opening_hour.rb:
def open?
RegularOpeningHour.where(dayOfWeek: Time.zone.now.wday).any? { |opening_hour| opening_hour.opening_times.where('? BETWEEN opens AND closes', Time.zone.now).any? }
end
Unforutnately, that doesn't work. Any ideas to solve this?
How about this:
def open?
joins(:opening_times)
.where(dayOfWeek: Time.current.wday)
.where("opens <= :time AND closes >= :time", time: Time.current)
.any?
end
EDIT: Missing ':' in the join
You could create some scopes to make selecting open OpeningTimes and open RegularOpeningHours less clunky. This makes creating the given selection much easier.
class OpeningTime < ApplicationRecord
# ...
belongs_to :regular_opening_hour
def self.open
time = Time.current
where(arel_table[:opens].lteq(time).and(arel_table[:closes].gteq(time)))
end
# ...
end
class RegularOpeningHour < ApplicationRecord
# ...
has_many :opening_times
def self.open
where(
dayOfWeek: Time.current.wday,
id: OpeningTime.select(:regular_opening_hour_id).open,
)
end
# ...
end
def open?
RegularOpeningHour.open.any?
end
Since you have has_many association of RegularOpeningHour to OpeningTime you can use join query like below.:
RegularOpeningHour.joins(:opening_times).where(dayOfWeek: Time.zone.now.wday).where('? BETWEEN opening_times.opens AND opening_times.closes', Time.zone.now).any?

rails associations :autosave doesn't seem to working as expected

I made a real basic github project here that demonstrates the issue. Basically, when I create a new comment, it is saved as expected; when I update an existing comment, it isn't saved. However, that isn't what the docs for :autosave => true say ... they say the opposite. Here's the code:
class Post < ActiveRecord::Base
has_many :comments,
:autosave => true,
:inverse_of => :post,
:dependent => :destroy
def comment=(val)
obj=comments.find_or_initialize_by(:posted_at=>Date.today)
obj.text=val
end
end
class Comment < ActiveRecord::Base
belongs_to :post, :inverse_of=>:comments
end
Now in the console, I test:
p=Post.create(:name=>'How to groom your unicorn')
p.comment="That's cool!"
p.save!
p.comments # returns value as expected. Now we try the update case ...
p.comment="But how to you polish the rainbow?"
p.save!
p.comments # oops ... it wasn't updated
Why not? What am I missing?
Note if you don't use "find_or_initialize", it works as ActiveRecord respects the association cache - otherwise it reloads the comments too often, throwing out the change. ie, this implementation works
def comment=(val)
obj=comments.detect {|obj| obj.posted_at==Date.today}
obj = comments.build(:posted_at=>Date.today) if(obj.nil?)
obj.text=val
end
But of course, I don't want to walk through the collection in memory if I could just do it with the database. Plus, it seems inconsistent that it works with new object but not an existing object.
Here is another option. You can explicitly add the record returned by find_or_initialize_by to the collection if it is not a new record.
def comment=(val)
obj=comments.find_or_initialize_by(:posted_at=>Date.today)
unless obj.new_record?
association(:comments).add_to_target(obj)
end
obj.text=val
end
I don't think you can make this work. When you use find_or_initialize_by it looks like the collection is not used - just the scoping. So you are getting back a different object.
If you change your method:
def comment=(val)
obj = comments.find_or_initialize_by(:posted_at => Date.today)
obj.text = val
puts "obj.object_id: #{obj.object_id} (#{obj.text})"
puts "comments[0].object_id: #{comments[0].object_id} (#{comments[0].text})"
obj.text
end
You'll see this:
p.comment="But how to you polish the rainbow?"
obj.object_id: 70287116773300 (But how to you polish the rainbow?)
comments[0].object_id: 70287100595240 (That's cool!)
So the comment from find_or_initialize_by is not in the collection, it outside of it. If you want this to work, I think you need to use detect and build as you have in the question:
def comment=(val)
obj = comments.detect {|c| c.posted_at == Date.today } || comments.build(:posted_at => Date.today)
obj.text = val
end
John Naegle is right. But you can still do what you want without using detect. Since you are updating only today's comment you can order the association by posted_date and simply access the first member of the comments collection to updated it. Rails will autosave for you from there:
class Post < ActiveRecord::Base
has_many :comments, ->{order "posted_at DESC"}, :autosave=>true, :inverse_of=>:post,:dependent=>:destroy
def comment=(val)
if comments.empty? || comments[0].posted_at != Date.today
comments.build(:posted_at=>Date.today, :text => val)
else
comments[0].text=val
end
end
end

How to create scope in my Rails 3 model to display all where the value = false?

I recently added a new field called "Review" in a column called "Posts"
I want to display only the posts where Review = false.
I am trying to add the correct scope into my Posts model to do this.
Here's the current scope that I have.
default_scope order: 'posts.created_at DESC'
Model < ActiveRecord::Base
def self.without_review
where(review: false) # or how ever you have the boolean in your db
end
end
And of course a spec
describe Model do
describe "::without_review" do
it 'loads posts without reviews' do
no_review = Model.create(review: false)
Model.create(review: true)
Model.without_reivew.all.should == [no_review]
end
end
end
Just use a class method and chain scopes
You could create a named scope:
scope :unreviewed, where(:review => false)

How can I check if one ActiveRecord class belongs to another

Given two subclasses of ActiveRecord::Base, how can I implement a function that checks to see if one belongs to the other?
def ClazzA < ActiveRecord::Base
belongs_to :clazz_b
end
def ClazzB < ActiveRecord::Base
has_many :clazz_a
end
def belongs_to? a, b
...
end
Thanks!
Max
def belongs_to?(a,b)
sym = b.to_s.downcase.to_sym
a.reflect_on_all_associations(:belongs_to).map(&:name).include?(sym)
end
> belongs_to?(ClazzA,ClazzB) # true
> belongs_to?(ClazzB,ClazzA) # false
Try this:
def belongs_to? a, b
b.reflect_on_all_associations(:belongs_to).
any?{|bta| bta.association_class == a}
end
Note:
This question was unanswered when I started answering. After completing the answer I noticed the answer posted by #zeteic. I am letting the answer stand as this solution will work even for cases when the association name doesn't map to model name.

Can nested attributes be used in combination with inheritance?

I have the following classes:
Project
Person
Person > Developer
Person > Manager
In the Project model I have added the following statements:
has_and_belongs_to_many :people
accepts_nested_attributes_for :people
And of course the appropriate statements in the class Person. How can I add a Developer to a Project through the nested_attributes method? The following does not work:
#p.people_attributes = [{:name => "Epic Beard Man", :type => "Developer"}]
#p.people
=> [#<Person id: nil, name: "Epic Beard Man", type: nil>]
As you can see the type attributes is set to nil instead of "Developer".
Solution for Rails3: attributes_protected_by_default in now a class-method:
class Person < ActiveRecord::Base
private
def self.attributes_protected_by_default
super - [inheritance_column]
end
end
I encountered a similar problem few days ago. The inheritance column(i.e. type) in a STI model is a protected attribute. Do the following to override the default protection in your Person class.
Rails 2.3
class Person < ActiveRecord::Base
private
def attributes_protected_by_default
super - [self.class.inheritance_column]
end
end
Rails 3
Refer to the solution suggested by #tokland.
Caveat:
You are overriding the system protected attribute.
Reference:
SO Question on the topic
Patches above did not work for me, but this did (Rails3):
class ActiveRecord::Reflection::AssociationReflection
def build_association(*options)
if options.first.is_a?(Hash) and options.first[:type].presence
options.first[:type].to_s.constantize.new(*options)
else
klass.new(*options)
end
end
end
Foo.bars.build(:type=>'Baz').class == Baz
For those of us using Mongoid, you will need to make the _type field accessible:
class Person
include Mongoid::Document
attr_accessible :_type
end

Resources