Create association between two instancied objects - ruby-on-rails

I have two models: (Albums and Product)
1) Inside Models
Inside album.rb:
class Album < ActiveRecord::Base
attr_accessible :name
has_many :products
end
Inside product.rb:
class Product < ActiveRecord::Base
attr_accessible :img, :name, :price, :quantity
belongs_to :album
end
2) Using "rails console", how can I set the associations (so I can use "<%= Product.first.album.name %>")?
e.g.
a = Album.create( :name => "My Album" )
p = Product.create( :name => "Shampoo X" )
# what's next? how can i set the album and the product together?

You can do like this:
a = Album.create( name: "My Album" )
p = Product.create( name: "Shampoo X" )
# OR
p = Product.create( name: "Shampoo X", album_id: a.id )
# OR
p.album = a
# OR
p.album_id = a.id
# OR
a.products << a
# finish with a save of the object:
p.save
You may have to set the attribute accessible to album_id on the Product model (not sure about that).
Check #bdares' comment also.

Add the association when you create the Product:
a = Album.create( :name => "My Album" )
p = Product.create( :name => "Shampoo X", :album => a )

Related

how to use an instance of a model in another serializer

I'm stuck here and couldn't find solution to proceed my work,
I have 3 models: plans, days, and meals.
This is my Plan Controller I've managed to get the correct answer in the controller, I want it nested and inside the serializer because I'm using URL helper to retrieve my images URLs, is there a possible way to use the #plan.id inside the DaySerializer?
def meals
#plan = Plan.find(params[:id])
#days = #plan.days
#meals = Meal.where("plan_id = ? ", #plan.id)
render :json => { :plan => #plan, :days => #days,
:meals => #meals }
end
This is my Plan model
class Plan < ApplicationRecord
has_many :days
has_one_attached :image, dependent: :destroy
end
This is my Day model
class Day < ApplicationRecord
has_many :meals
has_many :plans
end
This is my Meal model
class Meal < ApplicationRecord
belongs_to :plan
belongs_to :day
has_one_attached :image, dependent: :destroy
end
I want to show all meals for a specific Plan, to do that I need to use a variable inside the daySerializer but I couldn't find how to do it.
This is my planSerializer
class PlanSerializer < ActiveModel::Serializer
attributes :id, :name, :monthly_price, :plan_days
def plan_days
object.days.map do |day|
DaySerializer.new(day, scope: scope, root: false, event: object)
end
end
end
and this is my DaySerializer which I need to use the instance of the plan inside
class DaySerializer < ActiveModel::Serializer
attributes :number, :plan_meals
def plan_meals
#how to be able to use this line in Serilizer? !important
#plan = Plan.find(params[:id])
object.meals.map do |meal|
if meal.plan_id == #plan.id
MealSerializer.new(meal, scope: scope, root: false, event: object)
end
end
end
end
target reason response :
{
id: 8,
name: "Plan1",
monthly_price: 88,
plan_days: [
{
number: 5,
plan_meals: [],
},
{
number: 4,
plan_meals: [],
},
{
number: 3,
plan_meals: [],
},
{
number: 2,
plan_meals: [],
},
{
number: 1,
plan_meals: [
{
id: 11,
name: "test meal",
calories: 32,
protein: 32,
fat: 32,
carbohydrates: 32,
plan_id: 8,
},
],
},
],
}
currently it's showing all meals that belongs to each day,
not only the meals with the plan_id = Plan.find(params[:id])
In general I think you could use something like this should work.
ActiveModel::Serializer::CollectionSerializer.new. It actually by itself allows you to pass additional information to your serializer. It does the same as your current code just you are able to explicitly pass new data.
Controller:
def meals
#plan = Plan.find(params[:id])
#days = #plan.days
#meals = Meal.where("plan_id = ? ", #plan.id)
render :json => {
:plan => #plan,
:days => ActiveModel::Serializer::CollectionSerializer.new(#days, serializer: DaySerializer, plan_id: #plan.id),
:meals => #meals
}
end
And then in DaySerializer:
class DaySerializer < ActiveModel::Serializer
attributes :number, :plan_meals
def plan_meals
object.meals.map do |meal|
if meal.plan_id == instance_options[:plan_id]
MealSerializer.new(meal, scope: scope, root: false, event: object)
end
end
end
end
So in short ActiveModel::Serializer::CollectionSerializer.new in controller and instance_options in serializer to access passed additional parameters.
UPDATED:
How about add meal serializer?
class MealSerializer < ActiveModel::Serializer
attributes :id, :name, :calories, :protein, :fat, # etc
end
class DaySerializer < ActiveModel::Serializer
attributes :number
has_many :meals, serializer: MealSerializer
end
ORIGINAL:
class PlanSerializer < ActiveModel::Serializer
attributes :id, :name, :monthly_price, :plan_days
has_many :plan_days, serializer: DaySerializer
end
something like this.

How can you clone records and their relationships in rails?

I have an App with og_objects, og_actions, and stories
I have created a way to create a clone of this app.
I am now trying to duplicate the og_objects, og_actions and stories into the clone, but I am getting stuck.
I am getting stuck in two places.
1. When I create a clone, the objects,actions,stories get moved to the
new clone, but they are not duplicated, meaning the parent app loses
them.
2. I get an error that my new clone does not have og_objects property. Specifically the error is:
ActiveModel::MissingAttributeError (can't write unknown attribute `og_actions'):
app/models/app.rb:39:in `block in populate'
app/models/app.rb:36:in `populate'
app/models/app_generator.rb:15:in `generate'
app/controllers/apps_controller.rb:12:in `create'
This is the code I have:
class App < ActiveRecord::Base
validates :name, :presence => true, :uniqueness => {:scope => :dev_mode}
validates :user_id, :presence => true
before_validation :validate_app
belongs_to :user
has_many :og_actions
has_many :og_objects
has_many :stories
has_many :log_reports
has_many :clones, class_name: "App", foreign_key: "parent_id"
belongs_to :parent_app, class_name: "App"
def self.dev_modes
['development', 'stage', 'production']
end
def is_clone?
!!self.parent_id
end
def clone_ids
if !is_clone?
self.clones.all.map(&id)
end
end
def populate
p "doing populate"
p self.clones
p "original clones"
new_og_actions = self.og_actions.dup
new_og_objects = self.og_objects.dup
new_stories = self.stories.dup
self.clones.each do |c|
p "updating ->"
p c
c[:og_actions] = new_og_actions
c[:og_objects] = new_og_objects
c[:stories] = new_stories
end
end
#Other irrelevant code here:
In my controller, I have a generator, and I have the following code:
if #parent_id.length > 0
parent_app = App.find(#parent_id)
App.create_with(og_actions: parent_app.og_actions.dup, og_objects: parent_app.og_objects.dup, stories:parent_app.stories.dup, parent_id: #parent_id, user_id: #user_id, app_id:#app_id, app_secret: #app_secret).find_or_create_by!(name: #name, dev_mode: 'clone')
parent_app.populate
else
To get this to work, I made use of the first_or_initialize method, and created the relationships if I couldn't find a record, otherwise I updated the record.
I changed the populate method to the following:
def populate
p "doing populate"
p self.clones
p "original clones"
new_og_objects = self.og_objects.dup
new_stories = self.stories.dup
self.clones.each do |c|
p "updating ->"
self.og_actions.each do | oa |
new_og_actions = OgAction.create(oa.attributes.merge({app_id:c.id, id: nil }))
c.og_actions.first_or_initialize(new_og_actions)
end
self.og_objects.each do |oo|
new_og_objects = OgObject.create(oo.attributes.merge({app_id:c.id, id: nil }))
c.og_objects.first_or_initialize(new_og_objects)
end
self.stories.each do | s|
new_stories = Story.create(s.attributes.merge({app_id:c.id, id: nil }))
c.stories.first_or_initialize(new_stories)
end
p c
end
end
I also removed the creation of these symbols in the AppGenerator like this:
if #parent_id.length > 0
parent_app = App.find(#parent_id)
App.create_with(parent_id: #parent_id, user_id: #user_id, app_id:#app_id, app_secret: #app_secret).find_or_create_by!(name: #name, dev_mode: 'clone')
parent_app.populate
else

How to write joins Rails ActiveRecord_Relation?

> u = User.first
> u.viewable_cars
OR
> Car.for(u)
would get me just the cars the user has permission to view but not the cars he owns! SQL in irb for both u.viewable_cars & Car.for(u), which is the same, cars with id 1 to 50 which belongs to user don't get called at all:
SELECT "cars".* FROM "cars" INNER JOIN "permissions" ON "permissions"."thing_id" = "cars"."id" AND "permissions"."thing_type" = $1 WHERE ((cars.user_id = 1) OR (permissions.action = 'view' AND permissions.user_id = 1)) ORDER BY created_at DESC [["thing_type", "Car"]]
=> #<ActiveRecord::Relation [#<Car id: 52, content: "sport edition", name: "BMW", user_id: 2, created_at: "2014-11-01 04:58:19", updated_at: "2014-11-01 04:58:19">, #<Car id: 51, content: "super sport car", name: "BMW M6", user_id: 3, created_at: "2014-11-01 04:44:31", updated_at: "2014-11-01 04:44:31">]>
class Car < ActiveRecord::Base
belongs_to :user
has_many :permissions, as: :thing
default_scope -> { order('created_at DESC') }
validates :user_id, presence: true
validates :name, presence: true, length: { maximum: 50 }, uniqueness: true
validates :content, length: { maximum: 300 }, allow_blank: true
scope :viewable_by, ->(user) do
joins(:permissions).where(permissions: { action: "view",
user_id: user.id })
end
scope :for, ->(user) do
joins(:permissions).
where("(cars.user_id = :user_id) OR (permissions.action = 'view' AND permissions.user_id = :user_id)", user_id: user.id)
end
end
class User < ActiveRecord::Base
...
has_many :cars, dependent: :destroy
has_many :permissions
has_many :viewable_cars, ->(user) { joins(:permissions).
where("(cars.user_id = :user_id) OR (permissions.action = 'view' AND permissions.user_id = :user_id)", user_id: user.id) },
class_name: "Car"
def viewable_cars
Car.for(self)
end
end
class Permission < ActiveRecord::Base
belongs_to :user
belongs_to :thing, polymorphic: true
end
Rails.application.routes.draw do
resources :users do
resources :cars
end
end
Your scope for in car.rb should be this:
scope :for, ->(user) do
joins("LEFT OUTER JOIN permissions ON permissions.thing_id = cars.id AND permissions.thing_type = 'Car'").where("(cars.user_id = :user_id) OR (permissions.action = 'view' AND permissions.user_id = :user_id)", user_id: user.id)
end
Now you can do: Car.for(current_user).find(params[:id]). However, this looks like an antipattern to me. So you can create another association in user.rb like this:
Rails 4:
has_many :viewable_cars, ->(user) {
joins("LEFT OUTER JOIN permissions ON permissions.thing_id = cars.id AND permissions.thing_type = 'Car'").
where("(cars.user_id = :user_id) OR (permissions.action = 'view' AND permissions.user_id = :user_id)", user_id: user.id) },
class_name: 'Car'
Rails 3(couldn't find a better way of doing it, e.g.: associations):
def viewable_cars
Car.for(self)
end
So that you can fetch all cars for user:
current_user.viewable_cars
In controller:
#car = current_user.viewable_cars.find(params[:id])

Rails - How to a validate model being altered in before filter from a different model?

Models - Purchaseorder, Purchaseorderadjustments, Productvariant, Location, Locationinventory
I'm storing inventory in Locationinventory which stores a location_id, productvariant_id, and quantity.
The situation arises when I want to create a purchaseorder. I'm using purchaseorderadjustments as a nested attribute to the purchaseorder. A purchaseorder has_many purchaseorderadjustments that store the productvariant_id and quantity.
I'm using before filters to create,update and destroy the related locationinventory records. Everything works well as it is now except that you can remove items from a location that doesn't have them available and the quantity just goes into the negative. I want to verify that the "From Location" has enough of the productvariant in stock to transfer to the "To Location".
Am I doing it wrong? thanks!
Rails 3.2.14
Purchaseorder.rb
class Purchaseorder < ActiveRecord::Base
attr_accessible :fromlocation_id, :status_id, :tolocation_id, :user_id, :purchaseorderadjustments_attributes
belongs_to :user
belongs_to :status
belongs_to :fromlocation, :class_name => "Location", :foreign_key => :fromlocation_id
belongs_to :tolocation, :class_name => "Location", :foreign_key => :tolocation_id
has_many :purchaseorderadjustments, :dependent => :destroy
accepts_nested_attributes_for :purchaseorderadjustments, allow_destroy: true
end
Purchaseorderadjustment.rb
class Purchaseorderadjustment < ActiveRecord::Base
attr_accessible :adjustmenttype_id, :productvariant_id, :purchaseorder_id, :quantity
belongs_to :purchaseorder
belongs_to :productvariant
belongs_to :adjustmenttype
validates_presence_of :quantity, :message => "You need a quantity for each product."
# On creation of a purchaseorderadjustment go ahead and create the record for locationinventory
before_create :create_locationinventory
def create_locationinventory
# Get some info before updating the locationinventory
if fromlocationinventory = Locationinventory.find(:first, conditions: { :location_id => purchaseorder.fromlocation_id, :productvariant_id => productvariant_id })
fromlocation_current_quantity = fromlocationinventory.quantity
end
if tolocationinventory = Locationinventory.find(:first, conditions: { :location_id => purchaseorder.tolocation_id, :productvariant_id => productvariant_id })
tolocation_current_quantity = tolocationinventory.quantity
end
# Create or update the from locationinventory
unless fromlocationinventory.nil?
fromlocationinventory.quantity = fromlocation_current_quantity - quantity
fromlocationinventory.save
else
new_fromlocationinventory = Locationinventory.new({ location_id: purchaseorder.fromlocation_id, productvariant_id: productvariant_id, quantity: 0 - quantity })
new_fromlocationinventory.save
end
# Create or update the to locationinventory
unless tolocationinventory.nil?
tolocationinventory.quantity = tolocation_current_quantity + quantity
tolocationinventory.save
else
new_tolocationinventory = Locationinventory.new({ location_id: purchaseorder.tolocation_id, productvariant_id: productvariant_id, quantity: quantity })
new_tolocationinventory.save
end
end
#On update of purchaseorderadjustment
before_update :update_locationinventory
def update_locationinventory
# Get some info before updating the locationinventory
fromlocationinventory = Locationinventory.find(:first, conditions: { :location_id => purchaseorder.fromlocation_id, :productvariant_id => productvariant_id })
tolocationinventory = Locationinventory.find(:first, conditions: { :location_id => purchaseorder.tolocation_id, :productvariant_id => productvariant_id })
fromlocation_current_quantity = fromlocationinventory.quantity
tolocation_current_quantity = tolocationinventory.quantity
fromlocationinventory.quantity = fromlocation_current_quantity - quantity + self.quantity_was
fromlocationinventory.save
tolocationinventory.quantity = tolocation_current_quantity + quantity - self.quantity_was
tolocationinventory.save
end
#On destroy of purchaseorderadjustment
before_destroy :destroy_locationinventory
def destroy_locationinventory
# Get some info before updating the locationinventory
fromlocationinventory = Locationinventory.find(:first, conditions: { :location_id => purchaseorder.fromlocation_id, :productvariant_id => productvariant_id })
tolocationinventory = Locationinventory.find(:first, conditions: { :location_id => purchaseorder.tolocation_id, :productvariant_id => productvariant_id })
fromlocation_current_quantity = fromlocationinventory.quantity
tolocation_current_quantity = tolocationinventory.quantity
fromlocationinventory.quantity = fromlocation_current_quantity + quantity
fromlocationinventory.save
tolocationinventory.quantity = tolocation_current_quantity - quantity
tolocationinventory.save
end
end
productvariant.rb
class Productvariant < ActiveRecord::Base
attr_accessible :barcode, :compare_at_price, :fulfillment_service, :grams,
:inventory_management, :inventory_policy, :inventory_quantity,
:option1, :option2, :option3, :position, :price, :product_id,
:requires_shipping, :shopify_id, :sku, :taxable, :title, :shopify_product_id, :product_title
belongs_to :product, primary_key: "shopify_id", foreign_key: "shopify_product_id"
has_many :purchaseorderadjustments
has_many :locationinventories
def product_plus_variant
"#{self.product.title} - #{self.title}"
end
end
locationinventory.rb
class Locationinventory < ActiveRecord::Base
attr_accessible :location_id, :productvariant_id, :quantity
belongs_to :productvariant
belongs_to :location
end
I'll write this answer because I feel you've provided so much code, you might have scared some answerers away!
Our experience is as follows:
Nested
You can validate nested models in several different ways
Your question is related to passing data in a accepts_nested_attributes_for - you can validate this directly:
#app/models/purchase.rb
Class Purchase < ActiveRecord::Base
has_many :purchase_items
accepts_nested_attributes_for :purchase_items
end
#app/models/purchase_item.rb
Class PurchaseItem < ActiveRecord::Base
belongs_to :purchase
validates :name,
presence: { message: "Your Purchase Needs Items!" } #Returns to initial form with this error
end
Standard
If you want to conditionally validate based on another model, you'll have to use inverse_of: to keep the object available throughout the data transaction:
#app/models/purchase.rb
Class Purchase < ActiveRecord::Base
has_many :purchase_items, inverse_of: :purchase
accepts_nested_attributes_for :purchase_items
end
#app/models/purchase_item.rb
Class PurchaseItem < ActiveRecord::Base
belongs_to :purchase, inverse_of: :purchase_items
validates :name,
presence: { message: "Your Purchase Needs Items!" },
if: :paid_with_card?
private
def paid_with_card?
self.purchase.payment_method == "card"
end
end

Retrieve all association's attributes of an AR model?

What do you think is the most optimal way to retrieve all attributes for all the associations an AR model has?
i.e: let's say we have the model Target.
class Target < ActiveRecord::Base
has_many :countries
has_many :cities
has_many :towns
has_many :colleges
has_many :tags
accepts_nested_attributes_for :countries, :cities, ...
end
I'd like to retrieve all the association's attributes by calling a method on a Target instance:
target.associations_attributes
>> { :countries => { "1" => { :name => "United States", :code => "US", :id => 1 },
"2" => { :name => "Canada", :code => "CA", :id => 2 } },
:cities => { "1" => { :name => "New York", :region_id => 1, :id => 1 } },
:regions => { ... },
:colleges => { ... }, ....
}
Currently I make this work by iterating on each association, and then on each model of the association, But it's kind of expensive, How do you think I can optimize this?
Just a note: I realized you can't call target.countries_attributes on has_many associations with nested_attributes, one_to_one associations allow to call target.country_attributes
I'm not clear on what you mean with iterating on all associations. Are you already using reflections?
Still curious if there's a neater way, but this is what I could come up with, which more or less results in the hash you're showing in your example:
class Target < ActiveRecord::Base
has_many :tags
def associations_attributes
# Get a list of symbols of the association names in this class
association_names = self.class.reflect_on_all_associations.collect { |r| r.name }
# Fetch myself again, but include all associations
me = self.class.find self.id, :include => association_names
# Collect an array of pairs, which we can use to build the hash we want
pairs = association_names.collect do |association_name|
# Get the association object(s)
object_or_array = me.send(association_name)
# Build the single pair for this association
if object_or_array.is_a? Array
# If this is a has_many or the like, use the same array-of-pairs trick
# to build a hash of "id => attributes"
association_pairs = object_or_array.collect { |o| [o.id, o.attributes] }
[association_name, Hash[*association_pairs.flatten(1)]]
else
# has_one, belongs_to, etc.
[association_name, object_or_array.attributes]
end
end
# Build the final hash
Hash[*pairs.flatten(1)]
end
end
And here's an irb session through script/console to show how it works. First, some environment:
>> t = Target.create! :name => 'foobar'
=> #<Target id: 1, name: "foobar">
>> t.tags.create! :name => 'blueish'
=> #<Tag id: 1, name: "blueish", target_id: 1>
>> t.tags.create! :name => 'friendly'
=> #<Tag id: 2, name: "friendly", target_id: 1>
>> t.tags
=> [#<Tag id: 1, name: "blueish", target_id: 1>, #<Tag id: 2, name: "friendly", target_id: 1>]
And here's the output from the new method:
>> t.associations_attributes
=> {:tags=>{1=>{"id"=>1, "name"=>"blueish", "target_id"=>1}, 2=>{"id"=>2, "name"=>"friendly", "target_id"=>1}}}
try this with exception handling:
class Target < ActiveRecord::Base
def associations_attributes
tmp = {}
self.class.reflections.symbolize_keys.keys.each do |key|
begin
data = self.send(key) || {}
if data.is_a?(ActiveRecord::Base)
tmp[key] = data.attributes.symbolize_keys!
else
mapped_data = data.map { |item| item.attributes.symbolize_keys! }
tmp[key] = mapped_data.each_with_index.to_h.invert
end
rescue Exception => e
tmp[key] = e.message
end
end
tmp
end
end
This is updated version of Stéphan Kochen's code for Rails 4.2
def associations_attributes
association_names = self.class.reflect_on_all_associations.collect { |r| r.name }
me = self.class.includes(association_names).find self.id
pairs = association_names.collect do |association_name|
object_or_array = me.send(association_name)
if object_or_array.is_a? ActiveRecord::Associations::CollectionProxy
association_pairs = object_or_array.collect { |o| [o.id, o.attributes] }
[association_name, Hash[*association_pairs.flatten(1)]]
else
[association_name, object_or_array.attributes]
end
end
Hash[*pairs.flatten(1)]
end

Resources