rails 3 how to limit relationships to 1 - ruby-on-rails

I have Users Bands and Codes.
A user creates a code and that code has a user_id and a band_id.
I have a before save callback generating the code.
before_create :generate_code
private
def generate_code
self.code = SecureRandom.hex(3)
end
I need to add a check, if code exists with user_id and band_id, return false
That way a user will only have one code per band
thoughts?

if you want to keep generating until you get a valid code, try:
def generate_code
begin
self.code = SecureRandom.hex(3)
end while User.exists?(:code => self[:code])
end
if you just want to try one code, and return false, try:
def generate_code
self.code = SecureRandom.hex(3)
return false if User.exists?(:code => self[:code])
end
Note: change "User" to whatever your relation you're checking the collision in :)
Alternatively, you could just try validates :code, :uniqueness => true or something similar :)

Related

Uploading csv file and save in database

I have one problem when I try to save some data into my database, imported from a CSV file (uploaded).
My environment is about a classroom reservation. I have the following code for my model Reservation:
class Reservation < ActiveRecord::Base
require 'csv'
belongs_to :classroom
validates :start_date, presence: true
validates :end_date, presence: true
validates :classroom_id, presence: true
validate :validate_room
scope :filter_by_room, ->(room_id) { where 'classroom_id = ?' % room_id }
def self.import(file)
CSV.foreach(file, headers: true ) do |row|
room_id = Classroom.where(number: row[0]).pluck(:id)
Reservation.create(classroom_id: room_id, start_date: row[1], end_date: row[2])
end
end
private
def validate_room
if Reservation.filter_by_room(classroom_id).nil?
errors.add(:classroom_id, ' has already booked')
end
end
end
The CSV file comes with these three headers: "classroom number", "start date", "end date".
Note that "classroom number" header came from a column of classroom table.
My job is to get the classroom.id using the "number" and create the row in the database of the reservation table.
Ok, but the problem is when I get the classroom_id in "self.import" method and print on the console, he exists. When I use the scope to filter the classroom_id, he is empty.
Expect I've expressed myself like I want.
Sorry for my bad English :/
Edit: Discovered that classroom_id before Reservation.create become nil when I use inside the create method. If I use row[0] works, but I need to use classroom_id.
{ where 'classroom_id = ?' % room_id }
Should be
{ where 'classroom_id = ?', room_id }
The answer is simple, I forgot to use .first after pluck(:id) method.
The pluck method returns a value wrapped in an array:
room_id = Classroom.where(number: row[0]).pluck(:id).first

Rails: Ensure only one boolean field is set to true at a time

I have a Logo model that has fields of name:string, default:boolean. I want the true value to be unique so that only one item in the database can be set to true at once. How do I set my update and new actions in my controller to set all the rest of the values of my logos to false?
Let's say I have the following setup
in my database
Model Logo
name:string | default:boolean |
Item1 | true |
Item2 | false |
Item3 | false |
If I change Item2 default value to true, I want it to loop through all logos and set the rest of them to false, so only one is true at once, so it looks like this.
name:string | default:boolean |
Item1 | false |
Item2 | true |
Item3 | false |
Thanks for any help in advance.
This code is stolen from previous answer and slightly simplified:
def falsify_all_others
Item.where('id != ?', self.id).update_all("default = 'false'")
end
You can use this method in before_save callback in your model.
Actually, it is better to "falsify" only records which values are 'true', like this:
Item.where('id != ? and default', self.id).update_all("default = 'false'")
UPDATE: to keep code DRY, use self.class instead of Item:
self.class.where('id != ? and default', self.id).update_all("default = 'false'")
I think it's good to check if the one you save is true before you falsify others. Otherwise you falsify everyone when you save a record that isn't active.
def falsify_all_others
if self.default
self.class.where('id != ? and default', self.id).update_all("default = 'false'")
end
end
In your controller code you could do something like this.... please note you're probably taking Item2 as a param[...] so you can interchange that below
#items_to_be_falsified = Item.where('id != ?', Item2.id)
#items_to_be_falsified.each do |item|
item.default = false
item.save
end
Please note when you get this working, its good practice to move this into the model, make it into a function and call it like Item2.falsify_all_others like below
def falsify_all_others
Item.where('id != ?', self.id).each do |item|
item.default = false
item.save
end
end
Enjoy!
I also recommend falsifying all your records then making them true.
add_column :users, :name ,:boolean, default: false
Okay there is a few more things you will need.
Don't use the field name default, its usually reserved for the database.
Saving a record with a default as false will set all records to false, this isnt what you want. check to see if we are setting this record to true and the falseify.
before_save :falsify_all_others
def falsify_all_others
if is_default
self.class.where('id != ?', self.id).where('is_default').update_all(:is_default => false)
end
end
More ActiveRecord, less raw SQL decision
after_commit :reset_default, if: :default?
private
def reset_default
self.class.where.not(id: id).where(default: true).update_all(default: false)
end
If you're coming here in a more recent time and are using Rails 6, this should be covered on the database level as well as the model level:
db level:
add_index :items, :default, unique: true, where: '(default IS TRUE)'
model level:
class Item < ApplicationRecord
scope :default, -> { where(default: true) }
validates :default, uniqueness: { conditions: -> { default } }
end
if you want this to work for creating and updating (rails v4)
make note of this tidbit from rails guides
after_save runs both on create and update, but always after the more
specific callbacks after_create and after_update, no matter the order
in which the macro calls were executed.
class Model < ApplicationRecord
before_save :ensure_single_default, if: :is_default?
private
def ensure_single_default
self.class.update_all(is_default: false)
end
end
You don't need to check the id because this callback happens before the truthy one is saved.

Rails 3 - adding an index failsafe to ensure the uniqueness of would-be duplicates

I have a rails question that I have been unable to find an answer for on my own. I apologize if it's very simple or obvious, I'm a complete beginner here.
So I have a column in my db called :client_code, which is defined in the model as the down-cased concatenation of the first character of :first_name, and :last_name. So for instance, if I pull up a new form and enter 'John' for :first_name, and 'Doe' for :last_name, :client_code is automatically given the value of 'jdoe'. Here's the relevant portion of the model code:
class Client < ActiveRecord::Base
before_validation :client_code_default_format
validates_presence_of :first_name, :last_name, :email
validates_uniqueness_of :client_code
...
def client_code_default_format
self.client_code = "#{first_name[0]}#{last_name}".downcase
end
end
I would like to add something to this code so that, in the event that someone enters a different client with the same exact name, it does't fail the uniqueness validation but instead creates a slightly modified :client_code ('jdoe2', for example). I could probably figure out how to add an index to all of them, but I would prefer to only include numbers as a failsafe in the case of duplicates. Can anyone point me in the right direction?
Calculating the number of current matching Client objects with the same client_code should work
def client_code_default_format
preferred_client_code = "#{first_name[0]}#{last_name}".downcase
count = Client.count(:conditions => "client_code = #{preferred_client_code}")
self.client_code = count == 0 ? preferred_client_code : preferred_client_code + count
end
Many thanks to #Dieseltime for his response. I took his suggestion and was able to get the functionality I wanted with some minor changes:
before_validation :format_client_code
validates_presence_of :first_name, :last_name, :email, :company_id
validates_uniqueness_of :client_code
...
def format_client_code
unless self.client_code != nil
default_client_code = "#{first_name[0]}#{last_name}".downcase
count = Client.count(:conditions => "client_code = '#{default_client_code}'")
if count == 0
self.client_code = default_client_code
else
self.client_code = default_client_code + (count + 1).to_s
end
end
end

While loop to create multiple records in rails

I'm building an application where users can purchase tracking numbers. I have an Order model and an Order Transaction model. If the Order Transaction returns from the gateway with success, I'm using an after_save callback to trigger a method that creates the tracking numbers and inserts them into the database. Sometimes a user just orders one, but if they order more than one, I can't seem to get rails to create and insert more than one record.
Here's what I'm using -- I've never had to user a loop like this, so I'm not sure what I'm doing wrong.
def create_trackables
if self.success == true
#order = Order.find(order_id)
#start = 0
while #start < #order.total_tokens
#trackable_token = Tracker.create_trackable_token
#start += 1
#trackable ||= Tracker.new(
:user_id => #current_user,
:token => #trackable_token,
:order_id => order_id
)
#trackable.save
end
end
end
dmarkow is right that you should use trackable instead of #trackable but you also should be using = instead of ||=. You also might as well just use create. Here's how I'd write it: def create_trackables
return unless self.success
order = Order.find(order_id) #you shouldn't need this line if it has_one :order
1.upto(order.total_tokens) do
Tracker.create!(
:user_id => #current_user,
:token => Tracker.create_trackable_token,
:order_id => order_id
)
end
end
Change #trackable to trackable to keep it scoped to the loop. Otherwise, the second time the loop runs, #trackable already has a value so the call to Tracker.new doesn't execute, and the #trackable.save line just keeps re-saving the same record. (Edit: Also remove the ||= and just use =).
def create_trackables
if self.success == true
#order = Order.find(order_id)
#start = 0
while #start < #order.total_tokens
#trackable_token = Tracker.create_trackable_token
#start += 1
trackable = Tracker.new(
:user_id => #current_user,
:token => #trackable_token,
:order_id => order_id
)
trackable.save
end
end
end

Validate uniqueness of value between multiple fields

I understand that validating uniqueness of a standard, single field like "username" is easy. However, for something that has an unlimited number of inputs like, for example, "Favorite Movies" where a user can add as many favorite movies, is something I can't figure out.
They can choose to add or remove fields via the builder, but how do I ensure that no two or more entries are duplicates?
I think the easiest way to accomplish something like this is to validate the uniqueness of something in a scope. I can't say for sure how it would fit in your scenario since you did not describe you model associations but here is an example of how it could work in a FavoriteMovie model:
class FavoriteMovie < ActiveRecord::Base
belongs_to :user
validates_uniqueness_of :movie_name, :scope => :user_id
end
This makes sure that there can't be two movie names that are the same for one specific user.
It turns out that when using nested attributes, you can only validate what's already in the database and not new duplicate occurrences. So, a validation extension (below) with memory validation is really the only option, unfortunately.
#user.rb
class User
has_many :favorite_movies
validate :validate_unique_movies
def validate_unique_movies
validate_uniqueness_of_in_memory(
favorite_movies, [:name, :user_id], 'Duplicate movie.')
end
end
#lib/extensions.rb
module ActiveRecord
class Base
def validate_uniqueness_of_in_memory(collection, attrs, message)
hashes = collection.inject({}) do |hash, record|
key = attrs.map {|a| record.send(a).to_s }.join
if key.blank? || record.marked_for_destruction?
key = record.object_id
end
hash[key] = record unless hash[key]
hash
end
if collection.length > hashes.length
self.errors.add_to_base(message)
end
end
end
end
A very un-rails like solution to the problem would be to add a unique key constraint on the columns that in combination are required to be unique:
create unique index names_idx on yourtable (id, name);
you could easly check it like:
params[:user][:favourite_movies].sort.uniq == params[:user][:favourite_movies].sort
or in model:
self.favourite_movies.sort.uniq == self.favourite_movies.sort
irb(main):046:0> movies = ['terminator', 'ninja turtles', 'titanic', 'terminator' ].map {|movie| movie.downcase }
=> ["terminator", "ninja turtles", "titanic", "terminator"]
irb(main):047:0> movies.sort.uniq == movies.sort
=> false
You can try to create virtual attribute and check it uniqueness:
def full_name
[first_name, last_name].joun(' ')
end
def full_name=(name)
split = name.split(' ', 2)
self.first_name = split.first
self.last_name = split.last
end
You can check uniqueness on the database level by fix your migration:
CREATE TABLE properties (
namespace CHAR(50),
name CHAR(50),
value VARCHAR(100),
);
execute <<-SQL
ALTER TABLE properties
ADD CONSTRAINT my_constraint UNIQUE (namespace, name)
SQL
Little more modern approach: validates method
validates :movie_name, :uniqueness => {:scope => : user_id}

Resources