Refactoring pseudo-enum models to enums - sql

I have a model named article_status, which does nothing more than providing statuses to the articles. I want to drop this article_status table and use enum within the article model directly.
So, I've created a new migration but my problem is how to write SQL to update the columns.
class AddStatusToArticles < ActiveRecord::Migration[6.1]
def change
add_column :articles, :status, :integer
add_index :articles, :status
execute <<~SQL
# Write SQL here
SQL
change_column :articles, :status, :integer, null: false
end
end
For the SQL part, I want the equivalent of:
Article.all.each do |article|
article.update_columns(status: article.article_status.name.parameterize.underscore)
end
In my article model:
enum status: { draft: 0, in_review: 1, reviewed: 2, published: 3, deleted: 4 }, _default: :draft
I added the enum like this.
PS: I'm using Postgres as my database.

I would do this:
statuses = ArticleStatus
.pluck(:id, :name)
.map { |(id, name)| [id, name..parameterize.underscore] }
.to_h
Article.find_each do |article|
article.update_columns(status: statuses[article. article_status_id])
end

Related

Group By multiple column for sql count statement , in rails environment

I have two Relations (class)
class RecommendedForType < ActiveRecord::Base
attr_accessible :description, :name
has_many :recommended_for_type_restaurants
end
And
class RecommendedForTypeRestaurant < ActiveRecord::Base
attr_accessible :recommended_for_type_id, :restaurant_id, :user_id
belongs_to :restaurant
belongs_to :user
belongs_to :recommended_for_type
def self.get_count(rest_id)
r = RecommendedForTypeRestaurant.where(restaurant_id: rest_id)
#result = r.includes(:recommended_for_type)
.select("recommended_for_type_id, recommended_for_types.name")
.group ("recommended_tor_type_id, recommended_for_types.name")
.count
end
end
if I call
RecommendedForTypeRestaurant.get_count(1) # get stat of restaurant_id: 1
I get
{"dinner"=>1, "fast food"=>1, "lunch"=>3, "romantic dinner"=>1}
My goal is to get both id and name of RecommendTypeFor in the return result as well. currently i can only make it return either id or name. Something like this
{{"id" => 1, "name" => "dinner", "count" => 1}, {"id" =>2, "name" => "lunch", "count" => 3} }
For sure i can do another round of sql once i get id or name to create that hash but i think that not the efficient way. Appreciate any help or suggestion :)
You need to group separately, try the following group.
.group ("recommended_tor_type_id", "recommended_for_types.name").count
I believe that if you remove the .count at the end and change the select to:
.select("count(*), recommended_for_type_id, recommended_for_types.name")
You will get an array of models that will have the attributes you need and the count.
You should be able to test it out in the console by do something like this:
r = RecommendedForTypeRestaurant.where(restaurant_id: rest_id)
#result = r.includes(:recommended_for_type)
.select("recommended_for_type_id, recommended_for_types.name, count(*)")
.group ("recommended_tor_type_id, recommended_for_types.name")
#result.each do |r|
puts r.recommended_for_type_id
puts r.name
puts r.count
end
Hope it helps!

Rails form to edit JSON object as text

I'd like to make a form that lets a user edit one field of a mongoid object as rendered JSON text. There's a field in the model that my rails app should not understand, but I want to expose a generic editor. So for this field, I'd like to render it as pretty JSON, and expose it in a big <textarea> and then parse the JSON back in after any edits.
I can think of a dozen ways to do this, but I'm wonder what would be most consistent with Rails philosophy and least divergent from normal scaffolding. Should I render the object to JSON text in the controller? Then I'd have to repeat that code in the new and edit methods, and the parsing code in the update and create methods, which seems a bit kludgy. Is there a way to define a helper or custom form widget that goes in the _form.html.erb that is more reusable? Or maybe one already written?
You can make your own attribute writer/reader, in the model:
attr_accessible the_field_raw
def the_field_raw
self.the_field.to_s
end
def the_field_raw=(value)
self.the_field = JSON(value)
end
whitch should be compatible with form generators and no extra code in the controllers.
Hope it helps!
Serialize the values as JSON.
class Price < ActiveRecord::Base
serialize :values, JSON
validates :start, :end, :values, :presence => true
end
migration:
class CreateMyModels < ActiveRecord::Migration[7.0]
def change
create_table :my_models do |t|
t.jsonb :name, default: {}, null: false
t.jsonb :description, default: {}, null: false
t.integer :another_param
t.timestamps
end
end
end
model and concern:
class MyModel < ApplicationRecord
AVAILABLE_LOCALES = I18n.available_locales
include JsonLocalize
json_localize :name, :description
end
module JsonLocalize
extend ActiveSupport::Concern
included do
def self.json_localize(*attrs)
self::AVAILABLE_LOCALES.each do |locale|
attrs.each do |attr|
define_method("#{attr}_#{locale}") do
send(attr)[locale.to_s]
end
define_method("#{attr}_#{locale}=") do |value|
send(attr)[locale.to_s] = value
end
end
end
end
end
end
then you can have in your form:
.row
.col-md-6
- MyModel::AVAILABLE_LOCALES.each do |loc|
= f.input "name_#{loc}"
= f.input "description_#{loc}"
controller params:
def resource_params
params.require(:my_model).permit(
[
:another_param
] | [:name, :description].map {|attr| MyModel::AVAILABLE_LOCALES.map { |loc| "#{attr}_#{loc}".to_sym } }.flatten
)
end

Rails: changing from three columns in a table to one

I am modifying a Documents table from using three columns (article1, article2, and article3) to one (articles) which has a string of comma-separated IDs stored in it (i.e., 23,4,33,2). That's all working well, but I'm trying to adjust the functions that read the three columns to read the one and I'm getting rather stuck.
In the model I have:
scope :all_articles, lambda {|p| where(:page => p) }
In the controller I have this:
#articles = (1..3).to_a.map { |i| Article.all_articles(i).reverse }
And in the view:
<% #articles.each_with_index do |a, i| %>
<%= a[i].name %>
<% end %>
It's just a bit beyond me at this point.
Cheers!
It's usually not good practice to put store the ids in a column like you have done. It is better to break that relationship out into a Has and Belongs to Many relationship. You set it up in your models like this:
class Document < ActiveRecord::Base
#...
has_and_belongs_to_many :articles
#...
end
class Article < ActiveRecord::Base
#...
has_and_belongs_to_many :documents
end
Then you will create a join table that ActiveRecord will use to store the relationships.
create_table :articles_documents, :id => false do |t|
t.integer :article_id
t.integer :document_id
end
add_index :articles_documents, [:article_id, :document_id], unique: true
This will allow you to query a lot more efficiently than you are currently doing. For example, to find all documents that have some article id. You would do:
#documents = Document.joins(:articles).where("articles.id = ?", some_article_id)
Or if you want to query for a document and return the articles with it:
#documents = Document.includes(:articles).where(some_conditions)

Elasticsearch: filtering by group with tire gem

I want to filtering search by age groups.
Now I have:
program.rb
class Program < ActiveRecord::Base
has_many :age_groups, through: :programs_age_groups_relations
include Tire::Model::Search
include Tire::Model::Callbacks
mapping do
indexes :name, analyzer: 'snowball', boost: 100
indexes :description, analyzer: 'snowball'
indexes :age_groups do
indexes :name, analyzer: 'snowball'
end
end
def to_indexed_json
to_json(include: {age_groups: {only: :name}})
end
def self.search(params, options={})
tire.search(load: {include: 'age_groups'}) do
query do
boolean do
must { string params[:name_query] } if params[:name_query].present?
end
end
filter do
boolean do
if params[:age_groups].present?
params[:age_groups].each do |ag_name|
must { string ag_name }
end
end
end
end
end
end
index.html.haml
= form_tag programs_path, method: :get do |f|
= text_field_tag :name_query, params[:name_query]
- AgeGroup.all.each do |ag|
= check_box_tag 'age_groups[]', ag.name, params[:age_groups].include?(ag.name)
in controller:
#programs = Program.search(params)
Age Groups:
AgeGroup.create([{name: 'Baby'}, {name: 'Toddler'}, {name: 'Preschoolers'}, {name: 'Elementary'}, {name: 'Middle School'}])
Realtion:
class ProgramsAgeGroupsRelation < ActiveRecord::Base
attr_accessible :age_group_id, :program_id
belongs_to :age_group
belongs_to :program
end
When I check one or more age_groups in search form nothing was happened with search result.
How can I correctly use tire for this task?
I'm not sure I understand your question, but please check this StackOverflow answer: Elasticsearch, Tire, and Nested queries / associations with ActiveRecord for info on ActiveRecord associations with Tire.
Once you have that sorted out, please update your question, as “nothing was happened with search result” hints at some kind of failed expectation, but I don't know which one.

How do I insert a record in Rails when there's no model and without using SQL

I have created a table that implements an n-to-n relation using the following statement:
create_table :capabilities_roles, :id => false do |t|
t.integer :capability_id, :null => false
t.integer :role_id, :null => false
end
There is no model for this table. How do I insert records without resorting to SQL?
I found this in the ActiveRecord::ConnectionAdapters::DatabaseStatements module:
insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
and also:
insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
I have no idea what arel means. Can someone give me an example of a valid insert? I would like to use stuff like :role_id => Role.find_by_name('Business user') in it.
If you're going to be manipulating the database records via Rails, then there should be a model for it. Just create a role.rb in your models directory with the lines
class Role < ActiveRecord::Base
end
And you're as good as gold.
It looks like a join table for has and belongs to many relationship between Capability and Role models. You should let the Rails handle it for you. First define required associations:
class Capability < ActiveRecord::Base
has_and_belongs_to_many :roles
end
class Role < ActiveRecord::Base
has_and_belongs_to_many :capabilities
end
Then just add instance of Role model to roles array of an instance of Capability model (or vice versa):
capability.roles << role
role.capabilities << capability
Removing records from join table is done via removing object from an array:
capability.roles -= [role]
In our project we have many meta tables which don't have models. To generate active record models on the fly we use follow module:
module EntityModel
module_function
ACCESS_MODELS = {}
def for(table_name)
return ACCESS_MODELS[table_name] if ACCESS_MODELS.has_key?(table_name.id)
ACCESS_MODELS[table_name] = create_access_model(table_name)
end
def create_access_model(table_name)
Class.new(ActiveRecord::Base) do
self.table_name = table_name
end
end
end
This create anonymous models and store it in the global hash for performance purposes.
Uses as:
EntityModel.for(:users)