I am writing a little plugin for my company redmine to assign unique documents [progressive] codes
the code I wrote so far works, but I don't think it is multi threads proof as there is a chance two users get the same document code.
I would like to find a way to lock the table while getting the last number and creating the new record with the incremented document number (D9999)
This is the active record:
class Documenti < ActiveRecord::Base
unloadable
def self.nextest
record=self.last
if (record.nil?) then ultimo=sprintf("D0000")
elsif (record.codice.nil?) then ultimo=sprintf("D0000")
else ultimo=record.codice
end
if (/^D[0-9]{4}/ =~ ultimo) == 0 then
c=ultimo.split("D")
p=c[1].to_i + 1
t=sprintf("D%04i",p)
end
return t
end
end
Controller then is like this:
def new
#documenti = Documenti.new
#documenti.codice=Documenti.nextest
respond_to do |format|
format.html # new.html.erb
end
end
so far the nextest value is not saved and another user can take the same value.
Related
I would like to store each value from an array.
For example the form sends me this data:
"attendance"=>{"event_id"=>"6", "member_id"=>["16", "28", "26"]}
I'd like the database to store the data as:
INSERT INTO "attendances" ("event_id", "member_id") VALUES ("6", "16")
INSERT INTO "attendances" ("event_id", "member_id") VALUES ("6", "28")
INSERT INTO "attendances" ("event_id", "member_id") VALUES ("6", "26")
I've tried to use the usual way of inserting data in Rails, but it failed because the member_ids didn't get passed (I've tried to print the member_ids after the Attendance.new(attendance_params)):
def create
#attendance = Attendance.new(attendance_params)
# puts #attendance[:event_id]
# puts #attendance[:member_id] -> Nothing showed up here.
if #attendance.save
flash[:success] = "Successfully created"
redirect_to new_attendance_path
else
#error_msg = #attendance.errors.full_messages
flash[:error] = #error_msg # Prints ["Member must exist"]
redirect_to new_attendance_path
end
end
I've also tried creating a new function in the model to change the Attendance.new but it'll return
NoMethodError - undefined method `new_each' for #<Class:0x000000000d1e7280>: app/controllers/attendances_controller.rb:17:in `create'
This is my current model:
class Attendance < ApplicationRecord
belongs_to :event
belongs_to :member
# def new_each(attendance)
# attendance_event = attenance[:event_id]
# attendance_members = attendance[:member_id]
# I tried to iterate and save each data here.
# end
end
So, how do I save each value from an array input (from the form) and save it to database?
Any answers and comment will be very appreciated.
You are trying to add attendance in bulk, For that you can do 2 things,
use gem bulk_insert, it's very easy to use here is the link https://github.com/jamis/bulk_insert
you need to iterate through all the members and events from params and create a record for each. though it's a very dirty way.
sample code would be like this. you can modify it as per your need.
errors = {}
attendance_params[:member_id].each do |member_id|
attendence = Attendence.new(event_id:attendance_params[:event_id], member_id: member_id)
errors[member_id] = "Could not save attendance, error =
attendance.errors.full_messages" unless
attendence.save
end
Could someone take a look at my code and let me know if there is a better way to do this, or even correct where I'm going wrong please? I am trying to create a new row for each venue and variant.
Example:
venue_ids => ["1","2"], variant_ids=>["10"]
So, I would want to add in a row which has a venue_id of 1, with variant_id of 10. And a venue_id of 2, with variant_id of 10
I got this working, and it's now passing in my two arrays. I think I am almost there I'm not sure the .each is the right way to do it, but I think that I'm on the right track haha. I have it submitting, however, where would I put my #back_bar.save? because this might cause issues as it won't redirect
Thanks in advance.
def create
#back_bar = BackBar.new
#venues = params[:venue_ids]
#productid = params[:product_id]
#variants = params[:variant_ids]
# For each venue we have in the array, grab the ID.
#venues.each do |v|
#back_bar.venue_id = v
# Then for each variant we associate the variant ID with that venue.
#variants.each do |pv|
#back_bar.product_variant_id = pv
# Add in our product_id
#back_bar.product_id = #productid
# Save the venue and variant to the DB.
if #back_bar.save
flash[:success] = "#{#back_bar.product.name} has been added to #{#back_bar.venue.name}'s back bar."
# Redirect to the back bar page
redirect_to back_bars_path
else
flash[:alert] = "A selected variant for #{#back_bar.product.name} is already in #{#back_bar.venue.name}'s back bar."
# Redirect to the product page
redirect_to discoveries_product_path(#back_bar.product_id)
end
end # Variants end
end # Venues end
end
private
def back_bar_params
params.require(:back_bar).permit(:venue_id,
:product_id,
:product_variant_id)
end
as i said in comments
this is untested code and just showing you how it's possible to do with ease.
class BackBar
def self.add_set(vanue_ids, variant_ids)
values = vanue_ids.map{|ven|
variant_ids.map{|var|
"(#{ven},#{var})"
}
}.flatten.join(",")
ActiveRecord::Base.connection.execute("INSERT INTO back_bars VALUES #{values}")
end
end
def create
# use in controller
BackBar.add_set(params[:venue_ids], params[:variant_ids])
# ...
end
Goal: dynamically update another Model's properties (Tracker) from Controller (cards_controller.rb), when cards_controller is running the def update action.
Error I receive : NameError in CardsController#update, and it calls out the 2nd last line in the
def update_tracker(card_attribute) :
updated_array = #tracker.instance_variable_get("#{string_tracker_column}")[Time.zone.now, #card.(eval(card_attribute.to_s))]
Perceived problem: I have everything working except that I don't know the appropriate way to 'call' the attribute of Tracker correctly, when using dynamic attributes.
The attribute of the Tracker is an array (using PG as db works fine), I want to
figure out what property has been changed (works)
read the corresponding property array from Tracker's model, and make a local var from it. (works I think, )
push() a new array to the local var. This new array contains the datetime (of now) and, a string (with the value of the updated string of the Card) (works)
updated the Tracker with the correct attribute.
With the following code from the cards_controller.rb
it's the if #card.deck.tracked in the update method that makes the process start
cards_controller.rb
...
def update
#card = Card.find(params[:id])
if #card.deck.tracked
detect_changes
end
if #card.update_attributes(card_params)
if #card.deck.tracked
prop_changed?
end
flash[:success] = "Card info updated."
respond_to do |format|
format.html { render 'show' }
end
else
render 'edit'
end
end
...
private
def detect_changes
#changed = []
#changed << :front if #card.front != params[:card][:front]
#changed << :hint if #card.hint != params[:card][:hint]
#changed << :back if #card.back != params[:card][:back]
end
def prop_changed?
#changed.each do |check|
#changed.include? check
puts "Following property has been changed : #{check}"
update_tracker(check)
end
end
def update_tracker(card_attribute)
tracker_attribute = case card_attribute
when :front; :front_changed
when :back; :back_changed
when :hint; :hint_changed
end
string_tracker_column = tracker_attribute.to_s
#tracker ||= Tracker.find_by(card_id: #card.id)
updated_array = #tracker.instance_variable_get("#{string_tracker_column}")[Time.zone.now, #card.(eval(card_attribute.to_s))]
#tracker.update_attribute(tracker_attribute, updated_array)
end
Edit: For clarity here's the app/models/tracker.rb:
class Tracker < ActiveRecord::Base
belongs_to :card
end
Your use of instance_variable_get has been corrected, however this approach is destined to fail because ActiveRecord column values aren't stored as individual instance variables.
You can use
#tracker[string_column_changed]
#card[card_attribute]
To retrieve attribute values by name. If you want to get an association, use public_send. The latter is also useful if there is some accessor wrapping the column value (eg carrierwave)
From your error it seem your issue is this:
#tracker.instance_variable_get("#{string_tracker_column}")
evaluates to this after string interpolation:
#tracker.instance_variable_get("front_changed")
which is incorrect use of instance_variable_get. It needs an # prepended:
#tracker.instance_variable_get("#front_changed")
Seems like using instance_variable_get is unnecessary, though, if you set attr_reader :front_changed on the Tracker model.
I have a 'publication' data structure, and my table name in postgres is 'publications'. I get a tweet from streaming and parse it according in class TweetFetcher. How do I call publication controller to save it to the database? PublicationController has the standard scrum abilities (new, create, show, edit...), where create is:
def create
#publication = Publications.new(params[:publication])
if #publication.save
redirect_to :action => 'list'
else
#subjects = Subject.find(:all)
render :action => 'new'
end
end
and parte of my twitter code is
class TweetFetcher
def saveTweet(parsedTweet)
pT = JSON.parse(parsedTweet)
#save here. like this?
#PublicationController.create(parsedTweet)
end
end
You shouldn't be using your controller in this situation, instead you can just build your record like this:
class TweetFetcher
def saveTweet(parsedTweet)
pT = JSON.parse(parsedTweet)
publication = Publications.new
publication.sample_field = pT.sample_field
... # set additional attributes
publication.save
end
end
Additionally, here is some advice/information that is not directly related to your question, but are things you should know:
The class name of your models should be singular, so instead of Publications, it should be Publication.
Method and variable names should be in snake case rather than camel case (i.e. save_tweet instead of saveTweet or parsed_tweet instead of parsedTweet).
The new hash syntax is prefered, unless of course you are using a Ruby version below 1.9. This looks like { key: value } instead of { key => value }.
Indentation for Ruby code is typically 2 spaces.
That being said, I would change your code like this:
PublicationsController#create
def create
#publication = Publication.new(params[:publication])
if #publication.save
redirect_to action: 'list'
else
#subjects = Subject.find(:all)
render action: 'new'
end
end
TweeFetcher
class TweetFetcher
def save_tweet(tweet)
parsed_tweet = JSON.parse(tweet)
publication = Publication.new
publication.sample_field = parsed_tweet.sample_field
... # set additional attributes
publication.save
end
end
I'm creating a picture-rating app where users can click on pictures and rate them on a scale from 1 to 5. I'm trying to calculate the average rating of a picture. Before when users clicked on a rating value, that value became the picture's rating.
Rating: 5
If a user clicked on 1, the rating would change to 1
Rating: 1
When reality, the rating should have been 3.
(5 + 1) / 2
=> 3
Here's what I've accomplished so far in implementing this feature.
I added a migration to create two new columns for my Pictures Table
rails g migration AddRatingsToPictures ratings_count: integer, rating_total: integer
Both the new attributes, ratings_count and rating_total are integer types, meaning they are assigned a nil value at default.
p = Picture.first
p.attribute_names
=> ['id', 'title', 'category', 'stars', 'updated_at', 'created_at',
'ratings_count', 'rating_total']
p.ratings_count
=> nil
p.rating_total
=> nil
My only problem is the NilClass Error.
Here is my update method in my PicturesController.
def update
#picture = Picture.find(params[:id])
#picture.ratings_count = 0 if #picture.stars.nil?
#picture.rating_total = #picture.stars
#picture.rating_total += #picture.stars if #picture.stars_changed?
#picture.ratings_count += 1 if #picture.rating_total_changed?
if #picture.update_attributes(picture_params)
unless current_user.pictures.include?(#picture)
#picture = Picture.find(params[:id])
current_user.pictures << #picture
redirect_to #picture, :flash => { :success => "Thank you! This picture has been added to your Favorites List" }
else
redirect_to :action => 'index'
flash[:success] = 'Thank you! This picture has been updated'
end
else
render 'edit'
end
end
Here is my picture_param method in my PicturesController
def picture_params
params.require(:picture).permit(:title, :category, :genre, :stars)
end
Here is what the two new columns do
ratings_count: Calculates the number of times a picture has been rated
rating_total: Calculates the sum of the stars a picture has received
In the above code, I first set the ratings_count to 0 if the picture doesn't have a rating. This means that the picture hasn't been rated yet.
I then need to initially set the rating_total to the number of stars a picture has. If a user changed the star rating, I would add those stars to the rating_total. And if the total increased, that's my cue to increase the number of ratings.
Obviously, to calculate the average, I'd do something like this.
(#picture.rating_total / #picture.ratings_count).to_f
Now, I think I have the right idea but I know why this doesn't work. When columns are created with an integer value, by default they are set to nil. This leads to a NilClass Error when I load the web page.
undefined method `/' for nil:NilClass
Here is my code in the View
<li><strong>Rating:</strong> <%= pluralize((#picture.rating_total / #picture.ratings_count), 'Star') %></li>
Ok, the main reason it is not working is because
you fetch the picture
you check the stars from the database, and the NOT the passed form-parameters
you do update_attributes, which if I am not mistaken, used to set attributes and then save the complete object, but since rails 4 only updates the passed attributes (which is what you would expect)
One small remark: keeping the rating correct is a function I would place in the model, NOT in the controller.
Furthermore, how to handle the if nil, initialise to zero I wrote a short blogpost about. In short: overrule the getter.
So I would propose the following solution. In your model write
class Picture < ActiveRecord::Base
def ratings_count
self[:ratings_count] || 0
end
def ratings_total
self[:ratings_total] || 0
end
def add_rating(rating)
return if rating.nil? || rating == 0
self.ratings_count += 1
self.ratings_total += rating
self.stars = self.ratings_total.to_f / self.ratings_count
self.save
end
def rating
return 0 if self.ratings_count == 0
self.ratings_total.to_f / self.ratings_count
end
and then the code in your controller becomes much cleaner and readable:
def update
#picture = Picture.find(params[:id])
stars = picture_params.delete(:stars)
if #picture.update_attributes(picture_params)
#picture.add_rating stars
unless current_user.pictures.include?(#picture)
current_user.pictures << #picture
redirect_to #picture, :flash => { :success => "Thank you! This picture has been added to your Favorites List" }
else
redirect_to :action => 'index'
flash[:success] = 'Thank you! This picture has been updated'
end
else
render 'edit'
end
end
I first delete the :stars from the parameters, because I do not want to save those, I want to use those for the add_rating. I then try to update_attributes, which will fail if there are any failing validations, and if that is ok, I will add_rating which itself will handle nil or zero correctly. Well granted: I do not know how you handle a "non-rating" (nil? zero?). It is possible a rating of zero should be added, because it will add a rating, but most UI I know do not allow to select 0 as rating, so you might want to change the zero handling.
This will handle the case of uninitialized (nil) values in your attributes...
def update
#picture = Picture.find(params[:id])
if #picture.stars_changed?
#picture.ratings_count = (#picture.ratings_count || 0) + 1
#picture.rating_total = (#picture.rating_total || 0) + ( #picture.stars || 0)
end
You don't need an array of ratings or ratings persisted to database, assuming you only count votes where the rating changes, you can accumulate the count and the total and divide the two (which is, in fact, what you're doing so I'm preaching to the converted).
Although it seems to me that if I change a picture from 5 to 1 and it only changes to 3, I'm gonna keep clicking 1 :)
You could set the default value on the migration when you created it. But no worries, you can create a new migration to change it:
# Console
rails g migration change_default_for_ratings_count_and_rating_total
# Migration Code
class ChangeDefaultForRatingsCountAndRatingTotal < ActiveRecord::Migration
def change
change_column :pictures, :ratings_count, :integer, default: 0
change_column :pictures, :rating_total, :integer, default: 0
end
end
Keep in mind that some databases don't automatically assign newly updated default values to existing column entries, so maybe you will have to iterate over every picture already created with nil values and set to 0.
Ok, an alternative...
Do an after_initialize so the fields are never, never, ever nil. Even if you're creating a new Picture object, they'll be initialized as zero. Problem will go away.
class Picture << ActiveRecord::Base
after_initialize do |picture|
picture.ratings_count ||= 0
picture.rating_total ||= 0
end
...
end