Rails ActiveRecord Database with just one row - mysql

Is there a way for a database to only allow one row (e.g. for site-wide settings) ?

class Whatever < ActiveRecord::Base
validate :there_can_only_be_one
private
def there_can_only_be_one
errors.add_to_base('There can only be one') if Whatever.count > 0
end
end

In Rails 4:
class Anything < ActiveRecord::Base
before_create :only_one_row
private
def only_one_row
false if Anything.count > 0
end
end
Silent errors are bad, then
class Anything < ActiveRecord::Base
before_create :only_one_row
private
def only_one_row
raise "You can create only one row of this table" if Anything.count > 0
end
end

Is there just one column in this row? If not, adding new columns with a migration may be overkill. You could at least make this table contain 'name' and 'value' columns and validate by uniqueness on name.

Related

Auto-create `belongs_to` record

Similar questions have been asked before but I just can't figure this out.
So I have Model_A and Model_B. Model_B belongs_to Model_A. What I want to do is when I create Model_A is to automatically call the create method for Model B. Then a script takes over a generates a bunch of data for Model_B. I use after_create because this only has to happen once.
It needs done this way. If you wanna know the details feel free to ask...
So I got Model_A here. I just can't seem to get the right syntax in create_model_b. In the example I used I just get an error saying the method doesn't exist for Model_A.
class Model_A < ActiveRecord::Base
belongs_to :Model_B
after_create :create_model_b
...
def create_model_b
#so I tried a bunch of stuff here but none of it worked
#I need to create a Model_B which will contain the current Model_A id
#ex. self.model_b.create(model_a_id: self.id)
end
end
Model_B doesn't do anything special really:
class Model_B < ApplicationController
def create
#model_b = Model_B.new(model_b_params)
create_the_data
respond_to do |format|
if #model_b.save
#redirect
else
#uh oh
end
end
end
end
Thanks!
Two ways:
1.
class Model_A < ApplicationController
def create
#model_a = ModelA.new(model_a_params)
if #model_a.save
ModelB.create(model_a_id: #model_a.id, .....)
#create data for model B either here or with after_create (of model B)
redirect_to somewhere_awesome_path
else
# rescue error
render 'new'
end
end
2.
class Model_A < ActiveRecord::Base
after_create :create_model_b
...
def create_model_b
ModelB.create(model_a_id: id)
end
end
Your Model A contains half of an association: belongs_to :Model_B
Your Model B is missing its association to Model A.
Depending on the relationship you set up, you can complete the association with a has_one :Model_A, or has_many :Model_A, for example.
Here's Active Record documentation for reference.

External database association in Rails . Update data

I have the following problem:
I have two models , that are connected to an external database (Mysql)
The right name of the two tables in the external database are:
f_aziende and f_partecipanti (it's italian).
In my Rails app i created two models to connect to these tables and i called:
formation_db and reference_db.
Here my models code:
class ReferenceDb < ActiveRecord::Base
establish_connection "#{Rails.env}_db2"
self.table_name = "f_partecipanti"
self.primary_key = 'id'
belongs_to :formation_db
end
class FormationDb < ActiveRecord::Base
establish_connection "#{Rails.env}_db2"
self.table_name = "f_aziende"
self.primary_key = 'id'
has_many :reference_dbs , :foreign_key => "id_azienda"
end
The problem is that i'm not able to update all the rows associated to formation_db. Also , i'm not sure if the association of the two models is correct. I mean , in this case what is the convenction of the rails words?
In my others project i usually write:
#company.update_attributes(params[:company])
and i update all the things related to company in one line of code.
How can i create something similar to this in my case?
You're walking perilously close to the line of "multi tenancy", which is why you're running into difficulty I think.
Anyway, I found there's a way to connect to other DBs without having to redclare it in each model:
#app/vendor/db.rb
class Db < ActiveRecord::Base
establish_connection "#{Rails.env}_db2"
end
#app/models/reference_db.rb
class ReferenceDb < Db
self.table_name = "f_partecipanti"
belongs_to :formation_db
end
#app/models/formation_db.rb
class FormationDb < Db
self.table_name = "f_aziende"
has_many :reference_dbs , :foreign_key => "id_azienda"
end
This will at least set up your models succinctly.
--
These models will work like any other one in Rails. The only caveat is that you cannot join across different databases; IE you cannot have a has_many :through with the databases aforementioned.
#company = Company.find params[:id]
#company.update_all ....

Rails 4: ActiveRecord or MySQL query where no related models have attribute

Having a tough time with this one. I have a Job model, and a JobStatus model. A job has many statuses, each with different names (slugs in this case). I need an 'active' method I can call to find all jobs where none of the associated statuses has a slug of 'dropped-off'.
class Job < ActiveRecord::Base
belongs_to :agent
has_many :statuses, :class_name => "JobStatus"
validates :agent_id,
:pickup_lat,
:pickup_lng,
:dropoff_lat,
:dropoff_lng,
:description,
presence: true
class << self
def by_agent agent_id
where(agent_id: agent_id)
end
def active
#
# this should select all items where no related job status
# has the slug 'dropped-off'
#
end
end
end
Job Status:
class JobStatus < ActiveRecord::Base
belongs_to :job
validates :job_id,
:slug,
presence: true
end
The closest I've gotten so far is:
def active
joins(:statuses).where.not('job_statuses.slug = ?', 'dropped-off')
end
But it's still selecting the Job that has a dropped-off status because there are previous statuses that are not 'dropped-off'. If i knew the raw sql, I could probably work it into activerecord speak but I can't quite wrap my head around it.
Also not married to using activerecord, if the solution is raw SQL that's fine too.
Job.where.not(id: JobStatus.where(slug: 'dropped-off').select(:job_id))
will generate a nested subquery for you.
Not the cleanest method, but you could use two queries.
# Getting the ID of all the Jobs which have 'dropped-off' JobStatuses
dropped_off_ids = JobStatus.where(slug: 'dropped-off').pluck(:job_id)
# Using the previous array to filter the Jobs
Job.where.not(id: dropped_off_ids)
Try this:
def active
Job.joins(:statuses).where.not('job_statuses.slug' => 'dropped-off')
end
or this:
def active
Job.joins(:statuses).where('job_statuses.slug != ?', 'dropped-off')
end
I think you may want to reevaluate your data model somewhat. If the problem is that you're turning up old statuses when asking about Job, you likely need to have column identifying the current status for any job, i.e. job.statuses.where(current_status: true)
Then you can very easily grab only the rows which represent the current status for all jobs and are not "dropped-off".
Alternatively, if I'm misunderstanding your use case and you're just looking for any job that has ever had that status, you can just go backwards and search for the status slugs first, i.e.
JobStatus.where.not(slug: "dropped-off").map(&:job)

Rails find method didn't use my foreign key

I have the following two Models:
class TopicContent < ActiveRecord::Base
unloadable
belongs_to :topic
end
and
class Topic < ActiveRecord::Base
unloadable
has_one :topic_content
accepts_nested_attributes_for :topic_content
end
And the following show action, which get the :id from the selected topic:
def show
#text = TopicContent.find(params[:id])
end
The problem is, that the find method always take the primary-key(id) instead of foreign-key (topic_id) from the TopicContent table.
Is there something wrong with my defined associations?
.find(primary_key) always retrieves the records from database based on primary key.
Use .find_by(conditions) instead as it finds the first record matching conditions passed to it.
For eg:
#text = TopicContent.find_by(topic_id: params[:id])
You need to find the TopicContent via the Topic.
def show
#topic = Topic.find(:topic_id)
#text = #topic.topic_content
end
This is assuming you have your routes set up as well.

Paginating a shuffled ActiveRecord query

I am attempting to paginate a shuffled ActiveRecord query. The syntax for doing this using the Kaminari gem is:
#users = Kaminari.paginate_array(User.all.shuffle).page(params[:page]).per(20)
The issue with this is that User.all is re-shuffled on each pagination request, causing duplicate records to be called. Is there any way to prevent this kind of duplication?
You need to pass seed for rand between queries
params[:seed] ||= Random.new_seed
srand params[:seed].to_i
#users = Kaminari.paginate_array(User.all.shuffle).page(params[:page]).per(20)
And in view add params[:seed] to all kaminari links to pages
As KandadaBoggu points out above, retrieving all of the User records from the database is inefficient when you only need 20. I would suggest using MySQL's RAND() function to perform the randomization before you return from the database. You can still pass a seed value to RAND() to make sure the shuffling only happens once per session.
For example:
class User < ActiveRecord::Base
def self.randomized(seed = nil)
seed = seed.to_i rescue 0
order("RAND(#{seed})")
end
end
class UsersController < ApplicationController
before_filter :set_random_seed
def index
#users = User.randomized(session[:seed]).page(params[:page]).per(20)
end
private
def set_random_seed
session[:seed] ||= Random.new_seed
end
end
I don't have a MySQL installation to test against, but this should perform better than your original code.
You can also do this:
class UsersController < ApplicationController
USERS_SEED = 1000 # Or any another not-so-big number
def set_random_seed
session[:seed] ||= Random.rand(USERS_SEED)
end
end
Because Random.new_seed will generate most likely the same result if your data isn't that big.