Cannot update attributes through = and save - ruby-on-rails

Following is the problem puzzling me:
I have a model Word, and it has an attribute article_count
however, if I use:
w = Word.first
w.article_count = 1
w.save
the the first word's article_count will not change, the log shows:
Word Exists (0.4ms) ...
but I can use
w.update_attributes :article_count => 1
Maybe I misunderstood the function of save, can anyone explain to me?
======================= Update =======================
Here is my article model:
class Article < ActiveRecord::Base
attr_accessible :content, :title, :source, :url
has_many :article_wordships, :dependent => :destroy
has_many :words, :through => :article_wordships
has_many :paragraphs, :dependent => :destroy
alias_attribute :word_count, :article_wordships_count
validates_uniqueness_of :title
end
Here is my code:
Article.select(:title).all.each do |a|
a.title = a.title.gsub(/\A.+?;\s(.+)/, '\1')
a.save
end
And after run the code, nothing changes in the articles.
Here is the console info:
1.9.3p194 :003 > a.save
(0.2ms) BEGIN
Article Exists (0.6ms) SELECT 1 AS one FROM `articles` WHERE (`articles`.`title` = BINARY 'A Shared State of Defeat' AND `articles`.`id` != 178) LIMIT 1
(0.9ms) UPDATE `articles` SET `title` = 'A Shared State of Defeat', `updated_at` = '2012-09-08 02:58:33' WHERE `articles`.`id` = 178
(2.2ms) COMMIT
=> true
Both save and save! return true.
Strangely, SOMETIMES IT WORKS, SOMETIMES IT DOES NOT WORK!
I'm getting mad...

I am not sure what you mean "SOMETIMES IT WORKS, SOMETIMES IT DOES NOT WORK!" It seems to work in the example you give. If by not work you mean it does not always issue updates, it might be that this line does not change the title. ActiveRecord does dirty checking and if the value of title does not change, it will not issue an update.
a.title = a.title.gsub(/\A.+?;\s(.+)/, '\1')

Related

How would a 'commentable' polymorphic association work on a 'user' model itself?

I'm learning rails and trying out polymorphic association. I have listed below a couple of simple models for illustration. Model associations seems to works fine as expected. But what if a user (commenter) would like to leave a comment for a another user? I can't seems to get it to work with these configuration. How do I go about doing so?
class User < ApplicationRecord
# username, email, password_digest
has_many :comments, as: :commentable, dependent: :destroy
end
class Project < ApplicationRecord
# title, completed, user_id
has_many :comments, as: :commentable, dependent: :destroy
end
class Comment < ApplicationRecord
# commenter_id, commentable_type, commentable_id, body
belongs_to :commentable, polymorphic: true
end
in console... setup
user1 = User.frist
user2 = User.last
project = Project.first
pro_comment = project.comments.new(body: 'some text')
pro_comment.commenter_id = user1.id
pro_comment.save
user_comment = user2.comments.new(body: 'some text')
user_comment.commenter_id = user1.id
user_comment.save
expected and actual results
Comment.all => successfully lists pro_comment & user_comment
But...
Comment.find_by(commenter_id: 1) => only listed the pro_comment
(what am I doing wrong?)
Also..
user1.comments => returned an empty object... was expecting 2 objects,
as you can see below it's not referencing 'commenter_id' ....
result...
comment Load (0.5ms) SELECT "comments".* FROM "comments" WHERE
"comments"."commentable_id" = $1 AND "comments"."commentable_type" = $2
LIMIT $3 [["commentable_id", 1], ["commentable_type", "User"],
["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy []>
I also tried ...
user1.comments.where(commenter_id: 1) >> which returned...
comment Load (0.4ms) SELECT "comments".* FROM "comments" WHERE
"comments"."commentable_id" = $1 AND "comments"."commentable_type" = $2
AND "comments"."commenter_id" = $3 LIMIT $4 [["commentable_id", 1],
["commentable_type", "User"], ["commenter_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::AssociationRelation []>
Not sure what I'm doing wrong. Could someone please point me in the right direction.
I thank you for your time.
find_by returns only one record, try Comment.where(commenter_id: 1) instead.
For user1.comments being empty, you are mixing the relationships. You should have 2 relationships: comment belongs to a commentable object (a project or a user) and comments also belongs to a commenter (the user you set as commenter_id).
It makes sense for user1.comments to be empty since the user is the commenter on both comments, it's not the commentable. user2.comments shouldn't be empty, same for project.comments
Try something like this:
class User < ApplicationRecord
has_many :comments_done, class_name: 'Comment', inverse_of: :commenter
has_many :comments, as: :commentable, dependent: :destroy
end
class Comment < ApplicationRecord
belongs_to :commenter, class_name: 'User'
belongs_to :commentable, polymorphic: true
end
(check the guide, I may be missing some config option https://guides.rubyonrails.org/v5.2/association_basics.html#has-many-association-reference)
Now you can use user1.comments_done and user1.comments for comments done by the user and done at the user.

Creating a Nested Association with Has_Many :Through

I am not sure if my title is properly using the vocabulary. I found a few SO posts where people had similar issues (I couldn't find a solution in those posts), so I used a similar title to those. I am trying to allow users to create clubs and automatically assign the user as a member of the club. However, it seems like I have something out of order when I tried to create the club.
I have three models:
###Models###
#user
has_many :club_memberships, :class_name => 'ClubMembership', :foreign_key => :member_id, :primary_key => :id
has_many :clubs, :through => :club_memberships
#club
attr_accessor :club_name, :club_type
has_many :club_memberships
has_many :members, :through => :club_memberships
#clubmembership
attr_accessor :club_id, :member_id, :membership_type
belongs_to :club
belongs_to :member, :class_name => "User", :foreign_key => :member_id, :primary_key => :id
Here are the relevant parts of the controllers
###Controllers###
#Clubs
def new
#club = Club.new
end
def create
#club = Club.new(club_params)
if #club.save
#club_membership = ClubMembership.create(
member_id: current_user.id,
club_id: #club.id,
membership_type: 'founder'
)
flash[:success] = "Club Created!"
redirect_to root_url
else
render #club.errors.full_messages
end
end
private
def club_params
params.require(:club).permit(:club_name, :club_type)
end
#ClubMemberships
def create
#club_membership = ClubMembership.new(club_membership_params)
if #club_membership.save
render #club_membership
else
render #club_membership.errors.full_messages
end
end
private
def club_membership_params
params.require(:club_membership).permit(:member_id, :club_id, :membership_type)
end
My form_for
###View###
#club#new
= form_for(#club) do |f|
.field.center-align
= f.label :club_name
= f.text_field :club_name, :class => "form-control fieldbox", autofocus: true
.field.center-align
= f.label :club_type
= f.text_field :club_type, :class => 'form-control fieldbox', autofocus: true
.actions.center-align
= f.submit "Create Club!", :class => "btn hoverable padtop"
And finally, here is what the log shows on post
#log
Started POST "/clubs" for at 2015-09-03 22:32:41 +0000
Cannot render console from ! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by ClubsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"Ar2dv41/Tqk9EVjwfLLeD8bnpLoVWQIdDxG3Ju1GO3stLLvPd/FFgoFF9YuHobWbgb2byqkgAMiWRJAg5YcGKQ==", "club"=>{"club_name"=>"Test Club", "club_type"=>"Test Type"}, "commit"=>"Create Club!"}
(0.2ms) BEGIN
Club Exists (0.4ms) SELECT 1 AS one FROM `clubs` WHERE `clubs`.`club_name` = BINARY 'Test Club' LIMIT 1
SQL (0.3ms) INSERT INTO `clubs` (`created_at`, `updated_at`) VALUES ('2015-09-03 22:32:41', '2015-09-03 22:32:41')
(3.4ms) COMMIT
User Load (0.1ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 56 LIMIT 1
(0.1ms) BEGIN
ClubMembership Exists (0.3ms) SELECT 1 AS one FROM `club_memberships` WHERE (`club_memberships`.`member_id` = BINARY 56 AND `club_memberships`.`club_id` IS NULL) LIMIT 1
SQL (1.6ms) INSERT INTO `club_memberships` (`created_at`, `updated_at`) VALUES ('2015-09-03 22:32:41', '2015-09-03 22:32:41')
(3.1ms) COMMIT
Redirected to c9
Completed 302 Found in 71ms (ActiveRecord: 11.1ms)
I'll be honest. I have no clue what is happening in that POST or where to tackle this from next. It seems like the parameters I want are going through, but then they aren't. Any help would be appreciated.
I figured it out. I tried two things at once and it worked, so I can't say for sure what helped, but I am pretty sure the second thing was most important.
First, I removed the attr_accessor from both model's (Club and ClubMembership). Why did I put it in there in the first place? No idea. I probably saw it somewhere and thought it was cool.
Second, also in the models, I had validators that I didn't post because I didn't think they were important. I had:
validates :club_name, :club_type, :presence => true
validates :club_name, :uniqueness => true
I changed this to:
validates :club_name, presence: true
validates :club_type, presence: true
Turns out that was important and everything is working fine now.

Ruby on Rails ActiveRecord include queries happen while not specifing it

Whenever i try to call this:
#ships = Ship.find(:all,
:conditions => {:sold => params[:sold]},
:order => "ship.id desc")
From this model:
class Ship < ActiveRecord::Base
self.table_name = 'ship'
self.inheritance_column = :ruby_type
belongs_to :brand, :class_name => 'Brand', :foreign_key => :brand
belongs_to :fuel, :class_name => 'Fuel', :foreign_key => :fuel
has_many :ship_pictures, :class_name => 'ShipPicture'
has_many :reservations, :class_name => 'Reservation'
end
I end up with these queries:
Ship Load (0.0ms) SELECT "ship".* FROM "ship" WHERE "ship"."sold" = 'false' ORDER BY ship.id desc
Brand Load (0.0ms) SELECT "brand".* FROM "brand" WHERE "brand"."name" = 'Percedes' LIMIT 1
Fuel Load (0.0ms) SELECT "fuel".* FROM "fuel" WHERE "fuel"."name" = 'Air' LIMIT 1
ShipPicture Load (0.0ms) SELECT "ship_picture".* FROM "ship_picture" WHERE "ship_picture"."ship_id" = 2
Brand Load (1.0ms) SELECT "brand".* FROM "brand" WHERE "brand"."name" = 'Volksship' LIMIT 1
Fuel Load (1.0ms) SELECT "fuel".* FROM "fuel" WHERE "fuel"."name" = 'Nuclear Reactor' LIMIT 1
ShipPicture Load (0.0ms) SELECT "ship_picture".* FROM "ship_picture" WHERE "ship_picture"."ship_id" = 1
Why is this happening? I'm not calling :include or anything like that? I want to join ship_pictures and only get the first result of that join.
And a second thing: What is considered to be better: the symbolic or method way?
This doesn't seem to be lazy loading at play here. If lazy loading was being used I'd expect to only see three queries for Brand, Fuel and ShipPicture with SQL like:
WHERE "ship_picture"."ship_id" IN [1,2]
Do you have any other code in your Ship model that calls any of those associations?
If your finder above is from a controller, you might be calling ship.fuel, ship.brand or ship.ship_pictures somewhere in your view. This is the usual cause of N+1 queries. If it's not from a controller then it is most likely to be some other code that's being run on the results of the query.

Given User.deleted_at, how to filter out deleted users in other models w (user_id,friend_id)?

My user model has a default scope to not show deleted users. This allows my app to soft delete users. The problem is this is causing an error in an associated model.
User.rb (id,name)
default_scope :conditions => 'users.deleted_at IS NULL'
NavItem.rb (id,user_id, friend_id)
items = NavItem.find_all_by_user_id(current_user.id)
items.each do |item|
user = User.find(item.friend_id)
end
The problem I'm having here is that when a user is set is deleted, meaning #user.deleted_at is NOT NULL, the query above for the user errors because no user is found.
How can I update NavItem.rb so it is joined to the User model and magically filters out users.deleted_at?
Thanks
The following might help you.
I scaffolded this:
class User < ActiveRecord::Base
attr_accessible :deleted_at, :name
default_scope :conditions => 'users.deleted_at IS NULL'
has_many :nav_items
end
class NavItem < ActiveRecord::Base
attr_accessible :friend_id, :user_id
belongs_to :user
scope :without_deleted_users, where(:user_id => User.scoped)
end
And NavItem.without_deleted_users:
NavItem.without_deleted_users
NavItem Load (0.2ms) SELECT "nav_items".* FROM "nav_items" WHERE "nav_items"."user_id" IN (SELECT "users"."id" FROM "users" WHERE (users.deleted_at IS NULL))

Why is the before filter giving me an error

Here is my data structure
class User < ActiveRecord::Base
has_many :companies, :through => :positions
has_many :positions
class Company < ActiveRecord::Base
has_many :positions
has_many :users, :through => :positions
class Position < ActiveRecord::Base
belongs_to :company
belongs_to :user
attr_accessible :company_id, :user_id, :regular_user
end
class Position < ActiveRecord::Base
belongs_to :company
belongs_to :user
attr_accessible :company_id, :user_id, :regular_user
before_save :set_regular_user
def set_regular_user
if self.user.is_admin?
self.regular_user = false
else
self.regular_user = true
end
end
end
everytime i run
#user.companies << Company.last
I get ActiveRecord::RecordNotSaved: ActiveRecord::RecordNotSaved
but if i remove my before filter everthing works out perfect and it saves as expected
#user.companies << Company.last
Company Load (0.2ms) SELECT `companies`.* FROM `companies` ORDER BY `companies`.`id` DESC LIMIT 1
(0.1ms) BEGIN
SQL (0.2ms) INSERT INTO `positions` (`company_id`, `created_at`, `regular_user`, `updated_at`, `user_id`)
VALUES
(263, '2012-07-25 14:44:15', NULL, '2012-07-25 14:44:15', 757)
Any ideas what i am missing ....this question is based on this earlier question
Callbacks need to return true in order to continue, false cancels the operation. In your function, the value of the if statement may be false: self.regular_user = false The return value of a ruby function is the last statement.
Just add a return true to the end.
As #DGM says, it is good practice for callbacks to always return true at the end (or false at some point in the flow if they should prevent the code continuing). Otherwise it can be the source of some very odd bugs further down the line (speaking from experience :) ).
I suspect it is the if branch returning the false. Hopefully if you just add true as the last statement in the callback it should work.

Resources