Storing list of actions in Rails app - mysql

I need some advice. I'm making an app that is a little complicated to learn. It has a user model. Once a user signs up, I'd like a modal dialog to appear the first time (and only the first time) he/she access certain pages. For example, there's a "vote" page and a "create a poll" page, etc.
What's the best way to implement this type of help system? I thought of adding a column to my user model that can contains a default list of values like "vote,create,share." When the user accesses the page that corresponds to one of those actions, if the name of the action is still in the list, the modal appears and the name of the action is removed from the list.
Does this sound reasonable? I know that in a normalized database, you shouldn't store multiple values in a single field. But it seems crazy to create a table called "actions" and another joining table to relate users to actions. There are only 4 or 5 things the user can do on the site.
Or is there some other way to do this that I'm missing? Thanks in advance for your advice.

You should probably make a UserTrigger model that can be used to "trigger" certain actions like this. It would look something like this in practice:
<% for_trigger(#user, :first_time_help) do %>
...help content...
<% end %>
This method would have a definition approximately like:
def for_trigger(model, name)
if (model.triggers.where(:name => name.to_s).delete_all > 0)
yield
end
end
A softer version would have a should_trigger? and did_trigger! method pair, where one would test and the other would actually remove the trigger record. This single-shot one should be good enough for most cases.
You would have a definition in your User model roughly like:
class User < ActiveRecord::Base
has_many :triggers,
:class_name => 'UserTrigger'
end
class UserTrigger < ActiveRecord::Base
belongs_to :user
end
You will have to pre-populate these trigger records after_create on your User model, or any other models that require it. To re-set the trigger, just re-add the record.

Related

Only one principal(user) can be created by role attribute which is enum + rails

i am new to rails and creating a school app where i have given an attribute role to user which is deciding the role of user and i want to create only one user can be principal(role), how do i achieve that there can not be create more than one principal user in app,
i am using devise for authentication...
i tried this =>
validates :role, :uniqueness => { :role => "principal" }
but it is restricting me from creating any kind of user , in my user enum i have given 4 roles [admin,principal,teacher,student]
any help or advice will be appreciated.... thanks in advance
I would use a custom validation like this:
validate :one_principal_exists
private
def one_principal_exists
return unless principal?
return unless User.where.not(id: id).exists?(role: 'principal')
error.add(:role, 'already exists')
end
The idea is to check first if the current user is a principal, if not skip the validation. And then check if there is already another user (who has a different id than the current user) in the database who has the principal role. If such a user exists, then add a validation error to the role attribute.
Please note that you might need to add more conditions to the database query when you, for example, want to support multiple schools and each school has a different principal.
in model use below code
user.rb
before_create :check_principle
def check_principle
if User.find_by(role: "principle")
errors.add(:code, "principle already exist")
return false
end
end

rails includes query loading entire data for included models

I have a rails application where I have following models.
BookingHotel
has_many :hotel_picture_partners, through: :hotel_pictures
has_many :booking_hotel_partner_details, dependent: :destroy
BookingHotelPartnerDetail
belongs_to :booking_hotel
HotelPicturePartner
belongs_to :hotel_picture, dependent: :destroy
I have a query as follows
#booking_hotel_partner_details = BookingHotelPartnerDetail.unscoped.select(:id, :booking_hotel_id, :partner_booking_hotel_id).includes(booking_hotel: :hotel_picture_partners)
This puts memory under pressure as it loads all data for included models.
Is there a way I can only load selective fields from booking_hotels & hotel_picture_partners tables ?
Also I want to get an activerecord array as response.
pluck method loads only attributes, without loading whole models. check it: http://apidock.com/rails/ActiveRecord/Calculations/pluck.
Try to rewrite it like this:
BookingHotelPartnerDetail.unscoped.select('booking_hotels.id as bh_id', 'hotel_picture_partners.id as hpp_id').joins(booking_hotel: :hotel_picture_partners)
Your project is probably already pretty far along, but for anyone else wanting this behaviour I've released a patch that filters columns if you have a .select(...) along with .includes(...) or .eager_load(...).
It's now included in a data-related gem I maintain, The Brick.
It works by overriding ActiveRecord::Associations::JoinDependency.apply_column_aliases() like this.
In order to enable this selective behaviour, add the special column name :_brick_eager_load as the first entry in your .select(...), which turns on the filtering of columns while the aliases are being built out. Here's an example based on your code:
#booking_hotel_partner_details =
BookingHotelPartnerDetail.unscoped
.includes(booking_hotel: :hotel_picture_partners)
.select(:_brick_eager_load, :id, :partner_booking_hotel_id, 'booking_hotel.name', 'hotel_picture_partners.notes')
Feel free to add myraid additional columns from any table as strings in the same way as the last two sample items I put into the .select(...).
Because foreign keys are essential to have everything be properly associated, they are automatically added, so you do not need to include :booking_hotel_id in your select list.
Hope it can save you both query time and some RAM!

Add column in database from controller

I want to add a column in a mysql table from a controller. The user completes a form, and when he sends it, it creates a new column (not row) with the information in the form. How can I do this?
it create new column
Don't.
Your database is sacrosanct, dynamically altering it is like dynamically changing a car based on some user's request. A car has four wheels, engine and seats. You can change the colour, tyres, etc... but not the fundamentals.
It's the same with web apps - you should not be changing the fundamental structure of your system. Sure, you'll be able to change various aspects of it (User Avatar etc), but the underlying basis of the system (the db schema) should be kept above any changes.
What you should be doing is maintaining your database fidelity through a tight set of Models, allowing you to create a dynamic experience around the data you've been provided.
For example...
The user complete a form and when he send it, it create new column
A better way to explain this will be to use a user story.
I'll surmise the following in your case:
A user wants to add a new project to his portfolio. He fills out the form to explain what the project will be and adds a number of extra fields specific for that project.
I think you're asking about the "extra fields" part...
You have to remember Rails is built on top of a relational database:
This means that you have the flexibility provided by your models to grant your users the capacity to add and manipulate as many pieces of associated data as they need.
The data they add to the system can have any name & any structure, so long as you provide that functionality within the system itself...
#app/models/user.rb
class User < ActiveRecord::Base
has_many :projects
has_many :specialized_fields, through: :projects
end
#app/models/project.rb
class Project < ActiveRecord::Base
belongs_to :user
belongs_to :specialized_field
accepts_nested_attributes_for :specialized_field
end
#app/models/specialized_field.rb
class SpecializedField < ActiveRecord::Base
has_many :projects
has_many :users, through: :projects
end
According to my example above,
User can make a Project
Project can have specialized fields (above the standard fields)
User can add specialized fields to the model
Thus you can do the following:
#app/controllers/projects_controller.rb
class ProjectsController < ApplicationController
def new
#project = current_user.projects.new #-> assuming you're using Devise
#specialized_field = #project.build_specialized_field
end
def create
#project = Project.save project_params
#project.save
end
private
def project_params
params.require(:project).permit(:name, :start_time, :end_time, specialized_field_attributes: [:name, :value])
end
end
The form could be as follows:
#app/views/projects/new.html.erb
<%= form_for #project do |f| %>
<%= f.text_field :name %>
<%= f.fields_for :specialized_field do |s| %>
<%= s.text_field :name %>
<%= s.text_field :value %>
<% end %>
<%= f.submit %>
<% end %>
Why would you like to add new columns to the database via the controller? This shouldn't be the case - and in general I couldnt' think of a single reason why this should ever be required. It sounds unconventional and against design principles.
If you add more information of what is required and what you are tyring to do I am pretty sure we can work out an alternative solution. (I bet what you are trying to do can be achieved with a many-to-many relationship, or similar, somehow). Post more information and see what we can do.
But as answer and solution I'd say it shouldn't be required.
Why do you want to do this? Database design is part of the application development, so you are the one deciding the rows. When the user inputs data, there are a gazillion things that can go wrong (hacks, invalid values...), which can affect your entire database. It's not worth risking that.
If you want a flexible schema, you can store hashes in a specific field.
For example you have a extra_data field which is a hash. Then in your form you can have to inputs, input_name and input_value which will go to the hash. This way you will have more flexible values for the same column and you don't need to change your database schema.

How to delete a record only if it exists in Rails?

I want to delete the tokens I created for a post, when I am deleting the post itself. But if the token does not exist, I don't want Rails to throw an error and stop executing.
Right now, this is how I go about it. I think this code is way too chunky. Is there any neat way to accomplish the same?
DownloadToken.find_by_post_id(post.id).destroy unless DownloadToken.find_by_post_id(#post.id).nil?
This is one way(old syntax)
DownloadToken.find_by_post_id(post.id).try(:destroy)
Newer syntax in rails:
DownloadToken.find_by(id: post.id).try(:destroy)
With Ruby's safe navigation operator:
DownloadToken.find_by(id: post.id)&.destroy
Look at your Post model; post.rb
Ie.
Class Post < ActiveRecord::Base
has_many :download_tokens, dependent: :destroy
end
Now when you delete a Post object, whatever the association; has_many, has_one, it will find the destroy the dependent also. In this case the DownloadToken(s)
DownloadToken.find_by_post_id(post.id)&.destroy
Executes destroy only if the query result is not nil. It's the abbreviated version of:
token = DownloadToken.find_by_post_id(post.id)
token.destroy if token
If you are certain you 'll handle the post deletion with its destroy method, than you can follow Jay's answer and it will work just fine.
If you will use the delete method on post you need some extra functionality to handle the download_tokens.
class DownloadToken < ActiveRecord::Base
def self.remove_post_tokens(the_post_id)
where(post_id: the_post_id).destroy_all
end
end
so your sequence will be:
id = post.id
post.delete #or post.destroy
DownloadToken.remove_post_tokens(id)
That scenario is not purely academic, because the dependent destroy action can be really expensive, eg if you have too many download_tokens, so you would not want it to be in the same transaction as post's destruction.

Save model id as foreign key through has_one / belongs_to

I'll briefly explain my situation: I have a model called "tax" which belongs_to a "user" and of which a "user" has_one.
In my users table I have a column called "tax_id" which I want to store the id of the tax model when a user creates one.
Currently, in my tax model the create function looks something like this:
class Tax < ActiveRecord:Base
belongs_to :user
tax = Tax.new(income: income, taxes: taxes, rrsp: rrsp)
tax.save
and then in the taxes_controller file, the create function looks like this:
def create
#tax = Tax.new(secure_params)
#tax.user = User.find(current_user.id)
if #tax.save
redirect_to show_tax_path(current_user.tax)
else
render :new
end
end
(secure_params) being the strong parameters for the field inputs set in a private definition.
Now, someone mentioned that I may have better luck if I use build but unfortunately I couldn't get it to work at all, something to do with how I'm using current_user (devise). Currently, my setup works just fine, other than saving the tax model id in the user model column "tax_id" like I said.
I'm wondering if I perhaps need to add a foreign ID key to either the belongs_to or has_one statement, even though I was under the impression that the "link" should be made automatically as long as the column was named "[model]_id"
try using
user.build_tax
I think this might help you out.
The build syntax for has_many association:
user.taxes.build
The build syntax for has_one association:
user.build_tax # this will work
user.tax.build # this will throw error