Good day... I have implemented an after_update callback for a specific column that will be updated, and after that column is updated the callback is meant to add the newly updated value to another column i.e
class Product < ActiveRecord::Base
after_update :update_stock, :if => :produced_changed?
private
def update_stock
self.stock = self.stock + self.produced
end
end
When i run rails console and run "Product.update produced:450" the stock column will automatically add the new value. i.e it works perfectly but when I try updating from the "view/controller" it doesn't work at all.
please is there a reason why?
2.2.4 :004 > h
=> Product id: 1, name: "MAC Air Filter", partnumber: 33440, description: "Air filter", **stock: 3440**, created_at: "2016-04-08 11:38:58", updated_at: "2016-04-19 20:33:00", **produced: 33**
2.2.4 :006 > h.update **produced:3000**
(0.1ms) SAVEPOINT active_record_1
SQL (1.4ms) UPDATE "products" SET "produced" = ?, "updated_at" = ? WHERE "products"."id" = ? [[**"produced", 3000**], ["updated_at", "2016-04-20 13:57:59.377954"], ["id", 1]]
(0.1ms) RELEASE SAVEPOINT active_record_1
=> true
2.2.4 :007 > h
=> Product id: 1, name: "MAC Air Filter", partnumber: 33440, description: "Air filter", **stock: 6440**, created_at: "2016-04-08 11:38:58", updated_at: "2016-04-20 13:57:59", **produced: 3000**>
You have save the product stock to update the product. Here in the update_stock callback you only set the value of product stock. self.stock = self.stock + self.produced only set the value of product stock.
To update the stock value you have to save in callback as:
class Product < ActiveRecord::Base
after_update :update_stock, :if => :produced_changed?
private
def update_stock
self.stock = self.stock + self.produced
self.save
end
end
But it runs the infinite loop and gives error. So you have to set the stock value before update using before_update callback.
class Product < ActiveRecord::Base
before_update :update_stock, :if => :produced_changed?
private
def update_stock
self.stock = self.stock + self.produced
end
end
And save the value of product in controller.
you should call save method in your callback or use callback before_update
Related
This question already has answers here:
Rails HABTM setup, model object, & join_table insertion controller setup
(2 answers)
Closed 2 years ago.
I'm new to Ruby on Rails, and I'm developing a backend API.
Currently, I got 2 Active Record Models called Book and Genre.
Active Record Model
class Book < ActiveRecord::Base
has_and_belongs_to_many :genres
end
class Genre < ActiveRecord::Base
hast_and_belongs_to_many :books
end
DB Schema Model
create_table :books do |t|
t.string "title"
end
create_table :genres do |t|
t.string "genre"
end
create_join_table :books, :genres do |t|
t.index [:book_id, :genre_id]
t.index [:genre_id, :book_id]
end
REST POST Request
# POST /book
def create
book= Book.new(books_params)
if book.save
render json: {status: 'SUCCESS', message:'Book Saved', data: book},status: :ok
else
render json: {status: 'ERROR', message: 'Booknot saved', data: book.errors}, status: :unprocessable_entity
end
end
private
def books_params
params.require(:book).permit(:title)
end
QUESTION
I'd like to make an HTTP Post request to create a new book with it's genres. I've already tested the book insertion (without genres, just passing the book name), it works perfectly. However I'd also like to add some genre categories.
Both the has_many and has_and_belongs_to_many class methods create a set of _ids setters and getters:
book = Book.new
book.genre_ids = [1, 2, 3]
book.genre_ids == [1, 2, 3] # true
These setters will automatically create and delete rows in the join table. Since ActiveModel::AttributeAssignment maps the hash argument to .new, .update and .create to setters in the model all you need to do is whitelist the genre_ids parameter:
def books_params
params.require(:book).permit(:title, genre_ids: [])
end
Passing an empty array permits an array of permitted scalar values like for example numericals or strings.
Iām using Rails 4.2.3 and MySQL 5.5.37. I want to write a finder method for my model, so I have written this (./app/models/user_object.rb):
class UserObject < ActiveRecord::Base
validates :day, :presence => true
validates_numericality_of :total
validates :object, :presence => true
def find_total_by_user_object_and_year
UserObject.sum(:total, :conditions => ['user_id = ?', params[:user_id], 'object = ?', params[:object], 'year(day) = ?', params[:year]])
end
end
However, when I attempt to invoke the method within a controller like so
#my_total = UserObject.find_total_by_user_object_and_year(session["user_id"], 3, #year)
I get the following error
undefined method `find_total_by_user_object_and_year' for #<Class:0x007fb7551514e0>
What is the right way to define my finder method?
Use self.method to define class method:
def self.find_total_by_user_object_and_year
sum(:total, :conditions => ['user_id = ?', params[:user_id], 'object = ?', params[:object], 'year(day) = ?', params[:year]])
end
In this case UserObject inside class method definition is redundant, besause it is same as self. Now you can write:
UserObject.find_total_by_user_object_and_year(params)
I'm using first_or_create to populate a table with a list of email subscribers (called members). The code is as follows:
def community_members=(members)
self.members = members.split(",").map do |member|
Member.where(email: member.strip, community_id: self.id).first_or_create! unless member.strip == nil
end
end
Everything works fine, except that when I add additional emails to the same community, the table turns the "community_id" column for all previous rows to NULL.
Here's the server log:
Member Load (0.2ms) SELECT "members".* FROM "members" WHERE "members"."email" = $1 AND "members"."community_id" = $2 ORDER BY "members"."id" ASC LIMIT 1 [["email", "lisa#holy.com"], ["community_id", 1]]
SQL (0.3ms) INSERT INTO "members" ("email", "community_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["email", "lisa#holy.com"], ["community_id", 1], ["created_at", "2015-04-30 16:14:25.930012"], ["updated_at", "2015-04-30 16:14:25.930012"]]
Member Load (0.2ms) SELECT "members".* FROM "members" WHERE "members"."community_id" = $1 [["community_id", 1]]
SQL (0.4ms) UPDATE "members" SET "community_id" = NULL WHERE "members"."community_id" = $1 AND "members"."id" = 30 [["community_id", 1]]
(0.3ms) COMMIT
The first "Member" load does exactly what it's supposed to do. But for some reason it always ends with the second Member load that goes in and sets all "community_id" fields to NULL.
Right now I call :community_member from a form on a community page:
<%= form_for(#community) do |f| %>
<%= f.text_area :community_members, class:"form-control input-lg", placeholder:"Please add your list of comma separated member email addresses here" %>
<%= f.submit "Save", class: "btn btn-lg btn-green btn-block pad-top" %>
<% end %>
Seems like I'm missing something obvious here. Any ideas? Thank you.
You're going to want to find by the unique attribute, email, and create by community name, I think.
If that's the case, you'll have to do something like:
Member.where(email: member.strip).first_or_create(community: self) unless...
If you have records with non-unique emails, you'll have to redesign your associations.
class Subscriber
#this should have the email attribute
has_many :community_subscribers
has_many :communities, through: :community_subscribers
end
class CommunitySubscriber
#this is a 'bridge' table
belongs_to :subscriber
belongs_to :community
end
class Community
has_many :community_subscribers
has_may :subscribers, through: :community_subscribers
#I suggest new method and arg names
#Using self will keep the query within the scope of the community you are working on
#This also allows for creation of Subscriber records if you insist placing that here
#are you sure you want to map instead of just iterating the split list of emails?
def new_subscribers(emails)
emails.split(",").map do |email|
clean_email = email.strip
subscriber = Subscriber.where(email: clean_email).find_or_create unless clean_email.blank?
self.community_subscribers.where(subscriber: subscriber).first_or_create unless subscriber.blank?
end
end
end
Docs:
http://apidock.com/rails/v3.2.1/ActiveRecord/Relation/first_or_create
http://guides.rubyonrails.org/v3.2.13/active_record_querying.html#first_or_create
How can I save a unique string to my database and if the value exists increment it.
The behaviour I'm after is similar to when you save a file e.g. foo.txt, foo1.txt
I do NOT want to return a 'not unique value' error message.
class Person < ActiveRecord::Base
attr_accessible :name
end
Person.create(:name => 'Dave') # => 'Dave'
Person.create(:name => 'Dave') # => 'Dave1'
Person.create(:name => 'Dave') # => 'Dave2'
I'm using ruby, rails and mysql
you don't need to set validation uniqueness to your model . You can use something like this:
class Person < ActiveRecord::Base
attr_accessible :name
before_create :check_and_increment
private
def check_and_increment
if Person.exists? name: self.name
similar_persons = get_all_with_same_name self.name
next_num = similar_persons.max_by{|m| m.scan(/\d+/)}.scan(/\d+/).first.nil? ? 1 : similar_persons.max_by{|m| m.scan(/\d+/)}.scan(/\d+/).first.to_i + 1
self.name = "#{self.name}#{next_num}"
else
true
end
end
def get_all_with_same_name name
where("name LIKE ?", 'name%')
end
end
This is simple idea for your problem. Need to be carefull with persons like Anna and Annastasia, for example . These persons has different names but their names are overlap.
Hope this will help you .
I could not find any rails or mysql features to do this so I created the follow 3 methods and
def unique_name(name)
if Person.exists?(:name => name)
generate_unique_name(name)
else
name
end
end
def similar_names(name)
Person.where("name LIKE ?", "#{name}%").pluck(:name)
end
def generate_unique_name(name)
number = similar_names(name).map{ |a| a.scan(/\d+$/).max.to_i }.compact.max.to_i + 1
"#{name} #{number}"
end
...
Person.create(:name => unique_name('Dave'))
I have a nested form set up that will allow me to create up to 7 "Schedule" instances at one time. Each instance will allow a user to assign a schedule_start_time(datetime), schedule_end_time(datetime), and notes(String) field value. When I submit the form with a few of these completed, the parameters array looks exactly as I would expect:
{"utf8"=>"ā",
"authenticity_token"=>"HEoylzovRgr7BCZH47iNRPfizDHeVFMLTEmIiNeudcw=",
"workout"=>{"id"=>"2",
"schedules_attributes"=>{
"0"=>{"scheduled_start_time"=>"06/01/2011",
"scheduled_end_time"=>"06/02/2011",
"notes"=>"Notes 1"},
"1"=>{"scheduled_start_time"=>"",
"scheduled_end_time"=>"",
"notes"=>""},
"2"=>{"scheduled_start_time"=>"06/03/2011",
"scheduled_end_time"=>"06/04/2011",
"notes"=>"Notes 2"},
"3"=>{"scheduled_start_time"=>"",
"scheduled_end_time"=>"",
"notes"=>""},
"4"=>{"scheduled_start_time"=>"06/16/2011",
"scheduled_end_time"=>"06/30/2011",
"notes"=>"Notes 3"},
"5"=>{"scheduled_start_time"=>"",
"scheduled_end_time"=>"",
"notes"=>""},
"6"=>{"scheduled_start_time"=>"",
"scheduled_end_time"=>"",
"notes"=>""}}}, "commit"=>"Submit"}
In the controller, I filter those "schedules" with a blank start_date. My params list then looks like this:
{"utf8"=>"ā",
"authenticity_token"=>"HEoylzovRgr7BCZH47iNRPfizDHeVFMLTEmIiNeudcw=",
"workout"=>{"id"=>"2",
"schedules_attributes"=>{
"0"=>{"scheduled_start_time"=>"06/01/2011",
"scheduled_end_time"=>"06/02/2011",
"notes"=>"Notes 1"},
"2"=>{"scheduled_start_time"=>"06/03/2011",
"scheduled_end_time"=>"06/04/2011",
"notes"=>"Notes 2"},
"4"=>{"scheduled_start_time"=>"06/16/2011",
"scheduled_end_time"=>"06/30/2011",
"notes"=>"Notes 3"}}},
"commit"=>"Submit",
"action"=>"create",
"controller"=>"schedules"}
The SQL that gets generaated is not what I would expect however:
(0.1ms) BEGIN WARNING: Can't
mass-assign protected attributes: id
SQL (0.2ms) INSERT INTO schedules
(created_at, notes,
scheduled_end_time,
scheduled_start_time, updated_at,
workout_id) VALUES ('2011-06-29
03:23:45', 'Notes 1', '2011-02-06
00:00:00', '2011-01-06 00:00:00',
'2011-06-29 03:23:45', 2)
SQL
(0.1ms) INSERT INTO schedules
(created_at, notes,
scheduled_end_time,
scheduled_start_time, updated_at,
workout_id) VALUES ('2011-06-29
03:23:45', 'Notes 2', '2011-04-06
00:00:00', '2011-03-06 00:00:00',
'2011-06-29 03:23:45', 2)
SQL
(0.2ms) INSERT INTO schedules
(created_at, notes,
scheduled_end_time,
scheduled_start_time, updated_at,
workout_id) VALUES ('2011-06-29
03:23:45', 'Notes 3', NULL, NULL,
'2011-06-29 03:23:45', 2) (0.5ms)
COMMIT
Some of the valid date values are in the params array, but are being filtered out before the SQL commits.
Here is the controller code:
def create
#workout = Workout.find(params[:workout][:id])
7.times do |count|
#schedule = params[:workout][:schedules_attributes]["#{count}"]
if (#schedule[:scheduled_start_time].blank?)
params[:workout][:schedules_attributes].delete count.to_s.to_sym
end
end
if #workout.update_attributes(params[:workout])
redirect_to schedules_url, :notice => "Successfully updated schedule."
else
render :action => 'new'
end
end
And the Workout Model
class Workout < ActiveRecord::Base
belongs_to :team, :class_name => "Team", :foreign_key => "team_id"
has_many :exercise_instances, :dependent => :destroy
validates :name,
:presence => true
has_many :schedules, :dependent => :destroy
accepts_nested_attributes_for :schedules
end
And the Schedule Model
class Schedule < ActiveRecord::Base
attr_accessible :workout_id, :scheduled_start_time, :scheduled_end_time, :notes
belongs_to :workout
end
Any direction would be welcome. I'm suspecting caching at some level, but I'm just not sure where to start looking. Thanks!
I think you are reinventing the wheel here and it's the source of the problem. Try the following:
class Workout < ActiveRecord::Base
has_many :schedules, :dependent => :delete_all
accepts_nested_attributes_for :schedules, :allow_destroy => true, :reject_if => proc { |schedule| schedule[:scheduled_start_time].blank? }
end
You will still need to restrict the number of schedules to 7. I would recommend checking out existing solutions for this behavior; there are a lot of solid patterns on this already.
Personally I would do something like
params[:workout][:schedules_attributes].each do |sched|
#workout.schedules << #workout.schedules.build(sched) if sched[:scheduled_start_time].present?
end
if #workout.save # etc
And not use nested_attributes_for. This will guarantee that you will only get what was sent.
I've found with nested_attributes that it's often best to delete and recreate on edit every time as well, which may or may not be what you want.
I'm sure others with better nested_attributes 'fu' than me may have better solutions.