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.
Related
Good afternoon. I'm new to rails and I'm using google translate to post in English here, so sorry if it's not very readable.
My question is, I have a User table, and a Setting table.
They are related (but I don't know if the relationship is correct), they can even confirm me, and I would like to know if:
when creating a user, I would like to automatically change the "email" and "push" fields of that user's settings table to true.
Would it be possible via a method that in the user model called: "setting_default"?
User model.
class User < ApplicationRecord
has_one :setting
before_save :setting_default
def setting_default
self.setting.update(:email, 'true')
self.setting.update(:push, 'true')
end
Setting Model
class Setting < ApplicationRecord
has_one :user
end
The Controller is normal, if you need it, I can put it in the post
My migration:
class CreateSettings < ActiveRecord::Migration[6.0]
def change
create_table :settings do |t|
t.boolean :email, default: true
t.boolean :push, default: true
t.timestamps
end
end
end
class AddSettingsToUser < ActiveRecord::Migration[6.0]
def change
add_reference :users, :setting, null: true, foreign_key: true
end
end
Google translate has worked well for you here.
First off you'll want to change your Setting model to belong to the User:
class Setting < ApplicationRecord
belongs_to :user
end
Your settings DB table is missing a user_id field to tie the setting back to the user. I'm not used to the add_reference technique so I just do things myself in the migrations. This would work:
class CreateSettings < ActiveRecord::Migration[6.0]
def change
create_table :settings do |t|
t.integer :user_id
t.boolean :email, default: true
t.boolean :push, default: true
t.timestamps
end
end
end
(Make note that your users DB table has a field setting_id that it does not need. I don't think it should be there. I would remove it. Unless it's a Rails 6 thing I'm not used to.)
Next it would probably be better to assign the values if the save succeeds (and not if it fails) so you'll want an after_save instead. And I'm simplifying your value assignment just in case you're having an issue there:
class User < ApplicationRecord
has_one :setting
after_save :setting_default
def setting_default
setting.email = true
setting.push = true
setting.save(validate: false)
end
private :setting_default
And to answer what seems to be your question, yes, what you're trying to do should be easily possible. This is a very common thing to do. It should work.
When you use one-to-one association you need to choose has_one in one and belongs_to in another model
Semantically user has one setting, but not setting has one user
So it's better to reverse them
To change your schema you need to write new migration
class ChangeOneToOneDirection < ActiveRecord::Migration[6.0]
def up
change_table :settings do |t|
t.belongs_to :user, foreign_key: true, null: false
end
User.where.not(setting_id: nil).find_each |user|
Setting.find(user.setting_id).update_columns(user_id: user.id)
end
change_table :users do |t|
t.remove :setting_id
end
end
def down
add_reference :users, :setting, null: true, foreign_key: true
Setting.find_each do |setting|
User.find(setting.user_id).update_columns(setting_id: setting.id)
end
change_table :settings do |t|
t.remove :user_id
end
end
end
After migration you can change User model
class User < ApplicationRecord
has_one :setting
after_commit :setting_default
private
def setting_default
setting&.update(email: true, push: true)
end
end
It's better to update associated model only if saves are in the database. And user can haven't setting. That's why after_commit and safe-navigator &
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
I am using rails 4.2, I just want to know if there would be any difference if I use the :foreign_key keyword in my migrations rather than just adding a user_id column to add relationship to my models ?
YES
The key difference is not on the application layer but on the database layer - foreign keys are used to make the database enforce referential integrity.
Lets look at an example:
class User < ActiveRecord::Base
has_many :things
end
class Thing < ActiveRecord::Base
belongs_to :user
end
If we declare things.user_id without a foreign key:
class CreateThings < ActiveRecord::Migration
def change
create_table :things do |t|
t.integer :user_id
t.timestamps null: false
end
end
end
ActiveRecord will happily allow us to orphan rows on the things table:
user = User.create(name: 'Max')
thing = user.things.create
user.destroy
thing.user.name # Boom! - 'undefined method :name for NilClass'
While if we had a foreign key the database would not allow us to destroy user since it leaves an orphaned record.
class CreateThings < ActiveRecord::Migration
def change
create_table :things do |t|
t.belongs_to :user, index: true, foreign_key: true
t.timestamps null: false
end
end
end
user = User.create(name: 'Max')
thing = user.things.create
user.destroy # DB says hell no
While you can simply regulate this with callbacks having the DB enforce referential integrity is usually a good idea.
# using a callback to remove associated records first
class User < ActiveRecord::Base
has_many :things, dependent: :destroy
end
In our Rails 4 app, there are four models:
class User < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :calendars, through: :administrations
end
class Administration < ActiveRecord::Base
belongs_to :user
belongs_to :calendar
end
class Calendar < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :users, through: :administrations
has_many :posts, dependent: :destroy
end
class Post < ActiveRecord::Base
belongs_to :calendar
end
Here are the corresponding migrations:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :email
t.integer :total_calendar_count
t.integer :owned_calendar_count
t.timestamps null: false
end
end
end
class CreateAdministrations < ActiveRecord::Migration
def change
create_table :administrations do |t|
t.references :user, index: true, foreign_key: true
t.references :calendar, index: true, foreign_key: true
t.string :role
t.timestamps null: false
end
end
end
class CreateCalendars < ActiveRecord::Migration
def change
create_table :calendars do |t|
t.string :name
t.timestamps null: false
end
end
end
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.references :calendar, index: true, foreign_key: true
t.date :date
t.time :time
t.string :focus
t.string :format
t.string :blog_title
t.text :long_copy
t.text :short_copy
t.string :link
t.string :hashtag
t.string :media
t.float :promotion
t.string :target
t.integer :approval
t.text :comment
t.timestamps null: false
end
end
end
So basically, a user can have many calendar and a calendar can have many user.
When a #user creates a new #calendar, we generate a new #administration: this is currently working.
What we would like to do now is allowing a #user to invite other #user — already registered or not — to join an existing #calendar.
This would create a new #administration, between the invited #user and an existing #calendar.
The tricky part here — or at least the blurry part for us — seems to be to handle both registered and unregistered users:
For unregistered users: when the "inviting" user shares a calendar with the "invited" user, how do we allow the latter to get the invitation through the email address typed in by the former, while allowing him to register to the app through an email address of his choice (either the same or another one)?
For registered users: when the "inviting" user shares a calendar with the "invited" user, how do we allow the latter to get the invitation through the email address typed in by the former, while allowing him to either log in to the app with his existing account (either the same or a different email address than the one used by the "inviting" user) or sign up with an email address of his choice (either the one used by the "inviting" user or another address)?
Most importantly, how do we persist the information related to the calendar (let's say calendar_id) so that, once the "invited" user has either logged in or signed up, he has access to the calendar the "inviting" user wanted to grant him access to?
Note: We are currently using a custom authentication/authorization system as suggested in Michael Hartl's Rails Tutorial and are not using Devise or another gem for that matter.
Now, the question is how to achieve this:
Should we simply create an invite method in the Administration model and use it in the AdministrationsController#new?
Should we create a whole new InvitationsController?
Should we use a gem like Devise Invitable*?
Or is there a completely different, obvious solution we are missing?
Ok, Here is my 2 cents. Please let me know if you need more information.
Because you have made a custom Authentication and the Authorisation then this solution might help you.
I assume your access list should be something like this as I can't see your Access list in the posted migration.
Anyway, at the registration you an assign a token to every user.
Something like this:
before_create :generate_token
private
def generate_token
begin
self.a_token = SecureRandom.hex
end while self.class.exists?(a_token: access_token)
end
Then when user invites simply allocate this token.
Now you need a method at the registration to simply recognise the token, then filled the Invite table.
rest should be easy.
Inside your Authentication module simply have a method like this:
def invited_by
Invite.where(:user_id => current_user.id).first
end
( I don't like Rails find_by_ methods you can use find_by_user_id - I am a bit old fashioned ;)
( I assume you made a module that you can use it throughout your application ) If not then let me know I will help you out mate.
So now you got the user who invited your current user. Now simply return the Permissions which you have allocated for the user who is invited your current user and continue with the application.
Hope it helps. Let me know if you need more direction. I used the same method for one of my application for affiliate program and member benefits of being affiliate.
Cheers.
Update
Because you asked about the Authorisation Module and you have a custom Authentication. Here some code. (Partially tested and Needs to be refactored please use it as a guide, they are some common tasks I could think of)
module AuthorisationRelated
def what_are_current_user_roles
objArray = []
current_user.roles.each do |role|
objArray.push role.name
end
return objArray
end
def does_user_have_this_role?(role_name)
result = false
obj_array = what_are_current_user_roles
obj_array.each do |a_role|
if a_role == role_name
result = true
end
end
result
end
def is_admin?
athu = false
if signed_in?
current_user.roles.each do |role|
if role.name == 'admin'
athu = true
end
end
end
return athu
end
# class_instance MUST be a parent class
# If you need to Authenticate Model then your class_instance has to be #instance
def user_allowed_create_and_edit?(class_model, class_instance)
user_is_allowed = false
if permitted_to? :create, class_model.new and has_user_own_this(class_instance)
user_is_allowed = true
else
user_is_allowed = false
end
# Override everything if user is admin
if is_admin?
user_is_allowed = true
end
return user_is_allowed
end
# relation has to be set to access_list
def has_user_own_this(model)
user_who_owns_this = model.access_list.user_id
if current_user.id == user_who_owns_this
return true
else
return false
end
end
def find_current_user_acls
acl = []
acls = AccessList.joins(:user).where("users.id = ?",current_user.id)
acls.each do |an_acl|
acl.push an_acl.id
end
acl
end
end
Update
Following your comments regarding a good source for Authorisation.
I would suggest use Gems or at least one Authorisation Gem that can handle complex assignments.
One of My All time sweethearts Declarative Authorization . I have implemented the same DB structure with this Gem on a website with 6 different Groups ACL and bucket loads of permissions with over 500K active users.
Check out this Tutorial: http://railscasts.com/episodes/188-declarative-authorization Very good starting point.
Because you don't have Devise or something like this, just make sure you have the current_user handy. You can define it in the ApplicationController
This tutorial will tell you how to make the current user (paid tutorial - it worth it - trust me!) http://railscasts.com/episodes/250-authentication-from-scratch-revised
I've been trying to figure out the best way to build out a user flagging system in rails 3.1. I experimented with the make_flaggable gem, but it didn't give me what I needed.
I'm using devise for my user model and I have a Post model that belongs to the user. I need to have the ability to retrieve a list of all posts that have been flagged from the admin side of the site. So far, I've had difficulty obtaining that.
I'm uncertain about which type of relationship I would need to use between a Flag model and the Post/User model. I've been reading up on Polymorphic relationships and that is looking promising.
Any ideas or feedback would be much appreciated!
It's very easy to roll your own solution. I would do it this way.
class User
has_many :flags
end
class Post
has_many :flags
end
class Flag
belongs_to :user
belongs_to :post
end
You can get posts that have been flagged by going through the flag model or the post model.
# since a post has many flags
# you will get duplicates for posts flagged more than once
# unless you call uniq
flagged_posts = Post.joins(:flags).uniq
Or through the flags model:
flags = Flag.includes(:post).uniq
flags.each do |flag|
puts flag.post
end
To ensure you don't get duplicate flags on the same post from the same user I would add a uniq index in the migration:
def change
create_table :flags do |t|
t.belongs_to :user, null: false
t.belongs_to :post, null: false
t.timestamps
end
add_index :flags, [:user_id, :post_id], unique: true
end
Maybe I'm misunderstanding what you're trying to do, but why not just add a column to your Posts table called "flagged?" Then you can just do User.posts(:where=> :flagged=>true).