Before saving my Payment model I would like to set an attribute for my other model Post .
My Payment belongs_to :user and my User has has_many :posts and has_many :payments
In my payment.rb I have an before_create :set_expiration_date callback.
How can I set the expiration date of my Post Model?
Is it something like this?
def set_expiration_date
self.User.Post.last.expiration = Date.today + self.duration.days
end
Would the before_create create even work since the payment has not been saved in the database yet and hence the association with the User?
Would it be simple if I care a 1-to-1 association Payment-Post?
I save my payment record the following way:
#Payment.rb
def save_with_payment
if valid?
Stripe::Charge.create(
:amount => 3000,
:currency => "usd",
:card => stripe_card_token,
:description => "Charge for test#example.com")
end
save!
end
The payment model does have a user_id attribute but it seems to always nil
SQL (1.1ms) INSERT INTO "payments" ("amount", "created_at", "transaction_number", "updated_at", "user_id") VALUES (?, ?, ?, ?, ?) [["amount", nil], ["created_at", Sun, 23 Feb 2014 14:50:42 UTC +00:00], ["transaction_number", nil], ["updated_at", Sun, 23 Feb 2014 14:50:42 UTC +00:00], ["user_id", nil]]
Thank you
post = self.user.posts.last
post.expiration = Date.today + self.duration.days
post.save!
Related
I checked the Rails Guides about has_many through relationship but they have a poor documentation for has_many through's effects in controllers in views.
The models:
class Project < ActiveRecord::Base
has_many :twitter_memberships
has_many :twitter_accounts, through: :twitter_memberships
end
class TwitterAccount < ActiveRecord::Base
has_many :twitter_memberships
has_many :projects, through: :twitter_memberships
end
class TwitterMembership < ActiveRecord::Base
belongs_to :project
belongs_to :twitter_account
end
Routes:
resources :projects do
resources :twitter_accounts
end
The question is, when I use the create method, a TwitterMembership object is being created automatically but when I use new method and then the save method, TwitterMembership is not being created. Many of the posts in Stackoverflow is saying that create = new & save but it seems that they are not the same.
For example:
Project.first.twitter_accounts.create(name: "bar")
is creating both TwitterAccount and TwitterMembership:
SQL (0.5ms) INSERT INTO "twitter_accounts" ("created_at", "name", "updated_at") VALUES (?, ?, ?) [["created_at", Thu, 20 Nov 2014 22:01:06 UTC +00:00], ["name", "test_record"], ["updated_at", Thu, 20 Nov 2014 22:01:06 UTC +00:00]]
SQL (0.3ms) INSERT INTO "twitter_memberships" ("created_at", "project_id", "twitter_account_id", "updated_at") VALUES (?, ?, ?, ?) [["created_at", Thu, 20 Nov 2014 22:01:06 UTC +00:00], ["project_id", 1], ["twitter_account_id", 8], ["updated_at", Thu, 20 Nov 2014 22:01:06 UTC +00:00]]
But when I do the same thing in the controllers/twitter_accounts_controller with new & create - it's not creating the TwitterMembership:
def new
#twitter_account = #project.twitter_accounts.new
end
def create
#twitter_account = #project.twitter_accounts.new(twitter_account_params)
#twitter_account.save
end
Can you explain me the difference between new-save and create? And is it okay if I use create method directly in my controller like this?
def create
#twitter_account = #project.twitter_accounts.create(twitter_account_params)
end
Thanks in advance.
Edit. Here is the full controller =>
class TwitterAccountsController < ApplicationController
before_action :set_project
before_action :set_twitter_account, only: [:show, :edit, :update, :destroy]
def new
#twitter_account = #project.twitter_accounts.new
end
def create
#twitter_account = #project.twitter_accounts.new(twitter_account_params)
#twitter_account.save
end
private
def set_project
#project = Project.friendly.find(params[:project_id])
end
def set_twitter_account
#twitter_account = TwitterAccount.friendly.find(params[:id])
end
def twitter_account_params
params.require(:twitter_account).permit(:name, :account_id)
end
end
The Fix
You need to set your inverse relationships on your models to guarantee the build and new on associations will work consistently to setup the relationships, foreign keys, and intermediate associations:
class Project < ActiveRecord::Base
has_many :twitter_memberships, inverse_of: :project
has_many :twitter_accounts, through: :twitter_memberships
end
class TwitterMembership < ActiveRecord::Base
belongs_to :project, inverse_of: :twitter_memberships
belongs_to :twitter_account, inverse_of: :twitter_memberships
end
class TwitterAccount < ActiveRecord::Base
has_many :twitter_memberships, inverse_of: :twitter_account
has_many :projects, through: :twitter_memberships
end
Which should then make this work
#twitter_account = Project.first.twitter_accounts.new(name: 'baz')
#twitter_account.save
And voila:
SQL (0.3ms) INSERT INTO "twitter_accounts" ("created_at", "name", "updated_at") VALUES (?, ?, ?) [["created_at", "2014-11-21 19:19:06.323072"], ["name", "baz"], ["updated_at", "2014-11-21 19:19:06.323072"]]
SQL (0.1ms) INSERT INTO "twitter_memberships" ("created_at", "project_id", "twitter_account_id", "updated_at") VALUES (?, ?, ?, ?) [["created_at", "2014-11-21 19:19:06.324779"], ["project_id", 1], ["twitter_account_id", 7], ["updated_at", "2014-11-21 19:19:06.324779"]]
Also, using create is perfectly acceptable assuming you've handled all your edge cases in the event of a failure. For example: if it fails do you show an error to the end user? If it is not supposed to fail, you should raise an exception (see create!) to generate a notification of some sort to inform you of the failure.
The Why
In short, without the inverse_of setup on has_many, has_one, and belongs_to Rails doesn't completely understand the chain of intermediate models that glue things together. Rails will in certain cases (like create) take a "best guess", and may get things right. Rails Guide doesn't make this clear, but the documentation on inverse_of spells this out.
It has become a Rails idiom to set inverse_of on all has_many, has_one, and belongs_to relationships, and should be done to ensure guessing by Rails is not necessary.
Using Rails 3.2.11 and ruby 1.9.3:
I have Review, User and ReviewAccess classes
class ReviewAccess < ActiveRecord::Base
belongs_to :user
belongs_to :review
attr_accessible :role_id
end
class Review < ActiveRecord::Base
has_one :review_accesses_owner, :class_name => 'ReviewAccess',
:conditions => "review_accesses.role_id = 1"
has_one :owner, :class_name => 'User', :through => :review_accesses_owner,
:source => :user
end
Basically Review is many-to-many with User and the join table is ReviewAccess where it additionally holds the relation role (1 for owner) in role_id column.
I can read the the review owner by:
Review.owner # works
# sql: SELECT "review_accesses".* FROM "review_accesses" WHERE "review_accesses"."review_id" = 7 AND (review_accesses.role_id = 1) LIMIT 1
However, setting the owner does not work because it doesn't set role_id to 1 (as stated in the conditions clause of the association)
Review.owner = current_user # does not set role_id
# sql: INSERT INTO "review_accesses" ("created_at", "review_id", "role_id", "updated_at", "user_id") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["created_at", Sun, 27 Oct 2013 08:02:54 UTC +00:00], ["review_id", 7], ["role_id", nil], ["updated_at", Sun, 27 Oct 2013 08:02:54 UTC +00:00], ["user_id", 1]]
I know I can override owner= but I have many of these (for each role) and I want to use the association DSL instead.
How to update associations having conditions to set the conditions on creation?
I'm a little bit afraid that using DSL it's not a good choice.
Prefer overriding accesorrs than putting code into callbacks if you need add some custom behavior.
To some generic, repetitive methods use metaprogramming stuff. If you'll have many roles you'll have DRY in your association definitions still, so problem is the same but in different place.
To sum up, override writer by using metaprogramming for it. Anywhere you have an array of roles, right?
Im on rails 4. Lets say I have three models; House, Color, and HouseColoring.
class House < ActiveRecord::Base
has_many :house_colorings
has_many :colors, through: :house_colorings
accepts_nested_attributes_for :house_colorings, allow_destroy: true
end
class Color < ActiveRecord::Base
has_many :house_colorings
has_many :houses, through: :house_colorings
end
class HouseColoring < ActiveRecord::Base
belongs_to :house
belongs_to :color
end
my house controller
class HousesController < ApplicationController
before_action :set_house
...
def new
#house = House.new
#house.house_colorings.build
end
def create
#house = House.create(house_params)
if #house.save
redirect_to #house
else
render 'new'
end
end
def edit
#Gets #house from set_house
end
def update
if #house.update(house_params)
redirect_to #house
else
render 'edit'
end
end
...
private
def set_house
#house = House.find(params[:id])
end
def house_params
params.require(:house).permit(:some_parameters, house_coloring_attributes: [:color_id, :some_other_params])
end
end
I have a list of colors in my db to choose from when creating a new house and a house can have multiple colors. When I go to create a new house, I choose a color and it saves just fine. The problem i'm running into is that is I edit my the house I just created and decide to change the color, it adds a new house_color record instead of changing the one I have already.
Here is my _form.html.erb partial for my house new and edit
<%= form_for #house do |f| %>
<%= f.fields_for :house_colorings do |c| %>
....
<%= c.collection_select :color_id, Color.all, :id, :name, {include_blank: "Select color"} %>
<% end %>
<% end %>
Why is this happening? What do I need to change to make it change/update my house_coloring instead of creating a new record? Thanks.
Edit:
Here is an update log for what happens when I try and update a house
Started PATCH "/MY_PATH/houses/16" for 127.0.0.1 at 2013-10-24 10:33:13 -0700
Processing by HousesController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"sfdj43ksdkjsd3434fmskwec=", "house"=>{"name"=>"house", "house_colorings_attributes"=>{"0"=>{"color_id"=>"2", "id"=>"65"}, "1"=>{"color_id"=>"3", "id"=>"66"}, "2"=>{"color_id"=>"1", "id"=>"67"}}}, "commit"=>"Update House", "id"=>"16"}
[1m[36mHouse Load (0.1ms)[0m [1mSELECT "houses".* FROM "houses" WHERE "houses"."id" = ? LIMIT 1[0m [["id", "16"]]
Unpermitted parameters: id
Unpermitted parameters: id
Unpermitted parameters: id
[1m[35m (0.1ms)[0m begin transaction
[1m[36mSQL (1.0ms)[0m [1mINSERT INTO "house_colorings" ("house_id", "created_at", "color_id", "updated_at") VALUES (?, ?, ?, ?, ?)[0m [["house_id", 16], ["created_at", Thu, 24 Oct 2013 17:33:13 UTC +00:00], ["color_id", 2], ["updated_at", Thu, 24 Oct 2013 17:33:13 UTC +00:00]]
[1m[35mSQL (0.3ms)[0m INSERT INTO "house_colorings" ("house_id", "created_at", "color_id", "updated_at") VALUES (?, ?, ?, ?, ?) [["house_id", 16], ["created_at", Thu, 24 Oct 2013 17:33:13 UTC +00:00], ["color_id", 3], ["updated_at", Thu, 24 Oct 2013 17:33:13 UTC +00:00]]
[1m[36mSQL (0.1ms)[0m [1mINSERT INTO "house_colorings" ("house_id", "created_at", "color_id", "updated_at") VALUES (?, ?, ?, ?, ?)[0m [["house_id", 16], ["created_at", Thu, 24 Oct 2013 17:33:13 UTC +00:00], ["color_id", 1], ["updated_at", Thu, 24 Oct 2013 17:33:13 UTC +00:00]]
[1m[35m (10.5ms)[0m commit transaction
Redirected to http://localhost:3000/houses/16
Completed 302 Found in 24ms (ActiveRecord: 12.5ms)
I figured it out, I needed to permit the id of the house_coloring in the params like this
def house_params
params.require(:house).permit(:some_parameters, house_coloring_attributes: [:id, :color_id, :some_other_params])
end
I hope this ends up saving someone the headache I was dealing with.
The Code (Rails 4.0.0)
class Track < ActiveRecord::Base
has_many :artist_tracks
has_many :owning_artists,
-> { where(:artist_tracks => { :artistic_role_id => 1 }) },
:through => :artist_tracks,
:source => :artist
end
class ArtistTrack < ActiveRecord::Base
belongs_to :artist
belongs_to :track
belongs_to :artistic_role
end
class Artist < ActiveRecord::Base
has_many :artist_tracks
has_many :tracks, :through => :artist_tracks
end
Finding Works
# artist_tracks.artistic_role_id is properly set to "1"
2.0.0p195 :003 > Track.last.owning_artists
Track Load (1.1ms) SELECT "tracks".* FROM "tracks" ORDER BY "tracks"."id" DESC LIMIT 1
Artist Load (0.8ms) SELECT "artists".* FROM "artists" INNER JOIN "artist_tracks" ON "artists"."id" = "artist_tracks"."artist_id" WHERE "artist_tracks"."artistic_role_id" = 1 AND "artist_tracks"."track_id" = $1 [["track_id", 10]]
Create Does Not Work
# artist_tracks.artistic_role_id is totally missing from the INSERT
2.0.0p195 :005 > Track.create!(name: "test_name", lyrics: "test_lyrics", owning_artist_ids: [1])
Artist Load (1.3ms) SELECT "artists".* FROM "artists" WHERE "artists"."id" = $1 LIMIT 1 [["id", 1]]
(0.5ms) BEGIN
Artist Exists (0.7ms) SELECT 1 AS one FROM "artists" WHERE ("artists"."name" = 'TestArtist1' AND "artists"."id" != 1) LIMIT 1
SQL (0.7ms) INSERT INTO "tracks" ("created_at", "lyrics", "name", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Thu, 13 Jun 2013 22:20:14 UTC +00:00], ["lyrics", "test_lyrics"], ["name", "test_name"], ["updated_at", Thu, 13 Jun 2013 22:20:14 UTC +00:00]]
#
# Y U NO have artist_tracks.artistic_role_id?
#
SQL (0.7ms) INSERT INTO "artist_tracks" ("artist_id", "created_at", "track_id", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["artist_id", 1], ["created_at", Thu, 13 Jun 2013 22:20:14 UTC +00:00], ["track_id", 12], ["updated_at", Thu, 13 Jun 2013 22:20:14 UTC +00:00]]
(1.0ms) COMMIT
According to the Rails Guide for Active Record Associations (4.3.3.1 where), I believe my usage of the scope and expectation are valid:
If you use a hash-style where option, then record creation via this
association will be automatically scoped using the hash.
Why is the artist_tracks.artistic_role_id attribute being lost? If my expectations are wrong, I'd like to understand why and how to implement an alternative solution.
I have also listed this as an issue on the Rails repo. Any insight is appreciated! Thank you
I believe that what is happening is that the associated model actually being created here is the join model, artist_tracks, and not the association with the actual conditions on it. You could probably fix this by declaring an alternate join association with conditions on it, and then attaching owning_artists through that instead. Like this:
class Track < ActiveRecord::Base
has_many :artist_tracks
has_many :owning_artist_tracks,
-> { where(:artistic_role_id => 1) },
:class_name => "ArtistTrack"
has_many :owning_artists,
:through => :owning_artist_tracks,
:source => :artist
end
I have a User model which has many roles. Roles contains a user_id field, which I want to validate_presence_of
The issue is: if I assign a role to user upon create, the validation fails because no user_id is set. Now, I do want to validate that a user_id exists, but I need to save the user before checking that.
The code currently looks like this:
#user = User.new(params[:user])
#user.roles << Role.new(:name => 'Peon') unless #user.has_roles?
if #user.save
# ...
The only ways I can think of getting around the problem involves either disabling the validation, which I don't want to do, or double-saving to the DB, which isn't exactly efficient.
What's the standard way for handling this issue?
After researching a bit, this solution seems to be easiest. First, in your Role model, instead of validating user_id, validate user:
validates :user, :presence => true
Then, in your User model, add :inverse_of => :user to your has_many call:
has_many :roles, :inverse_of => :user
Then it works as expected:
irb(main):001:0> #user = User.new
=> #<User id: nil, created_at: nil, updated_at: nil>
irb(main):002:0> #user.roles << Role.new(:name => "blah")
=> [#<Role id: nil, user_id: nil, name: "blah", created_at: nil, updated_at: nil>]
irb(main):003:0> #user.roles[0].user
=> #<User id: nil, created_at: nil, updated_at: nil>
irb(main):004:0> #user.save
(0.1ms) begin transaction
SQL (3.3ms) INSERT INTO "users" ("created_at", "updated_at") VALUES (?, ?) [["created_at", Fri, 04 Jan 2013 02:29:33 UTC +00:00], ["updated_at", Fri, 04 Jan 2013 02:29:33 UTC +00:00]]
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = 3 LIMIT 1
SQL (0.2ms) INSERT INTO "roles" ("created_at", "name", "updated_at", "user_id") VALUES (?, ?, ?, ?) [["created_at", Fri, 04 Jan 2013 02:29:34 UTC +00:00], ["name", "blah"], ["updated_at", Fri, 04 Jan 2013 02:29:34 UTC +00:00], ["user_id", 3]]
(1.9ms) commit transaction
=> true
irb(main):005:0> #user.roles.first
=> #<Role id: 4, user_id: 3, name: "blah", created_at: "2013-01-04 02:29:34", updated_at: "2013-01-04 02:29:34">
Note, however, that this still produces two SQL transactions, one to save the user and one to save the role. I don't see how you can avoid that.
See also: How can you validate the presence of a belongs to association with Rails?
I think you can get around the validation problem if you change your code to look like this:
#user = User.new(params[:user])
#user.roles.new(:name => 'Peon') unless #user.has_roles?
if #user.save
# ...
If that doesn't work, you could try changing you validation to this:
class Role < ActiveRecord::Base
belongs_to :user
validates :user_id, :presence => true, :unless => Proc.new() {|r| r.user}
end
You must take a look at ActiveRecord's Callbacks. Probably you will use the before_validation to do it.
For anyone Googling for a solution to this problem for a has_many :through association, as of 5/December/2013 the :inverse_of option can't be used in conjunction with :through (source). Instead, you can use the approach suggested by #waldyr.ar. For example, if our models are set up as follows:
class User < ActiveRecord::Base
has_many :roles
has_many :tasks, through: roles
end
class Role < ActiveRecord::Base
belongs_to :user
belongs_to :task
end
class Task < ActiveRecord::Base
has_many :roles
has_many :users, through: roles
end
We can modify our Role class as follows to validate the presence of both task and user before saving
class Role < ActiveRecord::Base
belongs_to :user
belongs_to :task
before_save { validates_presence_of :user, :task }
end
Now if we create a new User and add a couple tasks like so:
>> u = User.new
>> 2.times { u.tasks << Task.new }
Running u.save will save the User and the Task, as well as transparently build and save a new Role whose foreign keys user_id and task_id are set appropriately. Validations will run for all models, and we can go on our merry way!