Adding data to a database in ruby - ruby-on-rails

I am in quite the pickle , I think I overreach with my current ruby knowledge but I don't want to give up.
I currently have a tweeter that can post and people can follow other people thanks to https://www.railstutorial.org/book/ . I do want to add hashtags to this tutorial tweeter. In order to do I created 2 tables since tweet and hashtag is a many to many relationship . The tables are :
class CreateHashrelations < ActiveRecord::Migration
def change
create_table :hashrelations do |t|
t.integer :tweet_id
t.integer :hashtag_id
t.timestamps null: false
end
add_index :hashrelations, [:tweet_id, :hashtag_id], unique: true
end
end
which is the extra table you need to keep the keys of the tweet and hashtag . And the other table is the hashtag table where I have the id and the name of the hastag
class CreateHashtags < ActiveRecord::Migration
def change
create_table :hashtags do |t|
t.string :name
t.timestamps null: false
end
end
end
In the models I put the following relathipships:
class Hashtag < ActiveRecord::Base
has_many :hashtagrelations, dependent: :destroy
has_many :tweets, through: :hashtagrelations
validates :name, presence: true
end
class Hashrelation < ActiveRecord::Base
belongs_to :tweet
belongs_to :hashtag
validates :tweet_id, presence: true
validates :hashtag_id, presence: true
end
class Tweet < ActiveRecord::Base
.....
has_many :hashtagrelations, dependent: :destroy
has_many :hashtags, through: :hashtagrelations
....
end
When a tweet is submited I save it and if it is saved I want to see if it has hashtags and if it does I want to add the necessary data in the Hashtagrelations and Hashtags tables.
I try to do this this way :
class TweetsController < ApplicationController
......
def create
#tweet = current_user.tweets.build(tweet_params)
if #tweet.save
add_hashtags(#tweet)
flash[:success] = "Tweet created!"
redirect_to root_url
else
#feed_items = []
render 'static_pages/home'
end
end
......
private
........
def add_hashtags(tweet)
tweet.content.scan(/(?:\s|^)(?:#(?!(?:\d+|\w+?_|_\w+?)(?:\s|$)))(\w+)(?=\s|$)/){ |tag|
newhash[:new] = tag
#hashtag = Hashtag.new(new_hash[:new])
#hashtag.save
data[:new] = [tweet.id,#hashtag.id]
#hashrel = Hashtagrel.new(data[:new])
#hashrel.save
}
end
end
Which is not the right way. I tried to add the newhash and data because if I only did tag there I would get
When assigning attributes, you must pass a hash as an argument.
I realise that this is kind of a silly question but I have found no tutorial that teaches me how should I add this data to my tables. I would be grateful for your help

This is an array of values:
data[:new] = [tweet.id,#hashtag.id]
Before moving things inside another variable (or data structure), try being explicit first.
#hashrel = Hashtagrelation.new(tweet_id: tweet.id, hashtag_id: #hashtag.id)
The rest of the code looks good, I think you've got it.

Related

RAILS - CHANGE FIELD OF ANOTHER TABLE - BOOLEAN

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 &

How Do I Use A Join Table in Ruby On Rails?

I have users and books. It's a many to many relationship, so I created a join table based on questions and answers I found on this site.
class Book < ApplicationRecord
has_and_belongs_to_many :users
end
class User < ApplicationRecord
has_and_belongs_to_many :books
end
class BooksUsersJoinTable < ActiveRecord::Migration[5.0]
def change
create_table :books_users_join_table, :id => false do |t|
t.integer :book_id, index: false
t.integer :user_id, index: false
end
end
add_index(:books_users, [:book_id, :user_id], :unique => true)
add_index(:books_users, :book_id)
add_index(:books_users, :user_id)
end
So I suppose my questions are:
Will this join_table work how I have it or is there a better way to have written it?
I just wrote the add_index lines in this file. Do I have to create indexes from the command line and if so, how do I do that?
How do I use this join_table in a controller?
This is what I use do a join
class BooksController < ApplicationController
def index
#books Book.all.joins(:user)
end
end
I hope that this helps

Rails: incrementing attribute from a model, upon creation of an instance from another model

Our Rails app works with the following 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
end
And here are our 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
When a new #calendar is created, we need to increment :total_calendar_count and :owner_calendar_count by one in the User table.
We tried this in the CalendarsController:
class CalendarsController < ApplicationController
def create
#calendar = current_user.calendars.create(calendar_params)
current_user.total_calendar_count += 1
current_user.owned_calendar_count += 1
current_user.administrations.find_by(calendar_id: #calendar.id).update(role: 'Creator')
...
end
But it does not seem to update :total_calendar_count and :owner_calendar_count by one in the User table.
Are we missing a step here? Should we use an update action instead?
The actual problem in your code is that you don't then save the user.
So you update the counter... but this changes it on the local instance... and then after the controller action is done the change you made just disappears.
if you wanted to keep your code the way it is, you could do:
current_user.save
at the end.
but I'd advise you to look into the counter_cache, because it's the Rails way.
Also I'll point out that you haven't checked that the calendar successfully got created, before incrementing that counter... it's possible that it could fail a validation and not really have been created... you need to check for that first.
I have a best idea to solve your problems is as below....
Create a method that will call on the creating of calendar with the callbacks of model like as below...
Add the below inside the calendar model just after the validation and ORM relations
after_create :increment_counter
def increment_counter
calendar_user = self.user
calendar_user.update(:total_calendar_count += 1, :owned_calendar_count += 1 )
end
With the above code you don't need to do anything. It will increment the counter of calendar on every new entry of calendar.

Rails model multiple has_many :through relationships output to json

I have the following data models and would like to render a json hash that includes information from each model. For example, client.id, client.name_first, client, name_last, every workout description for each client and each exercise description for each workout.
class Client < ActiveRecord::Base
belongs_to :account
belongs_to :trainer
has_many :programs
has_many :workouts, :through => :programs
end
class Workout < ActiveRecord::Base
has_many :programs
has_many :clients, :through => :programs
has_many :routines
has_many :exercises, :through => :routines
end
class Exercise < ActiveRecord::Base
has_many :routines
has_many :workouts, :through => :routines
end
My database migrations:
class CreateClients < ActiveRecord::Migration
def change
create_table :clients do |t|
t.integer :account_id
t.integer :trainer_id
t.string :name_first
t.string :name_last
t.string :phone
t.timestamps
end
end
end
class CreateWorkouts < ActiveRecord::Migration
def change
create_table :workouts do |t|
t.string :title
t.string :description
t.integer :trainer_id
t.timestamps
end
end
end
class CreateExercises < ActiveRecord::Migration
def change
create_table :exercises do |t|
t.string :title
t.string :description
t.string :media
t.timestamps
end
end
end
I am able to return the workouts for a particular client:
#client = Client.find(params[:id])
clients_workouts = #client.workouts.select('workouts.*,programs.client_id').group_by(&:client_id)
render json: clients_workouts
And I am able to return the exercises for a particular workout:
#workout = Workout.find(params[:id])
exercises_workouts = #workout.exercises.select('exercises.*, routines.workout_id').group_by(&:workout_id)
render json: exercises_workouts
However, I do not know how to return the data with information from all three tables (Client, Workout, Exercise) included (joined through Programs and Routines). Is this possible? And how is it done?
First, I'm not really sure what's happening in your query:
clients_workouts = #client.workouts.select('workouts.*,programs.client_id').group_by(&:client_id)
Is this not sufficient?
#client.workouts
Now, on to the answer... assuming I'm still following:
ActiveRecord offers a .to_json method, which is what's being implicitly called here. The explicit version would be e.g.
render json: clients_workouts.to_json
Knowing that, you can look up to_json in the api (here's some good documentation even though it shows as deprecated: http://apidock.com/rails/ActiveRecord/Serialization/to_json). But, basically, the answer is to start with the root object -- the client I believe -- and build the included objects and attributes/methods from there in the options hash.
render json: #client.to_json(include: { workouts: { include: :exercises } })
You can customize which attributes or methods are included from each related model if needed, just dig into the documentation a little. Have fun!
Very possible and their are different ways to optain this.
One, without any 3rd party library is to use includes, just as if you were solving an n+1 problem or…
Use a much cooler approach and use active model serializers
Active Model Serializers

how to extract hashtags and display them groups by descending dates? (ruby on rails)

I created a new model called 'hashtags' and a new table in my database
Here's the schema.db
create_table "hashtags", :force => true do |t|
t.string "hashtags"
t.integer "post_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
hashtags.rb
class Hashtags < ActiveRecord::Base
attr_accessible :hashtags, :post_id
end
def create
hashtag_regex = /\b#\w\w+/
#post = current_user.posts.build(params[:post])
#post.hashtags = #post.text.scan(hashtag_regex)
end
Inside the post model, this is what I've added
class Post < ActiveRecord::Base
attr_accessible :content
belongs_to :user
has_many :hashtags, dependent: :destroy
For all posts where there's a hashtag (or could be 2+ hashtags), I want to list them in descending order by date in a new page called '/hashtags'.
So this is how I want it to show in view
4/30/2013
#tag3 #tag2 by user2
#tag1 by user5
4/29/2013
#tagz by user10
#tagx #tagy by user3
4/25/2013
#tagz #tagy #tagx by user2
Inside views\static_pages\hashtags.html.erb
I'm trying to create this view, but how could I best go about this?
To save the hash tags you need a before save callback method, and get rid of that logic in the controller.
class Post < ActiveRecord::Base
after_save :process_hashtags
def process_hashtags
hashtag_regex = /\B#\w\w+/
text_hashtags = text.scan(hashtag_regex)
text_hashtags.each do |tag|
hashtags.create name: tag
end
end
end
This callback will iterate through the array of hashtags that it finds in the text, and create a Hashtag model and associate it with the Post model.
Let me know if it works, as it might need some adjustments.
This is how you would dot it in your Model: For
class Post < ApplicationRecord
has_many :hashtags, dependent: :destroy
after_commit :process_hashtags
def process_hashtags
hashtag_regex = (/#\w+/)
text_hashtags = content.to_s.scan(hashtag_regex)
text_hashtags.each do |name|
HashTag.create name: name, address: user.country
end
end
end
Model for # for hashTags
class HashTag < ApplicationRecord
belongs_to :post
end

Resources