I have a behavior about the one to many relationship that I don't get and it definitely turns me nuts.
Here is model 1:
class Account < ActiveRecord::Base
belongs_to :organization, :autosave => true
validates :organization, :presence => true
end
Here is model 2:
class Organization < ActiveRecord::Base
has_many :accounts, :autosave => true
validates :accounts, :presence => true
end
Now, in a rails console:
>> acc = Account.new
>> org = Organization.new
>> org.accounts << acc
>> org.accounts
[#<Account id: nil, organization_id: nil, created_at: nil, updated_at: nil>]
>> acc.organization
nil
or the other way around:
>> acc = Account.new
>> org = Organization.new
>> acc.organization = org
>> acc.organization
#<Organization id: nil, created_at: nil, updated_at: nil>
>> organization.accounts
[]
Is this normal behavior? Should I manually update both sides of the relationship?!
the answer is simple just save the object first
acc = Account.new
org = Organization.new
acc.organization = org
acc.save
Ref this use build
org = Organization.new
acc = org.build_account.new
org.save
Related
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
> 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])
class Subject
has_many :subject_attribute_types
has_many :subject_attributes
accepts_nested_attributes_for :subject_attributes
end
class SubjectAttributeType
belongs_to :subject
has_many :subject_attributes
attr_accessible :type_name
end
class SubjectAttribute
belongs_to :subject
belongs_to :subject_attribute_type
attr_accessible :value
end
For example:
s1 = Subject.create()
s2 = Subject.create()
sat1 = SubjectAttributeType.create(subject: s1, name: 'Age')
sat2 = SubjectAttributeType.create(subject: s1, name: 'Sex')
sat3 = SubjectAttributeType.create(subject: s2, type_name: 'Age')
sat5 = SubjectAttributeType.create(subject: s2, type_name: 'Username')
SubjectAttribute.create(subject: s1, subject_attribute_type: sat1, value: 20)
SubjectAttribute.create(subject: s1, subject_attribute_type: sat2, value: "male")
SubjectAttribute.create(subject: s2, subject_attribute_type: sat3, value: 21)
SubjectAttribute.create(subject: s2, subject_attribute_type: sat1, value: "user1")
Problem:
What's the best practice to make a search on exact subject_attributes.
If i want to find all Subjects with age >= 18 and nickname like %user%
currently i am using ransack gem, but i can't think out how to make a search on nested_attributes
I see there is a problem in business logic of your app. Why would you need your AttributeType to know about any of subject?
class Subject < ActiveRecord::Base
has_many :subject_attributes
has_many :attribute_types, through: :subject_attributes
end
class SubjectAttribute < ActiveRecord::Base
belongs_to :attribute_type
belongs_to :subject
attr_accessible :attribute_type_id, :subject_id, :value
end
class AttributeType < ActiveRecord::Base
attr_accessible :type_name
end
After that if you insert some data:
s1 = Subject.create
s2 = Subject.create
sat1 = AttributeType.create(type_name: "Age")
sat2 = AttributeType.create(type_name: "Sex")
sat3 = AttributeType.create(type_name: "Username")
SubjectAttribute.create(subject:s1, attribute_type:sat1, value: 20)
SubjectAttribute.create(subject:s1, attribute_type:sat2, value:"male")
SubjectAttribute.create(subject:s2, attribute_type:sat1, value:21)
SubjectAttribute.create(subject:s2, attribute_type:sat3, value:"user1")
you will be able to make selects.
In your example you use several attributes, so you have to make several requests:
that way you'll find subject with value name:
names = Subject.joins(:attribute_types).where("attribute_types.type_name = 'Username'
and value like '%user%'")
=> [#<Subject id: 2, created_at: "2013-05-29 11:11:51", updated_at: "2013-05-29 11:11:51">]
that way you'll find subject with value age
ages = Subject.joins(:attribute_types).where("attribute_types.type_name = 'Age'
and value >= 18")
=> [#<Subject id: 1, created_at: "2013-05-29 11:11:42", updated_at: "2013-05-29 11:11:42">,
#<Subject id: 2, created_at: "2013-05-29 11:11:51", updated_at: "2013-05-29 11:11:51">]
That way you'll find intersected subjects
subjects = (names&ages)
=> [#<Subject id: 2, created_at: "2013-05-29 11:11:51", updated_at: "2013-05-29 11:11:51">]
Using dynamic attribute_types makes select really hard. so if you ok with making separate request for each type-value params, use it. Otherwise maybe its really just columns of Subjects?
Given this class:
class MyModel < ActiveRecord::Base
belongs_to :association1
belongs_to :association2, :polymorphic => true
end
I know that when I set association1, it sets association1_id to the ID of object 1
m = MyModel.new
m.association1 = object1
#<MyModel id: nil, association1_id: 1, association2_id: nil, association2_type: nil>
I know that when I set association2, it sets association2_id AND association2_type
m.association2 = object2
#<MyModel id: nil, association1_id: 1, association2_id: 2, association2_type: 'ClassType'>
My question is:
Is there a function that can easily tell me what information is being set on an object in hash form?
MyModel.magic_function(:association1, object1)
# returns {:association1_id => 1}
MyModel.magic_function(:association2, object2)
# returns {:association2_id => 2, :association2_type => 'ClassType'}
Perhaps you're looking for changes:
person = Person.new
person.changes # => {}
person.name = 'bob'
person.changes # => { 'name' => [nil, 'bob'] }
This is the stop gap solution I have for now, just though I'd share:
def self.magic_method(association, object)
instance = self.new
instance.send(association, object)
h = Hash.new
instance.changes.each do |k,v|
h[k] = v[1]
end
h
end
Is this built into rails somewhere?
The problem I am having with this is Product is trying to create variants before the product is even created and there are certain callbacks for variants that require the product to exist. So how can I rewrite this so that v.save doesn't execute till the object is created or whatever.
Product.class_eval do
validates_presence_of [:style_no, :market_price, :designer, :short_description, :description]
validates_numericality_of [:size_47_quantity,
:size_46_quantity,
:size_45_quantity,
:size_44_quantity,
:size_43_quantity,
:size_42_quantity,
:size_41_quantity,
:size_40_quantity,
:size_39_quantity]
for i in 39..47
define_method:"size_#{i}_quantity" do
if v = self.variants.find_by_size(i)
v.count_on_hand
else
0
end
end
define_method:"size_#{i}_quantity=" do |amount|
# if only there is some method that can postpone all the following if this product hasn't been created yet!
self.id = Product.last.id + 1 unless self.id
v = self.variants.find_by_size(i) || self.variants.new(:size => i)
v.count_on_hand = amount
v.save
end
end
end
You can try this solution:
Product class
class Product < ActiveRecord::Base
validates_presence_of [:style_no, :market_price, :designer, :short_description, :description]
has_many :variants
# This method would check if variant was created or loaded.
#
# So many sequantial calls to it will return same object
def variant_with_size(size)
self.variants.select{|v| v.size == size}.first || self.variants.where('size = ?', size).first
end
module ClassExtensions
def self.included(base)
(39..47).each do |i|
method = "size_#{i}_quantity".to_sym
included_module = Module.new
included_module.module_eval <<EOF
def #{method}
if v = self.variant_with_size(#{i})
v.count_on_hand
else
0
end
end
def #{method}=(amount)
v = self.variant_with_size(#{i}) || self.variants.build(:size => #{i})
v.count_on_hand = amount
v
end
EOF
base.send :include, included_module
end
end
end
include ClassExtensions
end
Variant class
class Variant < ActiveRecord::Base
belongs_to :product
validates :count_on_hand, :numericality => true
end
Usage
Usage example with correct variant amount:
ruby-1.9.2-p180 :001 > p = Product.new
=> #<Product id: nil, style_no: nil, market_price: nil, designer: nil, short_description: nil, description: nil, created_at: nil, updated_at: nil>
ruby-1.9.2-p180 :002 > p.size_39_quantity
=> 0
ruby-1.9.2-p180 :003 > p.size_39_quantity = 2
=> 2
ruby-1.9.2-p180 :004 > p.variants
=> [#<Variant id: nil, product_id: nil, size: 39, count_on_hand: 2, created_at: nil, updated_at: nil>]
ruby-1.9.2-p180 :005 > p.save
=> true
ruby-1.9.2-p180 :006 > p.variants
=> [#<Variant id: 3, product_id: 3, size: 39, count_on_hand: 2, created_at: "2011-04-06 06:34:46", updated_at: "2011-04-06 06:34:46">]
Usage with incorrect variant amount:
ruby-1.9.2-p180 :007 > p1 = Product.new
=> #<Product id: nil, style_no: nil, market_price: nil, designer: nil, short_description: nil, description: nil, created_at: nil, updated_at: nil>
ruby-1.9.2-p180 :008 > p1.size_39_quantity = 'A'
=> "A"
ruby-1.9.2-p180 :009 > p1.save
=> false
ruby-1.9.2-p180 :010 > p1.errors
=> {:variants=>["is invalid"]}
ruby-1.9.2-p180 :011 > p1.variants[0].errors
=> {:count_on_hand=>["is not a number"]}
At a glance, I'd consider using an after_save callback on Product to create product variants.
Something like:
class Product < ActiveRecord::Base
has_many :variants
after_save :create_variants! if :not_a_variant?
OPTIONS = [:size_1_qty, :size_2_qty] # TODO: move to a OptionType model associated with Product
def not_a_variant?
size.nil? # or however you might distinguish a Product from a Variant
end
private
def create_variants!
# OPTIONS could instead be related option_types. perhaps a 'size' option type with values of 40, 41, 42, etc.
OPTIONS.each do |size|
variants.build(...)
end
save!
end
end
I was just reviewing the Spree shopping cart project by Rails Dog and they handle product variants in a similar fashion. You might check it out.