> 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])
Related
current relation is below.
class Hoge < ApplicationRecord
belongs_to :hoge_category
class HogeCategory < ApplicationRecord
belongs_to :big_hoge_category
has_many :hoges
class BigHogeCategory < ApplicationRecord
has_many :hoge_categories
has_many :hoges, through: :hoge_categories
I want to extract Hoge's data which is related HogeCategory table and BigHogeCategory table.
Like this.
HogesController
hoges = Hoge.index( params[:keyword], nil, params[:hoge_category_id], params[:offset].to_i, 10 ).as_json(include: :hoge_category)
render status: 200, json: { hoges: hoges } #send API
Hoge.rb
class Hoge < ApplicationRecord
searchkick callbacks: :async, language: "japanese", word_middle: [:title, :message]
def self.index(keyword, connect_id, hoge_category_id, offset, limit) #execute search
where = {}
if keyword.present?
hoges = Hoge.search(keyword, fields: [:title, :message],misspellings: false, where: where, order: { created_at: :desc }, limit: limit, offset: offset).results
else
hoges = Hoge.search(fields: [:title, :message],misspellings: false, where: where, order: { created_at: :desc }, limit: limit, offset: offset).results
end
return hoges
end
def search_data
{
title: title,
message: message,
created_at: created_at,
hoge_category_id: hoge_category&.id
}
end
end
From my search, it's better to use as_json with include option.
But I don't know how to write when related tables are multiple.
How can I write that?
Or if you have a better idea, let me know please..
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.
I have models Favorite_Photo, User, and Photo
In Heroku Console:
u = User.find(1)
u.favorites.last
=> #<Photo id: 37, user_id: 1, picture: "th.jpeg", title: "Cookies & Cream Pocky ", description: nil, photo_type: nil, location_type: nil, remote_picture_url: nil, created_at: "2016-07-07 03:04:03", updated_at: "2016-07-07 03:04:03">
And If I query:
u = User.find(1)
u.favorite_photos.last
=> #<FavoritePhoto id: 87, photo_id: 12, user_id: 1, created_at: "2016-07-07 19:37:28", updated_at: "2016-07-07 19:37:28">
class User
has_many :favorite_photos
has_many :favorites, through: :favorite_photos, source: :photo
class Photo
has_many :favorite_photos
has_many :favorited_by, through: :favorite_photos, source: :user
class FavoritePhoto
belongs_to :user
belongs_to :photo
validates :user_id, uniqueness: {
scope: [:photo_id],
message: 'can only favorite an item once'
}
UsersController
def show
#user = User.find(params[:id])
#favorites = #user.favorites
end
This returns a list of favorites ordered by photo_id. I want to create a scope that will order the favorites based on FavoritePhoto id:
has_many :favorites, -> { order("favorite_photos.id ASC") }, through: :favorite_photos, source: :photo
reference: scopes for has_many
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
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 )