Activity stream in angular or rails? - ruby-on-rails

I have rails/angular app where users can add movies to their watchlist, users can also create friendships with other users. Now I would like to create a activity stream that notifies users when a friend has added a movie to their watchlist. But I can't find any good resources on how to get started on this feature.
For the friendships I have a friendship model,
create_table "friendships", force: :cascade do |t|
t.integer "user_id"
t.integer "friend_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
What would be the connection between users, friends and newly added records (movies)?

You can try public_activity gem. I believe you find it usefull and the documentation provides almost every case you describe.
You have one screencast on the subject here and it supports both Rails 3.x and 4.x.
About your model, I believe you should start first by creating an relational model of your data and then translate it to Rails relationships.

You can try
Paper trail
Any time a change is made in your system, paper trail can track the user id via a whodunnit property. This is set automatically if your controller makes a current_user object available. . it will also track any user, regardless of whether there is an actual relationship between the user and the model.
https://github.com/pokonski/public_activity. It's pretty simple to install and setup.
Also there is Audited
PaperTrail, Vestal versions and Acts as versioned are all very powerful versioning gem. Yes, you can also use it to audit/track users' activities , but it's not very straight forward because they are 'versioning tools', but not for the 'auditing purpose'
Userstamp and audited are for auditing purposing only. but Userstamp is for older Rails versions ( it's a plugin... which is not supported by Rails3(or 3.1, 3.2?) anymore )

Related

Ruby on Rails 5 strong parameters

I'm having some troubles with a project I'm working on. Be warned I consider myself very much a beginner/novice at all this still :)
To keep things short and sweet, I'm using Rails & active admin to build up an admin interface where i can perform CRUD operations on my database models, which is all working great. However I recently decided I wanted to add another field to one of my models, a "description" field, so generated a migration, ran rake db:migrate and updated my list of allowed params in my controller & active admin resource.
My problem is data is not saved for this new "description" field - wether its via creating a new entry or updating an existing one. I can see the output in the terminal confirms it is being filtered out by strong params; returning Unpermitted parameter: :Description However i am under the impression i have set up my strong params correctly, so I'm unsure if i have set up my permit params properly or what else i can do.
Using Rails 5.1.0 & will post code below.
class CellsController < InheritedResources::Base
def index
end
private
def cell_params
params.require(:cell).permit(:name, :description)
end
end
#database schema for my cell model
create_table "cells", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.text "Description"
end
#Active Admin resource
ActiveAdmin.register Cell do
permit_params :name, :description
end
Again, greatly appreciate any help as I'm sure I've overlooked something, happy to provide any other information that is required :)
Thankyou!
To me it looks like the description param is not accepted because the model only has a Description column (with a capitalised D). To fix that, either change each params.permit(:description) to params.permit(:Description) or just rename the column inside a new migration:
def change
rename_column :cells, :Description, :description
end
I recommend renaming the column as it will avoid any trouble with the column in the future.

Subscription based restrictions for rails queries

I have a rather complex issue that I could some assistance with, as all implementations I've been trying are flawed (Rails 4.2.1).
Users have access to devices:
create_table "devices", force: :cascade do |t|
t.string "device_guid"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_ids"
end
and devices have many data entries:
create_table "datas", force: :cascade do |t|
t.string "device_id"
t.datetime "start_time"
t.datetime "end_time"
end
We want to restrict all queries to the data based on Subscriptions.
create_table "subscriptions", force: :cascade do |t|
t.integer "device_id"
t.integer "user_id"
t.datetime "start_date"
t.datetime "end_date"
end
So effectively, data should not be accessed unless Subscriptions allows it.
I wanted to have this restriction done in one place (not by individual method to access it), so other people writing code still deal with the subscription restriction. I don't want to use a 3rd party gem as the data is very sensitive.
I thought the most obvious way to do this would be set a default_scope on the Data model, but I ran into several problems:
1) You can't access #user from the Data model. I got around this with an ugly hack to access User.current from the model.
2) I can't use current_user since we have admins that pose as other users, and need to see the data as they would. Used the same workaround as #1
3) There can be multiple subscriptions with different dates for the same user and device. There isn't any way to do an ActiveRecord Query that I know of that can account for two separate date ranges (I got around this by using arel_table).
4) This makes all of the methods written that query the data to fail when I'm in the rails console (and theoretically through an API interface). I got around this by ignoring the default scope if there is no User.current, but obviously that's a security concern.
This is my current implementation (in psuedocode):
query = []
Subscription.each do |s|
if (User.current.id == s.user_id)
temp = Data.all.where(data[:start_time] >= s.start_date).where(data[:end_time] <= s.end_date).where(data[:device_id] == Device.find_by(id: s.device_id).device_guid)
end
end
query += temp
end
default_scope { query }
I put this code just directly into the model. I didn't think it would work, but it does... sometimes. It acts oddly, sometimes completely restricting a user when it shouldn't, although it hasn't (yet) given somebody access to data they should not have.
I'm looking to redesign this from the ground up, and do it the best way. I'm open to any suggestions (except 3rd-party, sorry) on how best to approach it. I don't mind if it takes a lot of work to implement, I'd rather do it right this time. Any account for expandability (such as if we implemented an API for remote queries) would be a great feature as well, though we don't currently need it.
Just to reiterate, I insist that this restriction be "automatic" to all queries, so that if a front coder for instance writes:
<%= #user.device.find(1).data.all.count %>
in a view, it won't show any data the user doesn't have a subscription for.
Thanks for any suggestions. Sorry for the long wall of text, this has been a rather perplexing problem.

Trying to rename Devise Model name in ruby on rails

We had 2 ruby on rails apps that one was using json outputs from the other. But we decided to integrate them which ran into a lot of issues.
The issue now is, the writer of one system used to 'devise' gem for all user authentication and registration, which was fine. But when we had to integrate them it caused problems, because the other system also had a model named users, which was not used for this purpose.
And it would be A LOT easier to just rename the devise user model to something like "site-user" which im currently trying.
Than re-name the other model.
I tried renaming everything that relates to devise in all the files to siteuser instead of user but still getting errors. Anyone have any advice on this. If i can just get this devise user to use a different model name than "User" i will be able to make a lot of progress but this is proving quite the annoyance.
Current error is
NoMethodError in Devise::Sessions#new - highlighting the line with "if user_signed_in"
<li>
<%= link_to t('nav.support'), 'http://banana.sweatervest.net/' %>
</li>
<%- if user_signed_in? -%>
<%- if controller_name != 'dashboard' -%>
<li>
<%= link_to t('nav.dashboard'), :dashboard %>
Why not uninstall it and then reinstall the model.
First:
rails destroy devise User
then:
rails generate devise whateveryourmodelnamehere
Much easier than trying to rename everything, only because I've been there ;)
I actually took the long route (? 1 hour work) to rename my Devise model I had named Admin some decade ago. I wanted it to be User.
(Previous rails version 4, into my latest rails version 7, devise 4.7)
Note that you might have got yourself wrapped a lot more into the naming than I did so buyers of this solution beware! For instance I had not indexes on any of my tables (which I will do now for goodness sake). Devise itself doesn't really write the model name into stuff so it's mostly digging into your own past.
My step by step that worked for me:
I - start a new clean git branch. You will suffer unless you are able to oversee what you are doing next.
II - Renaming all cases of admin -> user. (~34 files affected)
Using search/replace (case sensitive) in my editor:
lowcases of admin -> user (also look for admins -> users that got missed)
uppercase Admin -> User
Manually rename some files like the model: admin.rb -> user.rb
III - Create some migrations to create new users table and change admin_id to user_id in those tables affected:
Take the create_table body from schema.rb and wrap that into a migration, using the new name of the devise model, mine ended up like:
class AddUsersTable < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
t.string "email", limit: 255, default: "", null: false
t.string "encrypted_password", limit: 255, default: "", null: false
t.string "reset_password_token", limit: 255
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip", limit: 255
t.string "last_sign_in_ip", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
end
end
And next a migration to change what other tables need to update the foregin key
class RenameAdminIdsToUser < ActiveRecord::Migration[5.2]
def change
rename_column(:batches, :admin_id, :user_id)
rename_column(:offices, :admin_id, :user_id)
end
end
IV - copy data from old table to the new.
In my case I'm using postgres, but the SQL will probably work with most SQL-based DBs. Here I logged into my postgres-db and run the following sql:
INSERT INTO users (SELECT * FROM admins);
(This query can also be put in a migration which is a bit cleaner.)
V - if you are setting up a new rails like me - going from rails 5 to 7, also make sure to copy settings from devise.rb, like pepper token keys to match the data you have in your old table.
After these steps my rails app was moving on with User instead of Admin as if nothing happened!

Separating multiple models in Rails form submission

Specifically, I have a form for creating a User. One of the fields is Group, which is a separate model. During the User#create action I call Group.find_or_create_by_name to check and see if the Group already exists by pulling out params[:user][:group], and create the Group if it doesn't exist.
But, when I create the User, I can't pass params[:user], because params[:user][:group] is not a group, it's a String. This would be a lot easier if I could supply params[:user] and params[:group] to my controller, instead of everything bundled under a single variable, but I don't know how to do that.
Relevant code:
User#create
#group = Group.find_or_create_by_name(params[:user][:group])
#group.save!
#user = #group.users.build(params[:user])
Partial User Schema
create_table "users", :force => true do |t|
t.string "name", :default => "", :null => false
t.string "email", :null => false
t.integer "group_id"
Partial Group Schema
create_table "groups", :force => true do |t|
t.string "name"
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
Params Dump from form submission
{"commit"=>"Register",
"authenticity_token"=>"x1KgPdpJop5H2NldsPtk0+mBDtrmpM/oNABOxjpabIU=",
"utf8"=>"✓",
"user"=>{"name"=>"aName",
"group"=>"aGroupName",
"password_confirmation"=>"[FILTERED]",
"password"=>"[FILTERED]",
"email"=>"anEmail"}}
The best explanation I've run across is this question.
I don't really want to start rearranging the params has inside of my controller as that really makes me queasy, and seems like terrible programming. Which really makes me question whether I've got a bigger problem.
If it goes against Rails conventions to do this, what then is the best way to do it, and why should I not?
I don't think the proper way to do this here is to build the user off the group. That'd be appropriate if you were in an action of the GroupsController that, for example, added new users to a group. I think the best approach here would be to do the following:
#group = Group.find_or_create_by_name(params[:user][:group])
#group.save!
#user = User.new(params[:user])
#user.group_id = #group.id
#user.save
Since you're in the new action of the UsersController, it seems more fitting to be creating a new user instead of building it off an association and then adding that user to the group that either already existed or was just created.
Does that make sense?
So, I'm going to spell out everything I've learned, in case other people are having trouble understanding this like I did. Also, if I get anything wrong here, please correct me.
If you're using a form that creates multiple model instances at once (preferably associated ones), you first need to use the helper accepts_nested_attributes_for in your model definition (probably right underneath your declared associations). The reason for this is it creates a setter method that knows how to write that type of associated model. (Note: you can also define this method yourself in your main model). Once you've done that you can nest a fields_for inside of a form_for, and Rails will know how to make the proper assignments.
I initially thought that accepts_nested_attributes_for was referring to nested resources, which is definitely not the case. If you're looking for more information, refer to section 11.8.3 (pp 343-347) of The Rails 3 Way.

Scaffolding in Rails while defining nullable fields and foreign keys

I'm just figuring out my way around rails but I need a little help with the rails generate scaffold command.
Here's the command that I'd like to use
rails generate scaffold Expense user:??? name:string description:text
I'd like the description field to be nullable and the users field to be linked to another Model — in this case I'd like to create a foreign key to the Users. I'm using the devise authentication framework.
I've read that many RoR developers try and avoid the scaffolding method and opt for the manual approach instead but my web-app is quite simple and I've thought of going the scaffolding way.
Scaffolding only generates the migration that you then run. Once the file is generated simply crack open the generated migration and adjust any of the values you need specific constraints on. By default columns are set to null unless you specify otherwise e.g.:
create_table "slugs", :force => true do |t|
t.integer "sequence", :default => 1, :null => false
t.string "sluggable_type", :limit => 40
t.string "scope", :limit => 40
t.datetime "created_at"
end
This is the code generated by the friendly_id plugin as you can see they have specified that the sequence column cannot be null while the other fields have other constraints.

Resources