Adding attributes to a join model during assignment to parent model - sql

I have the following setup:
Schema.rb
ActiveRecord::Schema.define(version: 20130923235150) do
create_table "addresses", force: true do |t|
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "user_addresses", force: true do |t|
t.integer "user_id"
t.integer "address_id"
t.string "purpose"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "users", force: true do |t|
t.datetime "created_at"
t.datetime "updated_at"
end
end
User.rb:
class User < ActiveRecord::Base
has_one :user_address
has_one :primary_shipping_address, through: :user_address, class_name: :UserAddress, source: :address
has_one :primary_billing_address, through: :user_address, class_name: :UserAddress, source: :address
end
Address.rb:
class Address < ActiveRecord::Base
has_one :user_address
has_one :primary_shipping_user, through: :user_address, class_name: :UserAddress, source: :user
has_one :primary_billing_user, through: :user_address, class_name: :UserAddress, source: :user
end
UserAddress.rb:
class UserAddress < ActiveRecord::Base
belongs_to :user
belongs_to :address
end
When someone does user.primary_billing_address = address, I want the join model instance to have "billing" set as its purpose. Similarly with shipping and "shipping". Ex.
irb(main):013:0> u = User.new
=> #<User id: nil, created_at: nil, updated_at: nil>
irb(main):014:0> a = Address.create
=> #<Address id: 3, created_at: "2013-09-24 00:13:07", updated_at: "2013-09-24 00:13:07">
irb(main):015:0> u.primary_billing_address = a
=> #<Address id: 3, created_at: "2013-09-24 00:13:07", updated_at: "2013-09-24 00:13:07">
irb(main):016:0> u.save!
=> true
irb(main):017:0> u.user_address
=> #<UserAddress id: 2, user_id: 3, address_id: 3, purpose: nil, created_at: "2013-09-24 00:13:18", updated_at: "2013-09-24 00:13:18">
(not what I want... purpose should be "billing")
How can I do this such that it works for new AND persisted records?. I've come up with solutions that are 90% there, but break on some random spec due to an edge case my approach didn't catch.
The trickiest part to work around is how association= behaves: on new records, it queues the association for assignment through the join model.
PS: I left out the conditionals on the has_one relationships that I'd use to get the address I want. I think this issue is independent of that.

First, the associations are a bit off, both primary_shipping_address and primary_billing_address will return same address. You can change it to
class User < ActiveRecord::Base
has_many :user_addresses # user can have multiple addresses one for shipping and one for billing
has_one :primary_shipping_address,
through: :user_address, class_name: :UserAddress,
source: :address, :conditions => ['user_addresses.purpose = ?','shipping']
has_one :primary_billing_address,
through: :user_address, class_name: :UserAddress,
source: :address, :conditions => ['user_addresses.purpose = ?','billing']
end
To save the purpose while saving the address, there are two options.
Option 1 : Override the default association= method
# alias is needed to refer to original method
alias_method :orig_primary_billing_address=, :primary_billing_address=
def primary_billing_address=(obj)
self.orig_primary_billing_address = obj
self.user_addresses.where("address_id = ?", obj.id).update_attribute(:purpose, 'billing')
end
# repeat for shipping
Option 2 : Create a custom method (I prefer this as it is cleaner and DRY)
def save_address_with_purpose(obj,purpose)
self.send("primary_#{purpose}_address=", obj)
self.user_addresses.where("address_id = ?", obj.id).update_attribute(:purpose, purpose)
end

Related

ActiveRecord NoMethodError when trying to access a has_many collection

I have models representing Continent, Country, State, County and City and, for some reason, I'm able to access some collections but not others.
I have these models:
class Continent < ApplicationRecord
has_many :countries
end
class Country < ApplicationRecord
belongs_to :continent
has_many :states
validates :continent, presence: true
end
class State < ApplicationRecord
belongs_to :country
has_many :counties
validates :country, presence: true
end
class County < ApplicationRecord
belongs_to :state
has_many :cities
validates :state, presence: true
end
class City < ApplicationRecord
belongs_to :county
validates :county, presence: true
end
My schema is as follows (minus timestamps):
create_table "counties", force: :cascade do |t|
t.string "name"
t.bigint "state_id", null: false
t.index ["state_id"], name: "index_counties_on_state_id"
end
create_table "continents", force: :cascade do |t|
t.string "name"
end
create_table "countries", force: :cascade do |t|
t.string "name"
t.bigint "continent_id", null: false
t.index ["continent_id"], name: "index_countries_on_continent_id"
end
create_table "states", force: :cascade do |t|
t.string "name"
t.bigint "country_id", null: false
t.index ["country_id"], name: "index_states_on_country_id"
end
create_table "cities", force: :cascade do |t|
t.string "name"
t.bigint "city_id", null: false
t.index ["county_id"], name: "index_cities_on_county_id"
end
add_foreign_key "counties", "states"
add_foreign_key "countries", "continents"
add_foreign_key "states", "countries"
add_foreign_key "cities", "counties"
end
The following queries work:
Continent.first.countries works and returns a collection of countries.
Continent.first.countries[0].states works and returns a collection of states.
Nothing further works and I'm unable to access any further sub-collections. For example:
Continent.first.countries[0].states[0].counties
It returns:
NoMethodError (undefined method counties' for #State:0x00007fafc28d4180)`
OR if I use FIND to grab a specific county, I still can't access a collection of cities.
For clarification, I have records in the db for all entities. For example, the record for California - #<State id: 331, country_id: 4, created_at: "2020-07-09 19:57:32", updated_at: "2020-07-09 19:57:32"> and the record for Orange County - #<County id: 742, name: "Orange County", state_id: 331, created_at: "2020-07-09 19:57:32", updated_at: "2020-07-09 19:57:32">. But trying cali = State.find(331) yields the California record but cali.counties yield NoMethodError.
What am I overlooking or doing incorrectly?
Why am I able to access some but not others?
Any help would be greatly appreciated.
It looks like you don't have a record in Continent.first.countries[0].states[0], hence you're getting a NoMethodError (undefined method counties' for #State:0x00007fafc28d4180) error.
Try inserting a sample record in State and do State.first.counties if that works.

Want to order with Count, Group by and Order DESC in Rails

This is my doubt, I have the following table:
create_table "followings", force: :cascade do |t|
t.integer "follower_id"
t.integer "followed_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["followed_id"], name: "index_followings_on_followed_id"
t.index ["follower_id", "followed_id"], name: "index_followings_on_follower_id_and_followed_id", unique: true
t.index ["follower_id"], name: "index_followings_on_follower_id"
end
And this is my user table:
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "photo"
t.string "coverimage"
t.string "fullname"
t.string "username"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
and I need to create a top 10 of the most followed users, so in this case, the most repetitive followed_id's, but I can not make an order by on a count just created column inside ActiveRecord. I am trying something like this:
#userst = Following.where(:group => "followed_id", :select => "device_id, COUNT(folloing_id)", sort: :dsc)
what I can be doing wrong?
this is my user controller (top is not finished yet as you can see):
class UsersController < ApplicationController
before_action :authenticate_user!
def index
#users = User.all
end
def show
#user = User.find(params[:id])
#posts = #user.posts.ordered_by_most_recent
end
def edit
#user = User.find(params[:id])
end
def following
#title = "Following"
#user = User.find(params[:id])
#users = #user.following.paginate(page: params[:page])
render 'show_follow'
end
def followers
#title = "Followers"
#user = User.find(params[:id])
#users = #user.followers.paginate(page: params[:page])
render 'show_follow'
end
def top
#userst = User.where()
end
end
Just to let you know this is the user model:
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :authentication_keys => [:username]
validates :fullname, presence: true, length: { maximum: 20 }
validates :username, presence: true, length: { maximum: 20 }
validates_uniqueness_of :username
has_many :posts
has_many :active_followings, class_name: "Following",
foreign_key: "follower_id",
dependent: :destroy
has_many :passive_followings, class_name: "Following",
foreign_key: "followed_id",
dependent: :destroy
has_many :following, through: :active_followings, source: :followed
has_many :followers, through: :passive_followings, source: :follower
mount_uploader :photo, FileUploader
mount_uploader :coverimage, FileUploader
# Follows a user.
def follow(other_user)
following << other_user
end
# Unfollows a user.
def unfollow(other_user)
following.delete(other_user)
end
# Returns true if the current user is following the other user.
def following?(other_user)
following.include?(other_user)
end
end
Assuming you have a user model, and it contains a has_many relationship with the followings model, you can try with:
User.joins(:followings)
.order('COUNT(followings.followed_id) DESC')
.group('users.id')
.limit(10)

#<ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: users.sender_id:

I'm building three models in my rails application. One model references the same model twice as shown in my DB Schema. The only problem is that when I make a POST Request to create a new record in my shipment table. I get this error:
#<ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: users.sender_id: SELECT \"users\".* FROM \"users\" WHERE \"users\".\"sender_id\" = ? LIMIT ?>
I don't think that I need to add a sender_id & receiver_id column in my users table because the sender_id & receiver_id are basically the User_ID in the users column. Any help would be much appreciated it!
This is my user.rb file:
class User < ApplicationRecord
has_many :shipments
end
This is my shipment.rb
class Shipment < ApplicationRecord
belongs_to :sender, class_name: "User", primary_key: "sender_id"
belongs_to :receiver, class_name: "User", primary_key: "receiver_id"
validates_uniqueness_of :tntcode
end
This is my shipments_controller:
class ShipmentsController < ApplicationController
def index
shipments = Shipment.all
end
def show
shipment = Shipment.find(params[:id])
end
def create
shipment = Shipment.new(shipment_params)
if shipment.save
render json: {status: 'Shipment created successfully'}, status: :created
else
render json: { errors: shipment.errors.full_messages }, status: :bad_request
end
end
def shipment_params
params.require(:shipment).permit(:tntcode, :status, :shipment_type, :weight, :content, :price, :sender_id, :receiver_id)
end
end
And my schema.rb:
ActiveRecord::Schema.define(version: 20180826123320) do
create_table "shipments", force: :cascade do |t|
t.integer "tntcode"
t.string "status"
t.string "shipment_type"
t.integer "weight"
t.string "content"
t.integer "price"
t.integer "sender_id"
t.integer "receiver_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["receiver_id"], name: "index_shipments_on_receiver_id"
t.index ["sender_id"], name: "index_shipments_on_sender_id"
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email", null: false
t.string "role"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "photourl"
t.string "userid"
end
end
You don't want to change the primary_key on your belongs_to associations: that's the other table's ID column (id).
You instead want:
belongs_to :sender, class_name: "User", foreign_key: "sender_id"
belongs_to :receiver, class_name: "User", foreign_key: "receiver_id"
... which is the default, so this should work too:
belongs_to :sender, class_name: "User"
belongs_to :receiver, class_name: "User"

ActiveRecord: complex query with Sum, Join, Group By and Select multiple fields

I've waisted few days for this query but still not figured out the solution.
There are my models
class UserObject < ActiveRecord::Base
self.table_name = "user_objects"
belongs_to :user, class_name: 'User'
belongs_to :the_object, class_name: 'Object'
end
class Object < ActiveRecord::Base
self.table_name = 'objects'
has_many :user_objects, class_name: 'UserObject', foreign_key: :the_object_id, inverse_of: :the_object
has_many :users, through: :user_objects
end
class User < ActiveRecord::Base
self.table_name = "users"
end
Here is my schema
create_table "objects", force: true do |t|
t.float "importance", default: 0.5, null: false
end
create_table "user_objects", force: true do |t|
t.integer "user_id"
t.integer "the_object_id"
t.float "score", default: 0.0, null: false
end
create_table "users", force: true do |t|
t.string "first_name"
t.string "last_name"
end
I need a query for select objects.importance and sum of user_objects.score. But also I have to select for query only those objects which belongs to user1 and user2.
I wrote a query for select objects.importance
Object.select("objects.importance").joins(:user_objects).
where(user_objects: {user_id: [user1_id,user2_id]}).
group(objects: :id).
having('COUNT("user_objects"."id")=2')
And it's converted to
SELECT objects.importance FROM "objects" INNER JOIN "user_objects" ON "user_objects"."the_object_id" = "objects"."id" WHERE "user_objects"."user_id" IN (2, 7) GROUP BY "objects"."id" HAVING COUNT("user_objects"."id")=2
When I executed this query I got this response
[#<Object id: nil, importance: 0.5>, #<Object id: nil, importance: 0.5>]
Quantity of objects in response is OK. But I still don't know how to count in this query sum of user_objects.score. Next query doesn't work
Object.select("objects.importance, SUM(user_objects.score)").
joins(:user_objects).
where(user_objects: {user_id: [user1_id,user2_id]}).
group(objects: :id).having('COUNT("user_objects"."id")=2')
I expected in response something like this
[#[<Object id: nil, importance: 0.5>, 0.2], #[<Object id: nil, importance: 0.5>,0.3]]
I would greatly appreciate any help you can give me in working this problem.
Ок. I've found a solution. Here is right query.
Object.joins(:user_objects).
where(user_objects: {user_id: [user1_id,user2_id]}).
group(objects: :id).
having('COUNT("user_objects"."id")=2').
pluck("objects.importance, SUM(user_objects.score)")
Problem was in select method because it creates an object of a class which invoke this method. So it is impossible to choose in one select attributes of few models. But pluck return resulting array with values of fields So we can choose fields from different tables.
That`s it. So simple!

Rails 3 - reject_if on has_many :through association not possible?

Similar Setup on Stackoverflow
Hi there,
I have a little problem with my nested form setup...
Model "TimeShifting"
class TimeShifting < ActiveRecord::Base
has_many :article_position_time_shifting_assignments, :dependent => :destroy
has_many :article_positions, :through => :article_position_time_shifting_assignments
accepts_nested_attributes_for :article_position_time_shifting_assignments
end
Join Model "ArticlePositionTimeShiftingAssignment"
class ArticlePositionTimeShiftingAssignment < ActiveRecord::Base
belongs_to :article_position
belongs_to :time_shifting
accepts_nested_attributes_for :article_position, :reject_if => proc { |obj| obj['baan_id'].blank? }
end
Model "ArticlePosition"
class ArticlePosition < ActiveRecord::Base
has_many :article_position_time_shifting_assignments
has_many :time_shiftings, :through => :article_position_time_shifting_assignments
end
The important point in here is that I have some extra attributes in the Join Model ArticlePositionTimeShiftingAssignment...
create_table "article_position_time_shifting_assignments", :force => true do |t|
t.integer "article_position_id"
t.integer "time_shifting_id"
t.integer "created_by"
t.integer "updated_by"
t.datetime "created_at"
t.datetime "updated_at"
t.string "order_number"
t.date "confirmed_date"
t.string "purchase_positions_collection"
end
created_by and updated_by are getting filled out automaticly, order_number and confirmed_date through the form.
Well... I have absolutly no problem to create a new ArticlePosition with
<%= f.simple_fields_for :article_position_time_shifting_assignments do |builder| %>
and then
<%= f.simple_fields_for :article_position do |builder| %>
My problem is that it's always creating a new article_position_time_shifting_assignment record. Even when it's not creating a new ArticlePosition.
#<ArticlePositionTimeShiftingAssignment id: 10, article_position_id: nil, time_shifting_id: 10, created_by: 1, updated_by: 1, created_at: "2012-05-23 14:57:27", updated_at: "2012-05-23 14:57:27", order_number: "", confirmed_date: nil, purchase_positions_collection: "">
Mhhhh... I don't want that :P
accepts_nested_attributes_for :article_position_time_shifting_assignments, :reject_if => proc { |obj| obj['article_position_id'].blank? }
This won't work because there is no article_position_id until the corresponding article_position has been saved :-/
Any ideas to solve this problem?
Cheers,
Michael
Actually it WILL create new record in Mapping table (ArticlePositionTimeShiftingAssignment) and it is correct because it is many-to-many association. So in this case it is not good approach to assign some additional records to Mapping table.