Help me find out why new and build method doesnt work - ruby-on-rails

I have problem with my associations. I have n:n relation and everything going good but if i want initialize new object and then save it, it will by save with out associations. For example.
Models:
class User
has_many :users_in_organizations, :class_name => 'UserInOrganization'
has_many :organizations,:through => :users_in_organizations
end
#Attributes [:user_id, :organization_id, :user_role]
class UserInOrganization
set_table_name 'users_in_organizations'
belongs_to :user
belongs_to :organization
end
class Organization
has_many :users_in_organizations, :class_name => 'UserInOrganization'
has_many :users, :through => :users_in_organizations
end
this work fine but the problem is
org = User.first.organizations.new(:name => 'Test') # new || build is the same
org.save # => true
User.first.organizations # => []
Organization.all # => ['Test']
but if I use create then it works
org = User.first.organizations.create(:name => 'Test')
User.first.organizations # => ['Test']
Organization.all # => ['Test']
Can anybody tell me what i am doing wrong?
Thank You :)

If you want it working for new method, try this:
u = User.first
u.organizations.new :name => "new organozation"
u.save
u.organizations.size
=> 1
When you do org = User.first.organizations.new :name => "test" then you assign to org only organization and you save only that object. It doesn't save associated objects. That's why it doesn't work that way.
When you call create it saves created objects to db, using new or build doesn't save it to db.

Related

How to prevent duplicate records in a Join Table

I'm quite new to Ruby and Rails so please bear with me.
I have two models Player, and Reward joined via a has_many through relationship as below. My Player model has an attribute points. As a player accrues points they get rewards. What I want to do is put a method on the Player model that will run before update and give the appropriate reward(s) for the points they have like below.
However I want to do it in such a way that if the Player already has the reward it won't be duplicated, nor cause an error.
class Player < ActiveRecord::Base
has_many :earned_rewards, -> { extending FirstOrBuild }
has_many :rewards, :through => :earned_rewards
before_update :assign_rewards, :if => :points_changed?
def assign_rewards
case self.points
when 1000
self.rewards << Reward.find_by(:name => "Bronze")
when 2000
self.rewards << Reward.find_by(:name => "Silver")
end
end
class Reward < ActiveRecord::Base
has_many :earned_rewards
has_many :players, :through => :earned_rewards
end
class EarnedReward < ActiveRecord::Base
belongs_to :player
belongs_to :reward
validates_uniqueness_of :reward_id, :scope => [:reward_id, :player_id]
end
module FirstOrBuild
def first_or_build(attributes = nil, options = {}, &block)
first || scoping{ proxy_association.build(attributes, &block) }
end
end
You should validate it in db also
Add follwing in migrate file-
add_index :earnedrewards, [:reward_id, :player_id], unique: true
EDIT:
I've realised that my previous answer wouldn't work, as the new Reward is not associated to the parent Player model.
In order to correctly associate the two, you need to use build.
See https://stackoverflow.com/a/18724458/4073431
In short, we only want to build if it doesn't already exist, so we call first || build
Specifically:
class Player < ActiveRecord::Base
has_many :earned_rewards
has_many :rewards, -> { extending FirstOrBuild }, :through => :earned_rewards
before_update :assign_rewards, :if => :points_changed?
def assign_rewards
case self.points
when 1000...2000
self.rewards.where(:name => "Bronze").first_or_build
when 2000...3000
self.rewards.where(:name => "Silver").first_or_build
end
end
class Reward < ActiveRecord::Base
has_many :earned_rewards
has_many :players, :through => :earned_rewards
end
class EarnedReward < ActiveRecord::Base
belongs_to :player
belongs_to :reward
validates_uniqueness_of :reward_id, :scope => [:reward_id, :player_id]
end
module FirstOrBuild
def first_or_build(attributes = nil, options = {}, &block)
first || scoping{ proxy_association.build(attributes, &block) }
end
end
When you build an association, it adds it to the parent so that when the parent is saved, the child is also saved. E.g.
pry(main)> company.customers.where(:fname => "Bob")
Customer Load (0.1ms) SELECT "customers".* FROM "customers"
=> [] # No customer named Bob
pry(main)> company.customers.where(:fname => "Bob").first_or_build
=> #<Customer id: nil, fname: "Bob"> # returns you an unsaved Customer
pry(main)> company.save
=> true
pry(main)> company.reload.customers
=> [#<Customer id: 1035, fname: "Bob">] # Bob gets created when the company gets saved
pry(main)> company.customers.where(:fname => "Bob").first_or_build
=> #<Customer id: 1035, fname: "Bob"> # Calling first_or_build again will return the first Customer with name Bob
Since our code is running in a before_update hook, the Player will be saved as well as any newly built Rewards as well.

ActiveRecord (Rails 2.3.8) - Update existing, add new record when updating nested attributes

I have a "user" model that "has_one" "membership" (active at a time). For auditing and data integrity reasons, I'd like it so that if the membership changes for a user, the old/current record (if existing) has an inactive/active flag swapped, and a new row is added for the new changed record. If there are no changes to the membership, I'd like to just ignore the update. I've tried implementing this with a "before_save" call-back on my user model, but have failed many times. Any help is greatly appreciated.
models:
class User < ActiveRecord::Base
has_one :membership, :dependent => :destroy
accepts_nested_attributes_for :membership, :allow_destroy => true
end
class Membership < ActiveRecord::Base
default_scope :conditions => {:active => 1}
belongs_to :user
end
I have what I think is a pretty elegant solution. Here's your user model:
class User < ActiveRecord::Base
has_one :membership, :dependent => :destroy
accepts_nested_attributes_for :membership
def update_membership_with_history attributes
self.membership.attributes = attributes
return true unless self.membership.changed?
self.membership.update_attribute(:active, false)
self.build_membership attributes
self.membership.save
end
end
This update_membership_with_history method allows us to handle changed or unchanged records. Next the membership model:
class Membership < ActiveRecord::Base
default_scope :conditions => {:active => true}
belongs_to :user
end
I changed this slightly, since active should be a boolean, not 1's and 0's. Update your migration to match. Now the update action, which is the only part of your scaffold that needs to change:
def update
#user = User.find(params[:id], :include => :membership)
membership_attributes = params[:user].delete(:membership_attributes)
if #user.update_attributes(params[:user]) && #user.update_membership_with_history(membership_attributes)
redirect_to users_path
else
render :action => :edit
end
end
We're simply parsing out the membership attributes (so you can still use fields_for in your view) and updating them separately, and only if needed.
Did you look at acts_as_versioned? In the before_save of the Membership you could create a new version of the User, which would be acts_as_versioned.
Got it working. While it's probably not the best implementation, all my tests are passing. Thanks for the input guys.
before_save :soft_delete_changed_membership
def soft_delete_changed_membership
if !membership.nil? then
if !membership.new_record? && membership.trial_expire_at_changed? then
Membership.update_all( "active = 0", [ "id = ?", self.membership.id ] )
trial_expire_at = self.membership.trial_expire_at
self.membership = nil
Membership.create!(
:user_id => self.id,
:trial_expire_at => trial_expire_at,
:active => true
)
self.reload
end
end
end
Why don't you just assume that the latest membership is the active one. This would save you a lot of headache.
class User < ActiveRecord::Base
has_many :memberships, :dependent => :destroy
end
class Membership < ActiveRecord::Base
nested_scope :active, :order => "created_at DESC", :limit => 1
belongs_to :user
def update(attributes)
self.class.create attributes if changed?
end
end
then you can use
#user.memberships.active
to get the active membership, and you can just update any membership to get a new membership, which will become the active membership because it is the latest.

Rails has_many :through and Setting Property on Join model

Similar to this question, how do I set a property on the join model just before save in this context?
class Post < ActiveRecord::Base
has_many :post_assets
has_many :assets, :through => :post_assets
has_many :featured_images, :through => :post_assets, :class_name => "Asset", :source => :asset, :conditions => ['post_assets.context = ?', "featured"]
end
class PostAssets < ActiveRecord::Base
belongs_to :post
belongs_to :asset
# context is so we know the scope or role
# the join plays
validates_presences_of :context
end
class Asset < ActiveRecord::Base
has_many :post_assets
has_many :posts, :through => :post_assets
end
I just want to be able to do this:
#post = Post.create!(:title => "A Post")
#post.featured_images << Asset.create!(:title => "An Asset")
# ...
#post = Post.first
#featured = #post.featured_images.first
#=> #<Asset id: 1, title: "An Asset">
#featured.current_post_asset #=> #<PostAsset id: 1, context: "featured">
How would that work? I've been banging my head over it all day :).
What currently happens is when I do this:
#post.featured_images << Asset.create!(:title => "An Asset")
Then the join model PostAsset that gets created never gets a chance to set context. How do I set that context property? It looks like this:
PostAsset.first #=> #<PostAsset id: 1, context: nil>
Update:
I have created a test gem to try to isolate the problem. Is there an easier way to do this?!
This ActsAsJoinable::Core class makes it so you can have many to many relationships with a context between them in the join model. And it adds helper methods. The basic tests show basically what I'm trying to do. Any better ideas on how to do this properly?
Look at the has_many options in the ActiveRecord::Associations::ClassMethods API located here: http://rails.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#M001316
This is the most interesting quote:
:conditions
Specify the conditions that the associated object must meet in order to be included as a WHERE SQL fragment, such as authorized = 1. Record creations from the association are scoped if a hash is used. has_many :posts, :conditions => {:published => true} will create published posts with #blog.posts.create or #blog.posts.build.
So I believe your conditions must be specified as a hash, like so:
class Post < ActiveRecord::Base
has_many :post_assets
has_many :featured_post_assets, :conditions => { :context => 'featured' }
has_many :assets, :through => :post_assets
has_many :featured_images, :through => :featured_post_assets,
:class_name => "Asset", :source => :asset,
end
And you should also do the following:
#post.featured_images.build(:title => "An asset")
instead of:
#post.featured_images << Asset.create!(:title => "An Asset")
This should call the scoped asset build, as suggested in the quote above to add the context field to asset. It will also save both the join model object (post_asset) and the asset object to the database at the same time in one atomic transaction.

save object associate to another object automatically

Hi i have these classes:
class Core < ActiveRecord::Base
belongs_to :resource, :polymorphic => true
belongs_to :image, :class_name => 'Multimedia', :foreign_key => 'image_id'
end
class Place < ActiveRecord::Base
has_one :core, :as => :resource
end
If i try do launch this:
a = Place.find(5)
a.name ="a"
a.core.image_id = 24
a.save
name is saved. image_id no
i want save automatically all changes in records in relationship with place class at a.save command. is possible?
thanks
Use :autosave => true
See section titled One-to-many Example for ActiveRecord::AutosaveAssociation.
You'll want something like:
class Place
has_one :core, :as => :resource, :autosave => true
end
Disclaimer:
The :autosave => true should be used on the "parent" Object. It works great with has_one and has_many, but I've run into great difficulty attempting to use it on a belongs_to. relationship.
I think that you can use the build_association method to do that. For example,
a = Place.find(5)
a.name = "a"
a.build_core(:image_id => 24)
a.save
But it might only work if the place object was created before hand.

How to create "two-side" many-to-many relationships in Rails?

Suppose we have a photography site. Any author can subscribe to receive updates from any other author. Obviously if author A is subscribed to author B that doesn't mean that B is subscribed to A. So we build models
class Author < ActiveRecord::Base
has_many :subscriptions
has_many :subscribed_by_author, :through => :subscriptions, :source => :subscribed_to
end
class Subscription < ActiveRecord::Base
belongs_to :author
belongs_to :subscribed_to, :class_name => "Author", :foreign_key => "subscribed_to"
end
This way we can use
some_author.subscribed_by_author -- the list of the authors to whom some_author is subscribed.
For any subscription we can know both ends (who is subscribed to whom)
But the question is how to get the list of people subscribed to some author using only rails (not using plain SQL) i.e get the answer to :"Who is subscribed to some_author?"
Question: is there any ability in Rails to get the relationship working both sides i.e. not only writing some_author.subscribed_BY_author but having some_author_subscribed_TO_author? If there is one, then what is it?
P.S. Obvious solution is to
Change the database design, adding a column named "direction"
Create 2 records each time a subscription is created
Add to the author model
has_many :subscribed_BY_author, :through => :subscriptions, :source => :subscribed_to, :conditions => "direction = 'by'"
has_many :subscribed_TO_author, :through => :subscriptions, :source => :subscribed_to, :conditions => "direction = 'to'"
But i wonder if there is a solution without changing the database design.
I'd use plain HABTM for something simple like this, but you're going to need a join table no matter what.
create_table :subscriptions do |t|
t.column :author_id, :integer
t.column :subscriber_id, :integer
end
Point Author to it:
class Author < ActiveRecord::Base
has_and_belongs_to_many :subscribers
:class_name => "Author",
:join_table => "subscriptions",
:association_foreign_key => "subscriber_id"
def subscriptions # "subscribers" is already included above
self.subscribers.find(:all, :subscriber_id=>author.id) # hopefully not too
end # much SQL
end
If you're really committed to your method names:
def subscribed_to_author
subscribers
end
def subscribed_by_author(author)
self.subscribers.find(:all, :subscriber_id=>author.id)
end
Create some connections (I'd make SubscriptionsController to be RESTy)
SubscriptionsController < ApplicationController
def create
#author = Author.find(params[:author_id] # author to be subscribed to
#user = current_user # user clicking the "subscribe" button
#author.subscribers << #user # assuming authors should only
#author.save # be able to subscribe themselves
end
end
Display names, or whatever
#author.subscribers.each do |s|
s.name
end
# ...or...and...also...
<%= render :partial => #author.subscribers -%>
<%= render :partial => #author.subscriptions -%>
# Author model
has_many :subscriptions_to, :class_name => "Subscription", :foreign_key => "subscribed_to"
has_many :subscribed_to_author, :through => :subscriptions_to, :source => :author
As far as I know - it works! :)

Resources