Rails Find based on a set existing in through a relation - ruby-on-rails

I am trying to select an instance based on a relation of that instance containing a set. A simplified example follows:
class Product::Variation < ActiveRecord::Base
attr_accessible :name, :product_id, :quantity
belongs_to :product
has_many :bids, :foreign_key => :product_variation_id
has_many :product_variation_property_values, :class_name => 'Product::Variation::PropertyValue'
has_many :property_values, :through => :product_variation_property_values, :class_name => 'Property::Value'
end
class Product::Variation::PropertyValue < ActiveRecord::Base
attr_accessible :property_value_id, :variation_id, :property_id
belongs_to :variation
belongs_to :property_value, :class_name => 'Property::Value'
end
class Property < ActiveRecord::Base
attr_accessible :name
has_many :values, :class_name => 'Property::Value'
end
class Property::Value < ActiveRecord::Base
attr_accessible :content
belongs_to :property
belongs_to :partner
end
So now I want to do something like the following (in psuedo code):
Variation.where(:property_values includes [Property::Value.find(1), Property::Value.find(2)])
Is there a way to do this using ActiveRecord?
Thanks and let me know if you need more info.
More Info
I tried the following:
Product::Variation.joins(:property_values).where('property_values.id' => [Property::Value.find(1).id, Property::Value.find(2).id]).first
...which is the following SQL...
SELECT "product_variations".* FROM "product_variations" INNER JOIN "product_variation_property_values" ON "product_variation_property_values"."variation_id" = "product_variations"."id" INNER JOIN "property_values" ON "property_values"."id" = "product_variation_property_values"."property_value_id" WHERE "property_values"."id" IN (1, 2)
...and this returns...
#<Product::Variation id: 25, product_id: 1, quantity: 39, created_at: "2013-11-18 00:18:45", updated_at: "2013-11-18 00:18:45">
But if I do:
Product::Variation.find(25).property_values.inspect
...I get...
[#<Property::Value id: 1, property_id: 1, content: "XS", created_at: "2013-11-18 00:18:45", updated_at: "2013-11-18 00:18:45", color: nil, color_texture: nil, secondary_color: nil>, #<Property::Value id: 6, property_id: 2, content: "Dark Wood", created_at: "2013-11-18 00:18:45", updated_at: "2013-11-18 00:18:45", color: "#855E42", color_texture: "striped", secondary_color: "#FFB90F">]
But I'm looking for the Product::Variation that contains both Property::Value 1 and 2. This is returning those that contain 1 or 2.

This should be doable with a join query. Hard to write form heart, but should be something like this:
Variation.joins(propert_values: :values).where('values.id' => [Property::Value.find(1).id, Property::Value.find(2).id]).first

Related

Rails many-to-many self relationship failing validation

I'm building a toy Rails app that will run tournaments. The thing with this type of tournament is though is that rounds can have both multiple input rounds and multiple output rounds (i.e., you can't model a tournament's rounds as a tree, but it's still a DAG).
I'm running into an issue where I believe I have my associations set up properly, but Rails is complaining about the models suddenly becoming invalid, and I can't figure out how to get more information.
I have these relevant ActiveModel classes:
class Round < ApplicationRecord
belongs_to :tournament
has_and_belongs_to_many :players
has_many :previous_round_edges, foreign_key: :end_id, class_name: "RoundEdge"
has_many :previous_rounds, through: :previous_round_edges, source: :start
has_many :next_round_edges, foreign_key: :start_id, class_name: "RoundEdge"
has_many :next_rounds, through: :next_round_edges, source: :end
end
class RoundEdge < ApplicationRecord
belongs_to :start, foreign_key: :start_id, class_name: "Round"
belongs_to :end, foreign_key: :end_id, class_name: "Round"
end
The code in question that triggers errors:
final_round = rounds.build()
players.each_slice(4) do |player_chunk|
round = rounds.build(players: player_chunk, position: round_position)
# Objects are valid right until we start associating them with each other
round.next_rounds << final_round
final_round.previous_rounds << round
end
I've also tried throwing in a debugger, and these are the results:
round.errors
=> #<ActiveModel::Errors [#<ActiveModel::Error attribute=next_round_edges, type=invalid, options={}>, #<ActiveModel::Error attribute=next_rounds, type=invalid, options={}>]>
final_round.errors
=> #<ActiveModel::Errors [#<ActiveModel::Error attribute=previous_round_edges, type=invalid, options={}>, #<ActiveModel::Error attribute=previous_rounds, type=invalid, options={}>]>
If I look at the relations though, they seem correct:
round
=> #<Round:0x000000010fc0e760 id: nil, position: 0, next_round_id: nil, tournament_id: nil, created_at: nil, updated_at: nil>
final_round
=> #<Round:0x00000001104e2580 id: nil, position: nil, next_round_id: nil, tournament_id: nil, created_at: nil, updated_at: nil>
round.previous_rounds
=> []
round.next_rounds
=> [#<Round:0x00000001104e2580 id: nil, position: nil, next_round_id: nil, tournament_id: nil, created_at: nil, updated_at: nil>]
final_round.previous_rounds
=> [#<Round:0x000000010fc0e760 id: nil, position: 0, next_round_id: nil, tournament_id: nil, created_at: nil, updated_at: nil>]
final_round.next_rounds
=> []
So it looks like they point to each other fine, but I can't figure out what is going on.
Thanks!
I figured it out! I needed to manually specific the inverse relations. What was happening was that even though the Rounds were linked, the RoundEdges` were not being created properly.
The solution was to update the Round class to look as follows:
class Round < ApplicationRecord
belongs_to :tournament
has_and_belongs_to_many :players
has_many :previous_round_edges, foreign_key: :end_id, class_name: "RoundEdge", inverse_of: :end
has_many :previous_rounds, through: :previous_round_edges, source: :start, inverse_of: :next_rounds
has_many :next_round_edges, foreign_key: :start_id, class_name: "RoundEdge", inverse_of: :start
has_many :next_rounds, through: :next_round_edges, source: :end, inverse_of: :previous_rounds
end

How do i select an association record that has yet to be saved in Rails?

I have the following models and relationships. I'm building a form and am wanting to initialize terms of the proposal for the form. How can I select a specific ProposalTerm by it's term_type_id to pass on to my fields_for block?
Proposal
class Proposal < ApplicationRecord
after_initialize :add_terms
has_many :terms, class_name: "ProposalTerm", dependent: :destroy
accepts_nested_attributes_for :terms
def add_terms
terms << ProposalTerm.first_or_initialize(type: TermType.signing_bonus)
end
end
ProposalTerm
class ProposalTerm < ApplicationRecord
include DisableInheritance
belongs_to :proposal
belongs_to :type, class_name: "TermType", foreign_key: "term_type_id"
def self.signing_bonus
find_by(type: TermType.signing_bonus)
end
end
My Attempt
>> #proposal.terms
=> #<ActiveRecord::Associations::CollectionProxy [#<ProposalTerm id: nil, season: nil, value: nil, is_guaranteed: false, term_type_id: 2, proposal_id: nil, created_at: nil, updated_at: nil>]>
>> #proposal.terms.where(term_type_id: 2)
=> #<ActiveRecord::AssociationRelation []>
I was able to figure out an answer. I had tried "select" but I was doing it incorrectly.
I had tried the following,
#proposal.terms.select(term_type_id: 2)
but that wasn't returning anything. I then did the following...
#proposal.terms.select { |t| t.term_type_id = 2 }
If you want to return just the first instance use "detect" ...
#proposal.terms.detect { |t| t.term_type_id = 2 } }

Rails: concern doesn't load has_one child

If I have a parent class like so:
class Component < ApplicationRecord
include Imageable
end
And that concern looks like so:
module Imageable
extend ActiveSupport::Concern
included do
has_one :image_attachment, as: :imageable, dependent: :destroy
end
end
And that class looks like this:
class ImageAttachment < ApplicationRecord
belongs_to :imageable, polymorphic: true, optional: true
has_attached_file :data
end
Why can't they find each other given these two instances?
ImageAttachment.last
=> <ImageAttachment id: 12, imageable_type: "component", imageable_id: 3, data_file_name: "tumblr_nb88njd2DF1sfwp0ho1_1280.jpg", data_content_type: "image/jpeg", data_file_size: 63794, data_updated_at: "2018-05-02 11:07:12", created_at: "2018-05-02 10:37:48", updated_at: "2018-05-02 11:07:13">
Component.find(3)
=> <Component id: 3, name: "Testing", body: "Test body", created_at: "2017-11-22 02:43:03", updated_at: "2018-05-01 23:50:01">
Component.find(3).image_attachment
=> nil
ImageAttachment.last.component
=> NoMethodError (undefined method `component' for #<ImageAttachment:0x00007fea291ac1e0>)
1) It seems that your imageable_type attribute is wrong, because it should be a class name e.g. 'Component' not 'component'. Rails would not do this by default, so I assume you tampered with that column manually and that is why it is not working
2) ImageAttachment.last.component -> this won't work because your association for ImageAttachment is belongs_to :imageable, so in order to get parent, you need to call ImageAttachment.last.imageable

Properly modeling monetary transactions in Rails 4

I am making a toy application to learn Rails 4 (without just cloning a tutorial).
Users sign up (I'm using the Devise gem to take care of user authentication), and a BTC pub/prv keypair is generated, and an address is computed and displayed to the user (in a flash message), so they can top off their account. Other Users sign up, and anyone can search for anyone and a dropdown is dynamically populated with every single user, but filters down names as a User types the name of their friend/associate, whoever they want to send Bitcoin to. I am only using testnet for this idea at the moment, no real BTC (don't worry!).
Anyways, here is my idea for modeling this application:
class User < ActiveRecord::Base
has_many :account
end
class Tx < ActiveRecord::Base
has_one :receiver, class => "account"
belongs_to :user, through :account
end
class Account < ActiveRecord::Base
belongs_to :user
has_many :tx
end
The reason why I don't like the above is because in my mind it seems that a Tx (short for transaction since transaction is a reserved word in Rails) actually belongs to two users, but my readings seem to indicate that I can't have something like this:
class User < ActiveRecord::Base
has_many :tx
end
class Tx < ActiveRecord::Base
has_one :receiver, class => "user"
has_one :sender, class => "user
end
Which of these implementations is better? I appreciate any insight into this model.
I'd go with the second method. I went with "transfers" instead of "tx", for readability - but you can name it as you please.
class User < ActiveRecord::Base
has_many :transfers
has_many :received_transfers, :class_name => "Transfer", :foreign_key => "receiver_id"
end
class Transfer < ActiveRecord::Base
belongs_to :user # Sender
belongs_to :receiver, :class => "User"
end
Testing it:
>> Transfer.create(:user_id => 1, :receiver_id => 2, :amount => 4.00)
=> #<Transfer id: 1, user_id: 1, receiver_id: 2, amount: #<BigDecimal:7fb3bd9ba668,'0.4E1',9(36)>, created_at: "2014-09-03 04:35:47", updated_at: "2014-09-03 04:35:47">
>> User.first.transfers
=> #<ActiveRecord::Associations::CollectionProxy [#<Transfer id: 1, user_id: 1, receiver_id: 2, amount: #<BigDecimal:7fb3c10682f0,'0.4E1',9(18)>, created_at: "2014-09-03 04:35:47", updated_at: "2014-09-03 04:35:47">]>
>> User.last.received_transfers
=> #<ActiveRecord::Associations::CollectionProxy [#<Transfer id: 1, user_id: 1, receiver_id: 2, amount: #<BigDecimal:7fb3bdabace8,'0.4E1',9(18)>, created_at: "2014-09-03 04:35:47", updated_at: "2014-09-03 04:35:47">]>
Happy coding!

Using polymorph many-to-many self referencing relation with attributes on the relation in rails

I'd like to create a self referencing relation in rails. I have a Person model, and the person should have masters and pupils with same Person object.
So far I tried:
class Person <ActiveRecord::Base
has_many :relationships, :dependent => :destroy
has_many :masters, :through => :relationships, :conditions => "status='master'"
has_many :pupils, :through => :relationships, :conditions => "status='pupil'"
has_many :inverse_relationships, :class_name => "Relationship",
:foreign_key => "related_id"
has_many :inverse_masters, :through => :inverse_relationships,
:source => :person, :conditions => "status='master'"
has_many :inverse_pupils, :through => :inverse_relationships,
:source => :person, :conditions => "status='pupil'"
end
class Relationship < ActiveRecord::Base
belongs_to :person
belongs_to :master, :class_name => "Person", :foreign_key => 'related_id'
belongs_to :pupil, :class_name => "Person", :foreign_key => 'related_id'
end
It seems to work when I am trying to select:
#a = Person.find(:first)
#a.masters
but when I try to do a push into masters, it saves the relationship without the status set to master. It saves null instead. Is there an easy way to save status=master when I push into masters and status=pupil when I push into pupils?
Thanks
To make it short the solution is: association callbacks (more here under the Association Callback section: http://railsapi.com/doc/rails-v3.0.0/classes/ActiveRecord/Associations/ClassMethods.html)
To be a little more detailed I have adapted your example a little bit, but basically the structure is the same, here is the code:
class Person < ActiveRecord::Base
has_many :relationships
has_many :pupils, :through => :relationships, :source => :other_person, :conditions => 'relationships.type = "MasterPupil"', :after_add => Proc.new{|p,o| Relationship.update_all("type = 'MasterPupil'", ['person_id = ? AND other_person_id = ?', p.id, o.id])}
has_many :masters, :through => :relationships, :source => :other_person, :conditions => 'relationships.type = "PupilMaster"', :after_add => Proc.new{|p,o| Relationship.update_all("type = 'PupilMaster'", ['person_id = ? AND other_person_id = ?', p.id, o.id])}
end
class Relationship < ActiveRecord::Base
belongs_to :person
belongs_to :other_person, :class_name => 'Person'
before_validation :set_type
def set_type
self.type = 'OpenRelationship'
end
end
class MasterPupil < Relationship
end
class PupilMaster < Relationship
end
The RelationShip model contains a type column which is the equivalent of your status column, but type is nicer if I later want to do an STI and declare MasterPupil/PupilMaster relationship models.
RelationShip also has a set_type before_validation that will set the type to OpenRelationship which should be temporary before the after_add callback defined in the Person model in each association will set things clear (and set either a MasterPupil or PupilMaster type)
and now:
Loading development environment (Rails 3.0.0)
irb(main):001:0> p = Person.create
=> #<Person id: 1, created_at: "2010-09-10 23:35:37", updated_at: "2010-09-10 23:35:37">
irb(main):002:0> p.pupils
=> []
irb(main):003:0> p.masters
=> []
irb(main):004:0> p.pupils << Person.create
=> [#<Person id: 2, created_at: "2010-09-10 23:35:56", updated_at: "2010-09-10 23:35:56">]
irb(main):005:0> Relationship.all
=> [#<MasterPupil id: 1, person_id: 1, other_person_id: 2, type: "MasterPupil">]
irb(main):006:0> p.masters << Person.create
=> [#<Person id: 3, created_at: "2010-09-10 23:36:29", updated_at: "2010-09-10 23:36:29">]
irb(main):007:0> Relationship.all
=> [#<MasterPupil id: 1, person_id: 1, other_person_id: 2, type: "MasterPupil">, #<PupilMaster id: 2, person_id: 1, other_person_id: 3, type: "PupilMaster">]
irb(main):008:0> p.reload
=> #<Person id: 1, created_at: "2010-09-10 23:35:37", updated_at: "2010-09-10 23:35:37">
irb(main):009:0> p.pupils
=> [#<Person id: 2, created_at: "2010-09-10 23:35:56", updated_at: "2010-09-10 23:35:56">]
irb(main):010:0> p.masters
=> [#<Person id: 3, created_at: "2010-09-10 23:36:29", updated_at: "2010-09-10 23:36:29">]

Resources