Create associations after record creation - ruby-on-rails

I need to create 4 records in table inventory after creating a user. I know that i need to use callback after_create, but i guess it is not good according to the best practices and DRY principle to have 4 lines like this:
def create_items
Item.create({user_id: self.id, item_id: 1})
Item.create({user_id: self.id, item_id: 2})
...
end
Or maybe even like that?
def create_items
self.inventories.create([
{item_id: 1},
{item_id: 2}
])
end

Ofen you can solve this with has_many :through:
class Inventory
belongs_to :user
belongs_to :item
end
class User
has_many :inventories
has_many :items, through: :inventories
end
Then you can add them straight-up:
items.each do |item|
#user.items << item
end
If you have only ID values:
Item.where(id: ids).each do |item|
#user.items << item
end
That's generally efficient enough to get the job done. If you're experiencing severe load problems with this you can always do it with a bulk-insert plugin, but that's usually a last-resort.

Related

Ruby on Rails relationship model

In Ruby on Rails 4, how do you create a many-to-many relationship inside a relationship model for a friends list such as Facebook using the has_many :through ... syntax ?? I'm a newbie and currently learning Ruby on Rails 4. I have looked at this link.
But still have a hard time grasping it.
you will need a join table that references both sides of the relations
let us say you have an relation Post and another relation Category with a many to many relationship between them you need a join table to be able to represent the relationship.
migration for a join table would be
class CreateCategoriesPosts < ActiveRecord::Migration
def change
create_table :categories_posts do |t|
t.integer :category_id
t.integer :post_id
t.timestamps
end
add_index :categories_posts, [:category_id, :post_id]
end
end
and in the models/post.rb
Class Post < ActiveRecord::Base
has_and_belongs_to_many :categories
end
and in the models/category.rb
Class Category < ActiveRecord::Base
has_and_belongs_to_many :posts
end
more here:
http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
I think #RAF pretty much nailed it. But to use the OP's example:
class User < ActiveRecord::Base
has_and_belongs_to_many :users_list
end
class UsersList < ActiveRecord::Base
has_and_belongs_to_many :users
end
Although at first it might seem like a User should have only one list of friends (UsersList), that might not always be the case. Think of types within the UserList model, such as: 'close friends', 'work friends', 'all friends' for example.
My advice: dig into the Rails guides. This is a concept worth learning and truly understanding (which I'm still doing :).
many-to_many relationships are a simple concept, but complex when using the database because of the way databases work. A person could have 1 to N different friends, which means that a single entry for a database would need a dynamic amount of memory for each entry, which in the db world is a no-no. So instead of creating a list of friends you would have to make a table that represents the links between friends, for example:
friendship.rb
class Friendship < ActiveRecord::Base
belongs_to :friend, foreign_key: 'friend_A' # this entry has a field called 'friend_A'
belongs_to :friend, foreign_key: 'friend_B' # this entry has a field called 'friend_B'
end
These links will represent your network of friends. However, as the two previous answers have mentioned, Rails has some nifty magic, "has_and_belongs_to_many", which will do this for you.
NOTICE: The problem here is that in my StatusesController, in the index action, the #relationship object only gets the statuses of all your friends, but does not get your own statuses. Is there a better way of approaching this? I am trying to create a view to view all statuses of users that are your friends, and your own statuses too, and so far, I can't seem to figure out how to order it chronologically, even if in my status model, i included "default_scope -> { order(created_at: :desc) } ". Any advice would be deeply appreciated
class User < ActiveRecord::Base
has_many :relationships
has_many :friends, :through => :relationships
has_many :inverse_relationships, class_name: 'Relationship', foreign_key: 'friend_id'
has_many :inverse_friends, through: 'inverse_relationships', :source => :user end
#
class Relationship < ActiveRecord::Base
# before_save...
belongs_to :user
belongs_to :friend, class_name: 'User'
end
#
class RelationshipsController < ApplicationController
def friend_request
user_id = current_user.id
friend_id = params[:id]
if Relationship.where( user_id: user_id, friend_id: friend_id, accepted: false).blank?
Relationship.create(user_id: user_id, friend_id: friend_id, accepted: false)
redirect_to user_path(params[:id])
else
redirect_to user_path(params[:id])
end
end
def friend_request_accept
# accepting a friend request is done by the recipient of the friend request.
# thus the current user is identified by to_id.
relationship = Relationship.where(user_id: params[:id], friend_id: current_user.id).first
if Relationship.exists?(relationship) and relationship.accepted == false
relationship.update_attributes(accepted: true)
end
redirect_to relationships_path
end
def friend_request_reject
relationship = Relationship.where(user_id: params[:id], friend_id: current_user.id).first
relationship.destroy
redirect_to relationships_path
end
################################
def index
#relationships_pending = Relationship.where(friend_id: current_user.id, accepted: false)
end
end
#
class StatusesController < ApplicationController
def index
#status = Status.new
#relationship = Relationship.where('friend_id = ? OR user_id = ?', current_user.id, current_user.id).
where( accepted: true)
end
def new
#status = Status.new
end
end
#

Rails Create Related Models together

I have two Models Order(id, user_id,...) and OrderEvent(id, order_id, ...)the data of these are stored in two Objects #order and #order_event.
Now i want to save Order and update the order_id in OrderEvent and
then save the content in the database.
In CakePHP if we had the content structured in a certain way we could save all of these together at once. Is there a way to save the content in a single call so that if there is any validation error both records are not created and fails
Order
--- !ruby/object:Order
attributes:
id:
host_id: 1
user_id: 1
order_no: PH1504-F3D11353
type_of_order: events
order_date: !ruby/object:DateTime 2015-04-17 10:49:52.066168000 Z
sub_total: 7050.0
tax_rate:
rate_cost:
deliver_charges:
discount_code:
discount_rate:
discount_cost:
total_cost: 7050.0
order_dishes_count:
order_events_count:
status: 0
created_at:
updated_at:
OrderEvent
--- !ruby/object:OrderEvent
attributes:
id:
order_id:
event_id: 2
no_of_ppl: 3
event_date: 2015-01-22 00:00:00.000000000 Z
cost: 2350.0
total_cost: 7050.0
status: 0
created_at:
updated_at:
Order
class Order < ActiveRecord::Base
belongs_to :user
belongs_to :host
has_many :order_events
has_many :messages
end
OrderEvent
class OrderEvent < ActiveRecord::Base
belongs_to :event
belongs_to :order
end
If you have set proper associations, following will work for you:
#order = Order.create(...)
#order_event = #order.order_events.create(...) # pass all attrs except id & order_id
EDIT:
If I have the object ready and just wanna save can I use '.save' instead of .create – Harsha M V
In that case, you can directly update #order_event as:
#order_event.update(:order => #order)
Best way is to use Transactions of rails. Reference : Transactions
What you are looking for might be :
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
If you only want to update the OrderEvent just do
class OrderEvent
belongs_to :order
end
And in your controller
#order_event.update_attributes(order: #order)
Edit : update_attributes saves to the database.
See http://apidock.com/rails/ActiveRecord/Persistence/update for more info.
If you have both models ready but none has an id yet then I think you should go for nested attributes :
class Order
accepts_nested_attributes_for :order_events
has_many :order_events
end
class OrderEvent
belongs_to :order
end
I controller, In order to save the Order, with its order_events :
def update
#order.update_attributes(order_events_attributes: [#order_event])
end
Should work like a charm.
Inverse_of and accepts_nested_attributes_for allow you to create two associated objects at the same time.
Your models should be something like:
class Order < ActiveRecord::Base
has_many :order_events, inverse_of: :order
accepts_nested_attributes_for :order_events
end
class OrderEvent < ActiveRecord::Base
belongs_to :order, inverse_of: :order_event
end
In the orders controller:
def new
#order = Order.new
#order.order_events.build
end
def create
#order = Order.new(order_params)
...
end
Allow order_events attributes in the params:
def order_params
params.require(:order).permit(order_event_attributes: [])
end
In the form:
<%= form_for #order do |f| %>
<%= f.fields_for :order_events do |event| %>
<!-- event fields go here -->
<% end %>
Here is an article with more details what invese_of does: http://viget.com/extend/exploring-the-inverse-of-option-on-rails-model-associations

Rails - how to join/group nested tables and get joined values out?

I have the following hierarchy of models where each one has_many of the one below it:
class AccountGroup < ActiveRecord::Base
has_many :accounts, :inverse_of=>:account_group
# name: string
class Account < ActiveRecord::Base
belongs_to :accountGroup, :inverse_of=>:account
has_many :positions, :inverse_of=>:account
class Position < ActiveRecord::Base
belongs_to :account, :inverse_of=>:positions
# net_position: integer
In other words, an AccountGroup contains a bunch of Accounts, and an Account contains a bunch of Positions.
Goal: I want an hash of AccountGroup => (sum of its net_positions). That means there's a GROUP BY involved.
I can do this with raw SQL, but I haven't cracked it with Rails functions. The raw SQL is:
SELECT account_groups.id,SUM(net_position),account_groups.name
FROM account_groups
LEFT JOIN accounts ON accounts.account_group_id = account_groups.id
LEFT JOIN positions ON positions.account_id = accounts.id
GROUP BY account_groups.id,account_groups.name;
Is this something that Rails just can't do?
Rails (4.0.0) can do this - we have two ways to do it currently:
1. SQL "Alias" Columns
Rails Scoping For has_many :through To Access Extra Data
#Images
has_many :image_messages, :class_name => 'ImageMessage'
has_many :images, -> { select("#{Image.table_name}.*, #{ImageMessage.table_name}.caption AS caption") }, :class_name => 'Image', :through => :image_messages, dependent: :destroy
2. ActiveRecord Association Extensions
This is a little-known feature of Rails, which allows you to play with the collection object. The way it does it is to extend the has_many relationship you have created:
class AccountGroup < ActiveRecord::Base
has_many :accounts do
def X
#your code here
end
end
end
We have only got this method working for collections, but you can do all sorts with it. You should look at this tutorial to see more about it
Update
We just got this working by using an extension module:
#app/models/message.rb
Class Message < ActiveRecord::Base
has_many :image_messages #-> join model
has_many :images, through: :image_messages, extend: ImageCaption
end
#app/models/concerns/image_caption.rb
module ImageCaption
#Load
def load
captions.each do |caption|
proxy_association.target << caption
end
end
#Private
private
#Captions
def captions
return_array = []
through_collection.each_with_index do |through,i|
associate = through.send(reflection_name)
associate.assign_attributes({caption: items[i]})
return_array.concat Array.new(1).fill( associate )
end
return return_array
end
#######################
# Variables #
#######################
#Association
def reflection_name
proxy_association.source_reflection.name
end
#Foreign Key
def through_source_key
proxy_association.reflection.source_reflection.foreign_key
end
#Primary Key
def through_primary_key
proxy_association.reflection.through_reflection.active_record_primary_key
end
#Through Name
def through_name
proxy_association.reflection.through_reflection.name
end
#Through
def through_collection
proxy_association.owner.send through_name
end
#Captions
def items
through_collection.map(&:caption)
end
#Target
def target_collection
#load_target
proxy_association.target
end
end
Props to this gist for the variable functions
This basically overrides the load ActiveRecord function in the CollectionProxy class, and uses it to create our own proxy_association.target array :)
If you need any information on how to implement, just ask in the comments
You can make this little bit more prettier than raw sql by using rails AR querying methods:
AccountGroup.
select("account_groups.id, SUM(net_position), account_groups.name").
joins("LEFT JOIN accounts ON accounts.account_group_id = account_groups.id").
joins("LEFT JOIN positions ON positions.account_id = accounts.id").
group("account_groups.id,account_groups.name")
This can be done with pure Arel as well.
AccountGroup.select(
AccountGroup.arel_table[:id], Arel::Nodes::NamedFunction.new('SUM', [:net_position]), AccountGroup.arel_table[:name]
).joins(
AccountGroup.arel_table.join(Account.arel_table).on(
Account.arel_table[:account_group_id].eq(AccountGroup.arel_table[:id])
).join_sources
).joins(
AccountGroup.arel_table.join(Position.arel_table).on(
Position.arel_table[:account_id].eq(Account.arel_table[:id])
).join_sources
).group(
AccountGroup.arel_table[:id], AccountGroup.arel_table[:name]
)
I'm not 100% sure this will work, I simply copied your SQL from above and put it into scuttle.io
Use include function, in example
ac = AccountGroup.all(:include => :account)
$ AccountGroup Load (0.6ms) SELECT `account_groups`.* FROM `groups`
$ Account Load (16.4ms) SELECT `accounts`.* FROM `accounts` WHERE `accounts`.`id` IN (1010, 3, 4, 202, 203, 204, 9999)
Then you can call ac.account.name or something like that
There are a great Railscast http://railscasts.com/episodes/22-eager-loading?view=asciicast
If you really want to use ActiveRecord for this (no SQL), it will be something like:
ags = AccountGroup.all(:include => {:accounts => :positions})
hash = Hash[ags.map { |ag| [ag, ag.map(&:accounts).flatten.map(&:positions).flatten.map(&:net_position).reduce(0,&:+)]}]
But it will be slower than your SQL, and not any prettier.
Is this something that Rails just can't do?
As this question has been open for about a month, I'm gonna to go ahead and assume the answer to this question is...
Yes.
EDIT: Yes, for Rails 3. But Rails 4 can do it! See accepted answer.
Rails can't do it, outside of using find_by_sql or ActiveRecord::Base.connection.execute(query), which are pretty kludgy and not rails-y.

Access rails includes with has_many

I have a model called "Match" and a model called "Bet"
class Match < ActiveRecord::Base
...
has_many :bets
end
And my Model Bet:
class Bet < ActiveRecord::Base
attr_accessible :match_id, :user_id, :bet
...
belongs_to :match
belongs_to :user
end
I'm using the following code to select some matches and user's bets together:
#matches = Match.includes(:bets).where("bets.user_id = ? or bets.user_id is NULL", #user.id)
How can I access user bets with this query?
Using this does not work:
#matches.each do |match|
match.bet.bet
...
How to access bet attribute inside match?
Thanks!!
Trying #sevenseacat answer with this code:
#user ||= User.find_by_id(params[:user_id]) if params[:user_id]
if #user
#matches = Match.includes(:home_team, :away_team, :bets).where("bets.user_id = ? or bets.user_id is NULL", #user.id) #.group_by{ |match| match.date.strftime("%d/%m/%y")}
#matches.each do |match|
match.bets.each do |bet|
bet.bet
end
end
end
I've changed it to match.bets.first (I only have 1 bet for each match_id and user_id so it works).
You would access each match's bets by doing simply match.bets inside your block.
If you wanted to iterate over those bets, use another each.
#sevenseacat is right
#match.bet.each { |bet| bet.attr } maybe good for you

Model Relationship Problem

I am trying to calculate the average (mean) rating for all entries within a category based on the following model associations ...
class Entry < ActiveRecord::Base
acts_as_rateable
belongs_to :category
...
end
class Category < ActiveRecord::Base
has_many :entry
...
end
class Rating < ActiveRecord::Base
belongs_to :rateable, :polymorphic => true
...
end
The rating model is handled by the acts as rateable plugin, so the rateable model looks like this ...
module Rateable #:nodoc:
...
module ClassMethods
def acts_as_rateable
has_many :ratings, :as => :rateable, :dependent => :destroy
...
end
end
...
end
How can I perform the average calculation? Can this be accomplished through the rails model associations or do I have to resort to a SQL query?
The average method is probably what you're looking for. Here's how to use it in your situation:
#category.entries.average('ratings.rating', :joins => :ratings)
Could you use a named_scope or custom method on the model. Either way it would still require some SQL since, if I understand the question, your are calculating a value.
In a traditional database application this would be a view on the data tables.
So in this context you might do something like... (note not tested or sure it is 100% complete)
class Category
has_many :entry do
def avg_rating()
#entries = find :all
#entres.each do |en|
#value += en.rating
end
return #value / entries.count
end
end
Edit - Check out EmFi's revised answer.
I make no promises but try this
class Category
def average_rating
Rating.average :rating,
:conditions => [ "type = ? AND entries.category_id = ?", "Entry", id ],
:join => "JOIN entries ON rateable_id = entries.id"
end
end

Resources