How can I build a flagging system for posts? - ruby-on-rails

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).

Related

Is there a way in rails to make a model optionally belong to another model?

I am building a rails exercise app that has both exercises and routines. I want each routine to be composed of several exercises (has_many :exercises), but an exercise doesn't necessarily have to belong to a routine. Is there a way to do that?
Reading the guides is always a good start. This works from Rails 5 onwards.
belongs_to :routine, optional: true
You probably want a many-to-many relationship here, rather than a one-to-many relationship.
It would seem you would want for an Exercise to be associated with any number of Routines, and for a Routine to be associated with one or more Exercises.
You end up with something like this:
# app/models/routine.rb
class Routine < ActiveRecord::Base
has_and_belongs_to_many :exercises
end
# app/models/exercise.rb
class Exercise < ActiveRecord::Base
has_and_belongs_to_many :routines
end
# db/migrate/1213123123123_create_exercises_routines_join_table.rb
class CreateExercisesRoutinesJoinTable < ActiveRecord::Migration
def self.change
create_table :exercises_routines, :id => false do |t|
t.integer :exercise_id
t.integer :routine_id
t.index [:category_id, :routine_id]
end
end
end

activerecord relation model name displaying twice

Good afternoon,
First post here. I have done a bit of research into this error and not finding anything helpful to me, and because stackoverflow community is my number 1 place to find a answer. I thought why not ask here.
I have created a new rails app, and have created a relation between the order and order_item model.
app/models/order.rb
class Order < ActiveRecord::Base
belongs_to :customer, foreign_key: 'customer_id'
belongs_to :shop
has_many :order_items, dependent: :destroy
end
app/models/order_item.rb
class OrderItem < ActiveRecord::Base
belongs_to :order
end
The relationship between customer and order is working because I have created a record for both.
eg
Customer.create(:customer_attributes).orders.create
but when I try to access or create a order item
Customer.first.orders.first.order_items.first || Customer.first.orders.first.order_items.create
I get the following error
ActiveRecord::StatementInvalid: Could not find table 'order_order_items'
my question is, why is it looking for the table name with the prefix twice?
below is what my migration files look like for both models
db/migrate/orders
class CreateOrders < ActiveRecord::Migration
def change
create_table :orders do |t|
t.integer :shop_id
t.integer :customer_id
t.string :status
t.string :additional_info
t.timestamps null: false
end
end
end
db/migrate/order_items
class CreateOrderItems < ActiveRecord::Migration
def change
create_table :order_items do |t|
t.integer :order_id
t.integer :menu_item_id
t.timestamps null: false
end
end
end
Any help will be greatly appreciated. New to ruby and rails, so please excuse my lack of knowledge.
Cheers
EDITED
Hey guys! Thank for fast replies, I figured out what the problem was. Earlier in the project I had tried to implement namespacing into the models and this is what was causing the trouble. Once i moved all models directly under the app/models dir and restarted the console all worked 100%. So im guessing I dont understand enough yet of how rails handles the namespacing, but im going to try the namespacing at later stage. For now I just need a quick prototype up an running to demo to a few clients.
Once again thanks for the fast replies.

Rails ActiveRecord not working with two tables

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.

Why is my foreign key association not yielding results?

I have two models in my rails app, Appointment and Policy. My Appointment model has_many :policies, class_name: "Policy", foreign_key: 'writing_code' and my Policy model belongs_to :appointment. The writing_code column in each table is a String.
While it would appear that the association has been made (my app runs), #appointment.policies yields no results. Can someone shed some light on where I've gone wrong here?
Also, to preemptively answer the obvious question, I cannot simply use appointment_id because I will be uploading policy data that has an appointment's (or user's) "writing code" associated to each record. The data will not include the appointment id since it comes from a separate, third party system.
Thanks in advance for all your help!
EDIT:
schema:
create_table "policies", :force => true do |t|
t.integer "product_id"
t.decimal "premium"
t.string "writing_code"
t.datetime "created_at", :null => false t.datetime "updated_at", :null => false end`
I think here is your problem:
You have:
class Appointment
has_many :policies, class_name: "Policy", foreign_key: 'writing_code'
end
Here is what the guides says
"By convention, Rails assumes that the column used to hold the foreign key on this model is the name of the association with the suffix _id added."
Here is an example from the guides:
class Order < ActiveRecord::Base
belongs_to :customer, class_name: "Patron",
foreign_key: "patron_id"
end
In your case, your class name is Policy but your foreign_key name is writing_code. which is not conventional.
Rails is very opinionated. Stay within the way rails likes to work and life gets much easier.
I Agree completely with #WaliAli said also.
"#appointment.policies yields no results"
For appointment to have many policies, each policy needs to be linked with an appointment, in the Model AND in the the table schema.
What this means is that policies should have a field 'appointment_id' which is an integer.
Add an appointment_id field to the policies table.
$ rails generate migration AddAppointmentIdToPolicies appointment_id:integer
$ rake db:migrate
Then you do the following:
class Policy
has_many :appointments # this lets you do #policy.appointments
end
class Appointment
belongs_to :policy # this lets you do #appointment.policy
end
For 99% of rails apps doing it another way, whereby has_many & belongs_to includes more optional paramaters is a code smell.
[Update 1:]
"I have policy data that will be uploaded from a legacy system via .csv
file. This data will have no knowledge of the appointment_id, which is
why I need to use the writing_code as the foreign key value, as it's
the only common data that can associate the two models."
I would suggest resolving this as part of your CSV importer.
As you import each record, do a find for an appointment record that has a matching 'writing code' and then save the record to include the appointment id.
Something like this:
# Inside your CSV importer script
csv_rows.each do |row|
policy = Policy.new
policy.appointment_id = Appointment.find(writing_code: row.writing_code).id
# more code here..
policy.save
end

Scaffolding ActiveRecord: two columns of the same data type

Another basic Rails question:
I have a database table that needs to contain references to exactly two different records of a specific data type.
Hypothetical example: I'm making a video game database. I have a table for "Companies." I want to have exactly one developer and exactly one publisher for each "Videogame" entry.
I know that if I want to have one company, I can just do something like:
script/generate Videogame company:references
But I need to have both companies. I'd rather not use a join table, as there can only be exactly two of the given data type, and I need them to be distinct.
It seems like the answer should be pretty obvious, but I can't find it anywhere on the Internet.
Just to tidy things up a bit, in your migration you can now also do:
create_table :videogames do |t|
t.belongs_to :developer
t.belongs_to :publisher
end
And since you're calling the keys developer_id and publisher_id, the model should probably be:
belongs_to :developer, :class_name => "Company"
belongs_to :publisher, :class_name => "Company"
It's not a major problem, but I find that as the number of associations with extra arguments get added, the less clear things become, so it's best to stick to the defaults whenever possible.
I have no idea how to do this with script/generate.
The underlying idea is easier to show without using script/generate anyway. You want two fields in your videogames table/model that hold the foreign keys to the companies table/model.
I'll show you what I think the code would look like, but I haven't tested it, so I could be wrong.
Your migration file has:
create_table :videogames do |t|
# all your other fields
t.int :developer_id
t.int :publisher_id
end
Then in your model:
belongs_to :developer, class_name: "Company", foreign_key: "developer_id"
belongs_to :publisher, class_name: "Company", foreign_key: "publisher_id"
You also mention wanting the two companies to be distinct, which you could handle in a validation in the model that checks that developer_id != publisher_id.
If there are any methods or validation you want specific to a certain company type, you could sub class the company model. This employs a technique called single table inheritance. For more information check out this article: http://wiki.rubyonrails.org/rails/pages/singletableinheritance
You would then have:
#db/migrate/###_create_companies
class CreateCompanies < ActiveRecord::Migration
def self.up
create_table :companies do |t|
t.string :type # required so rails know what type of company a record is
t.timestamps
end
end
def self.down
drop_table :companies
end
end
#db/migrate/###_create_videogames
class CreateVideogames < ActiveRecord::Migration
create_table :videogames do |t|
t.belongs_to :developer
t.belongs_to :publisher
end
def self.down
drop_table :videogames
end
end
#app/models/company.rb
class Company < ActiveRecord::Base
has_many :videogames
common validations and methods
end
#app/models/developer.rb
class Developer < Company
developer specific code
end
#app/models/publisher.rb
class Publisher < Company
publisher specific code
end
#app/models/videogame.rb
class Videogame < ActiveRecord::Base
belongs_to :developer, :publisher
end
As a result, you would have Company, Developer and Publisher models to use.
Company.find(:all)
Developer.find(:all)
Publisher.find(:all)

Resources