How can I save a unique string by incrementing it in rails? - mysql

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'))

Related

ActiveRecord::AssociationTypeMismatch Rails CSV Import

I am using gem roo to import CSV data. It works smoothly, until the point where there is an association, and am hoping that roo can translate the string into the corresponding integer value in the association. In my case, I have a Staff model which belongs to State.
class State < ApplicationRecord
has_many :staffs
end
class Staff < ApplicationRecord
belongs_to :state
end
This means that I have state_id column in the staffs table. In my CSV, however, the end user has the names of the states, which correspond to the ones in the states tables. When I try to import the CSV, I get the error:
ActiveRecord::AssociationTypeMismatch in StaffsImportsController#create
State(#134576500) expected, got "Texas" which is an instance of String(#20512180)
The highlighted source is:
staff.attributes = row.to_hash
Is it possible for gem roo to translate 'Texas' in the csv file to, say, id 2, instead of the end user doing a lot of translation work before uploading the data?
Here is staffs_imports.rb
class StaffsImport
include ActiveModel::Model
require 'roo'
attr_accessor :file
def initialize(attributes={})
attributes.each { |name, value| send("#{name}=", value) }
end
def persisted?
false
end
def open_spreadsheet
case File.extname(file.original_filename)
when ".csv" then Csv.new(file.path, nil, :ignore)
when ".xls" then Roo::Excel.new(file.path, nil, :ignore)
when ".xlsx" then Roo::Excelx.new(file.path)
else raise "Unknown file type: #{file.original_filename}"
end
end
def load_imported_staffs
spreadsheet = open_spreadsheet
header = spreadsheet.row(1)
(2..spreadsheet.last_row).map do |i|
row = Hash[[header, spreadsheet.row(i)].transpose]
staff = Staff.find_by_national_id(row["national_id"]) || Staff.new
staff.attributes = row.to_hash
staff
end
end
def imported_staffs
#imported_staffs ||= load_imported_staffs
end
def save
if imported_staffs.map(&:valid?).all?
imported_staffs.each(&:save!)
true
else
imported_staffs.each_with_index do |staff, index|
staff.errors.full_messages.each do |msg|
errors.add :base, "Row #{index + 6}: #{msg}"
end
end
false
end
end
end
And finally the staff_imports_controller.rb:
class StaffsImportsController < ApplicationController
def new
#staffs_import = StaffsImport.new
end
def create
#staffs_import = StaffsImport.new(params[:staffs_import])
if #staffs_import.save
flash[:success] = "You have successfully uploaded your staff!"
redirect_to staffs_path
else
render :new
end
end
end
Any help/clues will be highly appreciated.
I managed to get a solution to this, thanks to a wonderfully detailed question and great answer provided here Importing CSV data into Rails app, using something other then the association "id"

Include parent attributes when searching via query

I have a User Model that has_many Job Applications.
Everything works great, but when I try to search Job Applications by the User's first_name I get the error below.
Error
SQLite3::SQLException: no such column: first_name: SELECT "job_applications"
From my understanding, I need to include the User attributes in the Job Application query.
How can I accomplish this?
View (job application)
<%= form_for :search, :html => {:method => :get, :id => 'search'} do |f| %>
<%= text_field_tag :terms, params[:terms], :type => 'search' %>
<% end %>
Controller (job application)
def index
#job = Job.find(params[:job_id])
#job_applications = #job.job_applications.search(params[:terms])
end
Model (job application)
def self.search(terms)
terms ||= ""
conditions = terms.split(" ").map do |term|
term = term.strip.gsub("'","''")
### I am having an issue here...
### how do i include the user attributes in this query
"first_name like '%#{term}%'"
end
where(conditions.join " OR ")
end
You have to join job_applications table with users table.
# job_applications.rb
def self.search(terms)
terms ||= ""
conditions = terms.split(" ").map do |term|
term = term.strip.gsub("'","''")
"users.first_name like :term"
end
joins(:user).where(conditions.join " OR ")
end
Avoid passing raw user's inputs into your queries directly to avoid sql injection. Use rails' built-in filters or sanitize it yourself.
def self.search(terms)
terms ||= ""
term_args = []
conditions = terms.split(" ").map do |term|
term = term.strip.gsub("'","''")
term_args << "%#{term}%"
"users.first_name like ?"
end
joins(:user).where(conditions.join(' OR '), term_args)
end

Trouble defining finder method in my application

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)

Ruby on Rails joins models for json output

I'm trying to build a JSON API end point with Ruby on Rails.
I followed the instruction in the following and was able to create JSON API for my models
http://railscasts.com/episodes/350-rest-api-versioning?view=comments/
I have the following controller:
/api/v1/movies_controller.rb
class MoviesController < ApplicationController
def index
if params[:p].nil?
p = 1
else
p = params[:p].to_i
end
#movies = Movie.order("id DESC").page(p)
end
def show
#movie = Movie.find(params[:id])
end
end
I need to join this with the Genre object where Movie has_many :genres, and Genre belongs_to :movie
However, I'm not able to use the following to get the genres joined with the movie object for the JSON output:
#movie = Movie.find(params[:id], :joins => :genres)
I did notice that the following command is able to generate the joined output in ruby console
#movie.to_json(:include=>:genres)
But then adding this in the controller doesn't show the additional genres fields
Can someone help me please?
Thanks!
My advise: Go with active_model_serializers
Add the following line to your Gemfile
gem 'active_model_serializers'
Do a bundle install afterwards.
After that do a
rails g serializer movie
rails g serializer genre
Than customize the following code:
/api/v1/movies_controller.rb
class MoviesController < ApplicationController
respond_to :json
def index
if params[:p].nil?
p = 1
else
p = params[:p].to_i
end
#movies = Movie.order("id DESC").page(p)
respond_with #movies
end
def show
#movie = Movie.find(params[:id])
respond_with #movie
end
end
app/serializers/movie_serializer.rb
class MovieSerializer < ActiveModel::Serializer
embed :ids, :include => true
attributes :id, :name
has_many :genres
end
app/serializers/genre_serializer.rb
class GenreSerializer < ActiveModel::Serializer
embed :ids, :include => true
attributes :id, :name
end
Have a look at the full documentation of active_model_serializers at
https://github.com/rails-api/active_model_serializers

MySQL with rails

I have to pass all the data saved in "first" table to "first_archives" table with the help of queries only. I used the query in the controller of "first" under 'destroy' such that it is selecting the id from "first" and inserting into archive table.
The code as follows:
class FirstsController < ApplicationController
def destroy
#first = First.find(params[:id])
if #first.destroy
cd = First_archive.new :first_id =>"First.find :all, :conditions => { :id => #id }"
cd.save
respond_to do |format|
format.html { redirect_to(firsts_url) }
format.xml { head :ok }
end
end
Please help for the doubts for two things:
* Is the syntax used is correct.
* What we have to write in first_archive table.
Thank you
I have solved it by:
x = X.find(params[:id])
archive = XArchive.new
archive.x_id = X.id
archive.x_name = x.x_name
archive.start_date = x.start_date
archive.end_date = x.end_date
archive.created_at = x.created_at
archive.updated_at = x.updated_at
archive.save
x.destroy
take care