I'm trying to add a validation to my User model.
I want to prevent people which mail is present in a "Bot" table to register.
The Bot table structure is:
create_table "bots", force: :cascade do |t|
t.string "banned_domains"
t.datetime "created_at"
t.datetime "updated_at"
end
I'm using Devise for registration, and I want to add a validation that check if "email domain is present in the bot table" the record is not valid.
The method to validate is this:
def is_a_bot?(user_email)
Bot.where("banned_domains LIKE (?)", "%#{user_email}%").present?
end
How can I add a validation using this method of the "email" attribute of my user model?
Add this validation to your User class
#in User
before_create :check_not_a_bot
def check_not_a_bot
if Bot.find_by_banned_domains(self.email.split("#").last)
self.errors.add(:email, "domain exists in list of banned domains")
end
end
Define method in the Bot model like that
self.is_a_bot?(user_email)
Bot.where("banned_domains LIKE (?)", "%#{user_email}%").present?
end
then in the User model you can use validates or before_create method (for example)
before_create :check_bot_email
def check_bot_email
if Bot.is_a_bot?(self.email)
errors.add(:base, "The user is a bot")
end
end
Related
Hi a Real Rails Rookie here. I am trying to write a basic customer mgmt system and when I create a new customer (customer table) I need it to also create 10 sub-records in another table (customer_ownership) with certain predetermined information which will then be updated/modified when we speak to the customer.
I am really struggling with this, do I try and call the sub_record create controller from the create customer controller or do I write a new controller action in the Customer Controller.
Thanks in advance
I think what you want to do is use an active record callback to perform the work you need done thst is associated with data creation.
Or use a service object design pattern to Perform all actions.
Or you can just add the code for the task to be done after create as a method and call the method directly instead of calling it with a callback.
Or this functionality could live on the model. All oth these options could be considered the “Rails Way” depending on who you talk to.
My preferred method would be...
In controllers/my_object_contoller.rb
def create
#object = MyObject.create(my_object_params)
my_private_method
end
private
def my_private_method
# create auxiliary data objects here
end
Also look into ActiveRecord associations
Because there are ways to create two data models that are programmatically linked or associated with one another using foreign_key ids on the DB columns.
Rails offers an excellent api for you to use which I’ve linked to the rails guide for above.
Such an implementation using active record associations might look like this...
# 'app/models/user.rb
class User < ActiveRecord::Base
has_one :address
...
end
# 'app/models/address.rb'
class Address < ActiveRecord::Base
belongs_to :user
...
end
# 'db/migrate/<timestamp>_create_users.rb'
class CreateUsers < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
t.string :email
t.string :first_name
t.string :last_name
t.timestamps
end
end
end
# 'db/migrate/<timestamp>_create_addresses.rb'
class CreateAddresses < ActiveRecord::Migration[5.2]
def change
create_table :addresses do |t|
t.references :user, null: false, foreign_key: true
t.string :house_number, null: false
t.string :streen_name
t.string :city
t.string :state
t.string :zip_code
t.timestamps
end
end
end
This give us a new way to manipulate our data. If we create a new user and the extra data we want to add is the users address. Then we can just collect all this data in a form and then user the methods that including the has_one helper gives us. For example...
def create
#user = User.new(params[:user])
#address = Address.new(params[:address])
#user.address = #address
#user.save
end
Of course this is all pseudo code so you should really dive into to active record association link I placed above
My Goal:
I'm trying to create two different types of users, with different profiles.
Barber, Client, BarberProfile, and ClientProfile
I have my base User object that contains information like email, password, and all the other fields that Devise keeps track of.
I'd like the base User model to have one Profile that keeps track of all basic information that I want all my users to have. For instance, first_name, last_name, avatar, etc.
I'm using single table inheritance to create two different types of users: Client and Barber.
I want each of these types of users to have a base Profile associated with it, and then have additional fields that belong to a BarberProfile and a ClientProfile, respectively.
The BarberProfile will have things that the Barber needs, but the Client doesn't. For instance, a bio. The ClientProfile will have things the Client needs, but the Barber doesn't. For instance, hair_type.
What I currently have, and my problem:
As stated above, I've created a table for User and Profile. So I'm able to call user.profile.first_name. I created a BarberProfile and ClientProfile table in order to add the extra fields.
I'd like to just be able to reference user.profile.bio if the user type is Barber. But bio isn't a part of the base profile. So in this case I'd have to create an associated Profile and and associated BarberProfile to get everything I need. I could just do user.profile.first_name and user.barber_profile.bio, but it feels messy, and I'm making two different associations from essentially the same type of model. I feel like it should be a simple thing to just have the BarberProfile inherit all fields from Profile and add its own specific Barber fields on top.
How does one go about doing this in Rails?
Edit: One of the main reasons I want to do this is so I can update things like first_name and bio within the same form for a Barber. And similarly, a first_name and hair_type within the same form for a Client.
If you want to avoid using two associations on the users for Profile and Client/BarberProfile, I think you should make ClientProfile and BarberProfile extend Profile (single table inheritance) and each of them "has one :barber_profile_data" (I'm not sure how to call it). To fix the long method calls, you can use delegated methods.
class Barber > User
has_one :barber_profile
delegate :bio, to: :barber_profile
class Client < User
has_one :client_profile
delegate :first_name, to: :client_profile
class BarberProfile < Profile
has_one :barber_profile_data
delegate :bio, to: :barber_profile_data
class ClientProfile < Profile
has_one :client_profile_data
delegate :first_name, to: :client_profile_data
Then, when you do "#barber.bio", it should call, internally, "#barber.barber_profile.barber_profile_data.bio".
This sounds like a good use case for Multiple Table Inheritance. In MTI you use additional tables to decorate the base model.
The main advantage to MTI is that the clients and barbers tables can contain the specific columns for that type vs STI which requires you to cram everything into users by design (thats why they call it single table).
create_table "barbers", force: :cascade do |t|
# only the specific attributes
t.text "bio"
end
create_table "clients", force: :cascade do |t|
# only the specific attributes
t.string "hair_type"
end
create_table "users", force: :cascade do |t|
t.string "email"
t.string "first_name"
t.string "last_name"
# ... all the other common attributes
t.integer "actable_id"
t.string "actable_type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
This is an example with the ActiveRecord::ActsAs gem.
class User < ApplicationRecord
actable
end
class Barber < ApplicationRecord
acts_as :user
end
class Client < ApplicationRecord
acts_as :user
end
Note that Barber and Client should not be true subclasses. ActiveRecord::ActsAs instead delegates to the "actable" class.
You can then do Barber.all or Client.all to fetch specific types or use User.all.map(:specific) to get decorated users of all types.
I initially asked a similar question in Restricting any access to a model in rails - but this hasn't covered my complete needs.
I have a Dataapi model with many entries:
create_table "dataapis", force: :cascade do |t|
t.string "device_id"
t.datetime "start_time"
t.datetime "end_time"
end
I have a Sandbox model with information on how I want to restrict the access (the Sandbox entries are defined in my admin panel).
create_table "sandboxes", force: :cascade do |t|
t.integer "device_id"
t.integer "user_id"
t.datetime "start_date"
t.datetime "end_date"
end
Effectively, I only want users to have access to the dataapi if they have an appropriate entry in the Sandbox, which restricts access by:
- User
- Start DateTime
- End DateTime
- Access to the device that sent the dataapi (already handled that part in previous question).
I can't seem to find a way to do this though - models don't have access to #user, so I can't check that in the default scope. Any recommendations? I've tried looking at gems (CanCanCan, Pundit, etc) but they only do controller-based authorization. I want this restriction to apply to all queries to dataapi, regardless of what controller called it.
You might want to try the Declarative Authorization gem which provides authorization at the model level.
https://github.com/stffn/declarative_authorization
It gives you a query method that filters based on user's permissions...
Employee.with_permissions_to(:read).find(:all, :conditions => ...)
Another alternative is you can store the current_user in the Dataapi model and populate it with an around_filter.
In the model...
class Dataapi < ActiveRecord::Base
def self.current_user
Thread.current[:current_user]
end
def self.current_user=(some_user)
Thread.current[:current_user] = some_user
end
end
In the application controller...
class ApplicationController < ActionController::Base
around_filter :store_current_user
def store_current_user
Dataapi.current_user = User.find(session[:user_id])
yield
ensure
Dataapi.current_user = nil
end
end
You can then reference the current_user in the Dataapi model using Dataapi.current_user
I've been trying to have my rails project only update the user table with the users unique facebook data. However, I can't get the facebook data to populate. I've tried multiple approaches but the end code seems to be hacky and using brute force to update the columns (as well as creating duplicate records)
Here are my examples:
User
class User < ActiveRecord::Base
has_one :facebook
def self.create_with_omniauth(auth)
create! do |user|
user.email = auth['email']
end
end
end
Facebook
class Facebook < ActiveRecord::Base
belongs_to :user
def self.create_with_omniauth(auth)
create! do |fb|
if auth['info']
fb.profile_link = auth['info']['profile_link'] || "test"
end
end
end
Migrations:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :email
t.timestamps null: false
end
end
end
class Facebooks < ActiveRecord::Migration
create_table :facebooks do |f|
f.belongs_to :user, index: true, :unique => true
f.string :profile_link
f.timestamps null: false
end
end
While creating the user:
SessionController (When calling create for user)
def create
auth = request.env["omniauth.auth"]
user = User.where(:provider => auth['provider'],
:uid => auth['uid'].to_s).first || User.create_with_omniauth(auth)
Facebook.create_with_omniauth(auth)
My understanding of Rails ActiveRecord so far... is that if I use "has_one" and "belongs_to" then it should automatically create records in the facebook table if a user table was created?
My expected Data would be:
SELECT * FROM users where id = 1;
id email
1 email#email.com
SELECT * FROM facebooks where user_id = 1;
id user_id profile_link
1 1 facebook.com/profile_link
facebook has no record created at all.
Not sure where I went wrong, I've followed tons of tutorials and hope I can master the active record.
Thanks!
Side Question for #val
def self.facebook_handler(user, auth)
if Facebook.exists?(user_id: id)
user = Facebook.find_by(user_id: id)
user.update(name: me['name'])
user.update(first_name: me['first_name'])
else
create! do |fb|
if me
fb.name = me['name']
fb.user_id = user.id
fb.first_name = me['first_name']
end
end
end
end
--- otherwise it kept inserting new records each time I logged in.
So many moving pieces in activerecord and in Rails. I think you have to go back to your migration and address a few things to set a solid model foundation for the view and controller parts of your MVC.
I see model-type function in the migration you posted, which is not going to serve you well. Migrations should be as flexible as possible, the constraints should be placed on the model.rb.
Migration: Flexible. Basic relationship indices set up.
Model: The
model.rb defines constraints (has_one, belongs_to, etc) and further
embellishes and validates data relationships (:dependent,:required,
etc.)
Your users model looks fine.
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :email
t.timestamps null: false
end
end
end
Your facebooks migration should have looked more like this. Create a t.reference and add the index.
class Facebooks < ActiveRecord::Migration
create_table :facebooks do |f|
t.references :user, index: true
f.string :profile_link
f.timestamps null: false
end
add_index :facebooks, [:user_id]
end
Then in your Facebook model you can apply restraints and requirements
facebook.rb
belongs_to :user,
validates :user_id, presence: true, :unique => true
Your user model.rb should include:
has_one :facebook
There are some other questions about your higher level actions in the controller, but I think setting up your model will help you make progress towards your goal.
The model constraints below, along with the index setup looks like it would cause ActiveRecord to ROLLBACK and not add a duplicate facebook record for a given user. But it sounds like duplicates are being added to the facebook table. So, how?
facebook.rb
belongs_to :user,
validates :user_id, presence: true, :unique => true
...
user.rb
has_one :facebook
The 'if' clause you wrote looks to me as if it would be unnecessary if the relationship between user / facebook are set up and working in the model and database table, which makes me think there's a missing validation somewhere.
There's something to try, a model migration (change) on Facebook data description to add a :unique validator to the user_id field of the db table itself. (There's no change_index command, you have to remove and then add.)
remove_index :facebooks, [:user_d]
add_index :facebooks, [:user_id], :unique => true
Try taking your 'if' logic out and see if you're getting dupes. The relationships need to be properly setup before proceeding to the logic in the controller or you will break your head trying to unwind it.
And to your question in the comment, scopes are beautiful for creating collections based on parameters. So, in your user.rb model:
scope :important_thing_is_true, -> { where(:provider => auth['provider'],:uid => auth['uid'].to_s).first) }
Which is referenced by user.important_thing_is_true returns the collection or nil, which then you can test or use in other logic or display, etc. But, if you don't have the dupe records problem, maybe this scope isn't needed.
I have the following model with subscriptions for classes
create_table "subscriptions", force: true do |t|
t.integer "user_id"
t.integer "course_id"
t.datetime "date_subscription_start"
t.datetime "date_subscription_end"
t.string "subscription_type"
t.datetime "created_at"
t.datetime "updated_at"
end
I want to make it so users are able to subscribe to the same class again if they, for some reason, want to retake it. My logic is that it should only be possible to sign up for a class if the date_subscription_end date of the class / user combination has already passed at the time of the new creation. If at the time of creation a Subscription with the same user_id and course_id exists that has a subscription_end date in the future it should be rejected because it means the user is still taking that course.
So something like this:
validates :user_id,
uniqueness: {
scope: :course_id,
message: "User is already subscribed to this course"
},
unless: Proc.new { |a|
Subscription.where(user_id: a.user_id, course_id: a.course_id) &&
Subscription.where('date_subscription_end < ?', Time.now)
}
Basically rails has to go through the subscriptions table, find all subscriptions with the same primary keys and check their date_subscription_end attributes. I feel like I'm close but missing something fundamental.
Thanks!
Jean
Edit: For some reason the "Hello!" greeting at the top of the post gets removed
First the Proc looks incorrect, as far as I can see the Proc returns a truthy value, as soon as the user had a subscription and some user (not necessarily a.user_id) subscribed any course (not necessarily a.course_id)
Second the unless needs to be an if and be part of the options hash for the uniqueness validation.
So the following code should work:
validates :user_id,
uniqueness: {
scope: :course_id,
message: "User is already subscribed to this course",
if: Proc.new { |a|
Subscription.where(user_id: a.usr_id, course_id: a.course_id)
.where('date_subscription_end >= ?', Time.now).exists?
}
}
This way you hit the database only once and your code doesn't need to instantiate all matching subscriptions like it does if you just run .where(…)
UPDATE:
the condition for the date_subscription_end should be: 'date_subscription_end >= ?', Time.now