So I'm trying to update few things in my user for it's role and its plan_id .. I've came up with something dirty in my console but everytime I'm trying to use update_all similar way I'm getting no where. I think I'm missing something.. here's my original console way;
expired_subscription_user_ids = Subscription.where("expiry_date < ?", Time.now.beginning_of_day).pluck(:user_id)
User.where(:id => cancelled_subscription).each do |user|
user.role = 'cancelled'
user.plan_id = 'cancelled'
user.save
end
Here's the same thing but using update all that's not working out for me.
User.where(:id => cancelled_subscription).each do |user|
user.update_all(:role => 'subscriber', :plan_id => 'subscriber')
end
So pretty much all users with cancelled_subscription will have their role and plan_id chanced.
You have to use the .update_all method on an ActiveRecord::Relation object, like this:
scope = User.where(:id => cancelled_subscription)
scope.update_all(:role => 'subscriber', :plan_id => 'subscriber')
Documentation: http://apidock.com/rails/ActiveRecord/Relation/update_all
An interesting comment in the documentation, from "openface":
Note that ActiveRecord will not update the timestamp fields (updated_at/updated_on) when using update_all().
Related
Hi, I am still a student and I'm taking a software engineering course and we have this big project (web design) and we're using rails so my question is I have a table Users and a table Groups and another association table GroupUsers where it has two foreign keys user_id and group_id. Whenever a user creates or join an already created group his id and the group_id are added to GroupUsers table
module GroupUsersHelper
def join
#group_id = params[:id]
#user_id = params[:user_id]
#newuser= GroupUser.new(:group_id => #group_id, :user_id => #user_id)
#newuser.save
redirect_to(:controller => 'groups', :action => 'show', :id => #group_id)
end
end
Now I have to create method leave group where I'll have to destroy the record from GroupUsers, so I wrote this code also in GroupUsersHelper
def leave
#group_id = params[:group_id]
#user_id = params[:user_id]
#group_user_id = params[:group_user_id]
#newuser= GroupUser.find(#group_user_id).where(:group_id => #group_id, :user_id =>
#user_id)
#newuser.destroy
redirect_to(:controller => 'groups', :action => 'show', :id => params[:id])
end
but I get this error
ActiveRecord::RecordNotFound in GroupsController#leave
Couldn't find GroupUser without an ID
If you need more info about the code please let me know.
The other answers addressed your code example, but not the question in the title. You cannot use Model.find(params[:id]).where() in ActiveRecord (Rails' default ORM). Model.find returns a single record (an instance of your Model class) but Model.where returns a Model::ActiveRecord_Relation object (an array-like object). The .where method is only available on your Model class directly (i.e. Model.where) or on an ActiveRecord_Relation. If you are chaining methods, you need to make sure each method returns an ActiveRecord_Relation. For example:
Model.where("name = ?", "max")
.order(created_at: :desc)
.reorder(created_at: :asc)
.includes(:sent_comments)
Model.find(params[:id]) will generate such kind of query
*Lets take an example : GroupUser.find(params[:id]) will generate sql equivalent*
select * from group_users where id = params[:id]
If you want to add where condition do something like this
GroupUser.where("# id ={params[:id]} and someColumn = #{somevariable}")
You are not passing the groupuser_id in the params hash. Instead of storing it in the #group_id and #user_id, just try this:
#newuser= GroupUser.find_by_group_id_and_user_id(params[:group_id],params[:user_id])
Can you additionally post your params dump so we can figure out how to make a good redirect since i hence that will be a problem also.
Just do:
group = Group.find(params[:group_id])
user = User.find(params[:user_id])
group.users.delete(user)
group.save
This will remove the user from the users association, and will automatically destroy the GroupUser record when the group is saved. See the collection.delete method.
#max pleaner is right, you should not use Model.find().where().
It is incorrect, and if you think about it, you will see that it is redundant. To find the association you wish to destroy, you either need params[:group_id] and params[:user_id], or just params[:group_user_id].
If you have passed the params[:group_user_id] to the leave method, you already have all the information needed to find the association:
def leave
#newuser= GroupUser.find(params[:group_user_id])
#newuser.destroy
redirect_to(:controller => 'groups', :action => 'show', :id => params[:group_id])
end
Or alternatively, If you have passed the params[:group_id] and params[:user_id] to the leave method:
def leave
#newuser= GroupUser.find_by_group_id_and_user_id(params[:group_id], params[:user_id])
#newuser.destroy
redirect_to(:controller => 'groups', :action => 'show', :id => params[:group_id])
end
Also, there are a few other redundancies in your code that you will notice I omitted above. There is no need to assign each parameter to an instance variable before passing the parameter to the find() method:
#model_id = params[:model_id]
#model= Model.find(#model_id)
#can be simplified to:
#model = Model.find(params[:model_id])
Generally, you only want to create instances variables for a record or collection of records you will need to access in a view (such as a Group in the context of your application).
When a user is created on my site I want a User.new instance to execute but I also need to make a Alias.new object too. Users have many Aliases. However, I also need to validate that there are no other Aliases with that name before saving.
From the console my code might look like this:
u = User.new(:name => "Bob")
a = Alias.new(:name => "SirBob", :user_id => u)
But that's doesn't work since u doesn't have a id until I save. So how do I validate both items for uniqueness of name before saving them?
Try this one:
u = User.new
u.aliases.build
Hope this helps...
Use
ActiveRecord::Base.transaction do
u = User.new(:name => "Bob")
a = Alias.new(:name => "SirBob", :user_id => u)
end
and add validates_uniqueness_of :name on Alias model
This will solve your problem.
I'm learning new tricks all the time and I'm always on the lookout for better ideas.
I have this rather ugly method. How would you clean it up?
def self.likesit(user_id, params)
game_id = params[:game_id]
videolink_id = params[:videolink_id]
like_type = params[:like_type]
return false if like_type.nil?
if like_type == "videolink"
liked = Like.where(:user_id => user_id, :likeable_id => videolink_id, :likeable_type => "Videolink").first unless videolink_id.nil?
elsif like_type == "game"
liked = Like.where(:user_id => user_id, :likeable_id => game_id, :likeable_type => "Game").first unless game_id.nil?
end
if liked.present?
liked.amount = 1
liked.save
return true
else # not voted on before...create Like record
if like_type == "videolink"
Like.create(:user_id => user_id, :likeable_id => videolink_id, :likeable_type => "Videolink", :amount => 1)
elsif like_type == "game"
Like.create(:user_id => user_id, :likeable_id => game_id, :likeable_type => "Game", :amount => 1)
end
return true
end
return false
end
I would do something like:
class User < ActiveRecord::Base
has_many :likes, :dependent => :destroy
def likes_the(obj)
like = likes.find_or_initialize_by_likeable_type_and_likeable_id(obj.class.name, obj.id)
like.amount += 1
like.save
end
end
User.first.likes_the(VideoLink.first)
First, I think its wrong to deal with the "params" hash on the model level. To me its a red flag when you pass the entire params hash to a model. Thats in the scope of your controllers, your models should have no knowledge of the structure of your params hash, imo.
Second, I think its always cleaner to use objects when possible instead of class methods. What you are doing deals with an object, no reason to perform this on the class level. And finding the objects should be trivial in your controllers. After all this is the purpose of the controllers. To glue everything together.
Finally, eliminate all of the "return false" and "return true" madness. The save method takes care of that. The last "return false" in your method will never be called, because the if else clause above prevents it. In my opinion you should rarely be calling "return" in ruby, since ruby always returns the last evaluated line. In only use return if its at the very top of the method to handle an exception.
Hope this helps.
I'm not sure what the rest of your code looks like but you might consider this as a replacement:
def self.likesit(user_id, params)
return false unless params[:like_type]
query = {:user_id => user_id,
:likeable_id => eval("params[:#{params[:like_type]}_id]"),
:likeable_type => params[:like_type].capitalize}
if (liked = Like.where(query).first).present?
liked.amount = 1
liked.save
else # not voted on before...create Like record
Like.create(query.merge({:amount => 1}))
end
end
I assume liked.save and Like.create return true if they are succesful, otherwise nil is returned. And what about the unless game_id.nil? ? Do you really need that? If it's nil, it's nil and saved as nil. But you might as well check in your data model for nil's. (validations or something)
I have an index action in rails that can handle quite a few params eg:
params[:first_name] # can be nil or first_name
params[:age] # can be nil or age
params[:country] # can be nil or country
When finding users I would like to AND all the conditions that are not nil. This gives me 8 permutations of the find conditions.
How can I can I keep my code DRY and flexible and not end up with a bunch of if statements just to build the conditions for the find. Keep in mind that if no conditions are specified I just want to return User.all
How about something like:
conditions = params.only(:first_name, :age, :country)
conditions = conditions.delete_if {|key, value| value.blank?}
if conditions.empty?
User.all
else
User.all(:conditions => conditions)
end
I would normally use named scopes for something like this:
class User < ActiveRecord::Base
named_scope :name_like, lambda {|name| {:conditions => ["first_name LIKE ?", "#{name}%"]}}
named_scope :age, lambda {|age| {:conditions => {:age => age}}}
named_scope :in_country, lambda {|country| {:conditions => {:country => country}}}
end
class UsersController < ActionController
def index
root = User
root = root.name_like(params[:first_name]) unless params[:first_name].blank?
root = root.age(params[:age]) unless params[:age].blank?
root = root.country(params[:country]) unless params[:age].blank?
#users = root.paginate(params[:page], :order => "first_name")
end
end
That's what I normally do.
This seems to work quite nicely:
conditions = params.slice(:first_name, :age, :country)
hash = conditions.empty? ? {} : {:conditions => conditions}
#users = User.all hash
Using James Healy answer, I modify the code to be used in Rails 3.2 (in case anyone out there need this).
conditions = params.slice(:first_name, :age, :country)
conditions = conditions.delete_if {|key, value| value.blank?}
#users = User.where(conditions)
You could try Ambition, or a number of other ActiveRecord extensions.
This works for me too
conditions = params[:search] ? params[:search].keep_if{|key, value| !value.blank?} : {}
User.all(:conditions => conditions)
If you happen to be on an ancient project (Rails 2.x) and very messy, you could do something like the following for adding new fields to the original query.
Original code:
User.find(:all,
:conditions => ['first_name LIKE ? AND age=? AND country=?',
"#{name}%", age, country]
Adding a new dynamic condition on zip_code field:
zip_code = params[:zip_code] # Can be blank
zip_query = "AND zip_code = ?" unless zip_code.blank?
User.find(:all,
:conditions => ['first_name LIKE ? AND age=? AND country=? #{zip_query}',
"#{name}%", age, country, zip_code].reject(&:blank?)
Adding a reject(&:blank?) to the conditions arrays will filter the nil value.
Note: The other answers are much better if you are coding from zero, or refactoring.
Is there any way of overriding a model's id value on create? Something like:
Post.create(:id => 10, :title => 'Test')
would be ideal, but obviously won't work.
id is just attr_protected, which is why you can't use mass-assignment to set it. However, when setting it manually, it just works:
o = SomeObject.new
o.id = 8888
o.save!
o.reload.id # => 8888
I'm not sure what the original motivation was, but I do this when converting ActiveHash models to ActiveRecord. ActiveHash allows you to use the same belongs_to semantics in ActiveRecord, but instead of having a migration and creating a table, and incurring the overhead of the database on every call, you just store your data in yml files. The foreign keys in the database reference the in-memory ids in the yml.
ActiveHash is great for picklists and small tables that change infrequently and only change by developers. So when going from ActiveHash to ActiveRecord, it's easiest to just keep all of the foreign key references the same.
You could also use something like this:
Post.create({:id => 10, :title => 'Test'}, :without_protection => true)
Although as stated in the docs, this will bypass mass-assignment security.
Try
a_post = Post.new do |p|
p.id = 10
p.title = 'Test'
p.save
end
that should give you what you're looking for.
For Rails 4:
Post.create(:title => 'Test').update_column(:id, 10)
Other Rails 4 answers did not work for me. Many of them appeared to change when checking using the Rails Console, but when I checked the values in MySQL database, they remained unchanged. Other answers only worked sometimes.
For MySQL at least, assigning an id below the auto increment id number does not work unless you use update_column. For example,
p = Post.create(:title => 'Test')
p.id
=> 20 # 20 was the id the auto increment gave it
p2 = Post.create(:id => 40, :title => 'Test')
p2.id
=> 40 # 40 > the next auto increment id (21) so allow it
p3 = Post.create(:id => 10, :title => 'Test')
p3.id
=> 10 # Go check your database, it may say 41.
# Assigning an id to a number below the next auto generated id will not update the db
If you change create to use new + save you will still have this problem. Manually changing the id like p.id = 10 also produces this problem.
In general, I would use update_column to change the id even though it costs an extra database query because it will work all the time. This is an error that might not show up in your development environment, but can quietly corrupt your production database all the while saying it is working.
we can override attributes_protected_by_default
class Example < ActiveRecord::Base
def self.attributes_protected_by_default
# default is ["id", "type"]
["type"]
end
end
e = Example.new(:id => 10000)
Actually, it turns out that doing the following works:
p = Post.new(:id => 10, :title => 'Test')
p.save(false)
As Jeff points out, id behaves as if is attr_protected. To prevent that, you need to override the list of default protected attributes. Be careful doing this anywhere that attribute information can come from the outside. The id field is default protected for a reason.
class Post < ActiveRecord::Base
private
def attributes_protected_by_default
[]
end
end
(Tested with ActiveRecord 2.3.5)
Post.create!(:title => "Test") { |t| t.id = 10 }
This doesn't strike me as the sort of thing that you would normally want to do, but it works quite well if you need to populate a table with a fixed set of ids (for example when creating defaults using a rake task) and you want to override auto-incrementing (so that each time you run the task the table is populate with the same ids):
post_types.each_with_index do |post_type|
PostType.create!(:name => post_type) { |t| t.id = i + 1 }
end
Put this create_with_id function at the top of your seeds.rb and then use it to do your object creation where explicit ids are desired.
def create_with_id(clazz, params)
obj = clazz.send(:new, params)
obj.id = params[:id]
obj.save!
obj
end
and use it like this
create_with_id( Foo, {id:1,name:"My Foo",prop:"My other property"})
instead of using
Foo.create({id:1,name:"My Foo",prop:"My other property"})
This case is a similar issue that was necessary overwrite the id with a kind of custom date :
# in app/models/calendar_block_group.rb
class CalendarBlockGroup < ActiveRecord::Base
...
before_validation :parse_id
def parse_id
self.id = self.date.strftime('%d%m%Y')
end
...
end
And then :
CalendarBlockGroup.create!(:date => Date.today)
# => #<CalendarBlockGroup id: 27072014, date: "2014-07-27", created_at: "2014-07-27 20:41:49", updated_at: "2014-07-27 20:41:49">
Callbacks works fine.
Good Luck!.
For Rails 3, the simplest way to do this is to use new with the without_protection refinement, and then save:
Post.new({:id => 10, :title => 'Test'}, :without_protection => true).save
For seed data, it may make sense to bypass validation which you can do like this:
Post.new({:id => 10, :title => 'Test'}, :without_protection => true).save(validate: false)
We've actually added a helper method to ActiveRecord::Base that is declared immediately prior to executing seed files:
class ActiveRecord::Base
def self.seed_create(attributes)
new(attributes, without_protection: true).save(validate: false)
end
end
And now:
Post.seed_create(:id => 10, :title => 'Test')
For Rails 4, you should be using StrongParams instead of protected attributes. If this is the case, you'll simply be able to assign and save without passing any flags to new:
Post.new(id: 10, title: 'Test').save # optionally pass `{validate: false}`
In Rails 4.2.1 with Postgresql 9.5.3, Post.create(:id => 10, :title => 'Test') works as long as there isn't a row with id = 10 already.
you can insert id by sql:
arr = record_line.strip.split(",")
sql = "insert into records(id, created_at, updated_at, count, type_id, cycle, date) values(#{arr[0]},#{arr[1]},#{arr[2]},#{arr[3]},#{arr[4]},#{arr[5]},#{arr[6]})"
ActiveRecord::Base.connection.execute sql