Many-to-Many with ECTO and put_assoc/4 - many-to-many

I try associate 2 existing Many-to-Many records with ECTO and put_assoc/4 but won't remove elements when try update.
Basically i have projects and users . for manage the access of users to projects i have the table "user_project".
def Project do
schema "project" do
...
# users (if user_type is :admin)
many_to_many(
:users,
User,
join_through: "user_project"
)
...
end
end
def User do
schema "user" do
...
# users (if user_type is :admin)
many_to_many(
:projects,
User,
join_through: "user_project"
)
...
end
...
def changeset_insert_not_active(%User{} = user, attrs) do
user
|> cast(attrs, #required_fields)
|> put_assoc(:projects, attrs.projects)
|> validate_required(#required_fields)
|> validate_user_type()
|> validate_format(:email, ~r/#/)
|> unique_constraint(:email)
end
...
def changeset_update_projects(%User{} = user, projects) do
user
|> cast(%{}, #required_fields)
# associate projects to the user
|> put_assoc(:projects, projects)
end
...
end
def Management do
...
def create_user(attrs \\ %{}, project_ids \\ []) when is_list(project_ids) do
projects =
Project
|> where([project], project.id in ^project_ids)
|> Repo.all()
%User{}
|> User.changeset_insert(attrs
|> Map.put(:projects, projects))
|> Repo.insert()
end
...
def upsert_user_projects(user, project_ids) when is_list(project_ids) do
projects =
Project
|> where([project], project.id in ^project_ids)
|> Repo.all()
user
|> User.changeset_update_projects(projects)
|> Repo.update()
end
...
end
When i create user with list of projects is everything created but when try update the user projects removing the access to one project nothing happens ...
For example:
test "xxx" do
Management.upsert_user_projects(user, [1])
l = Management.list_user_project_by_user(user.id)
IO.puts("---------")
IO.puts("1 - length:#{length(l)}")
IO.puts("---------")
Enum.each(l, fn project ->
IO.inspect(project.project_id, label: "inserted project_id ")
end)
Management.upsert_user_projects(user, [2])
l = Management.list_user_project_by_user(user.id)
IO.puts("---------")
IO.puts("2 - length:#{length(l)}")
IO.puts("---------")
Enum.each(l, fn project ->
IO.inspect(project.project_id, label: "inserted project_id ")
end)
end
Returned values:
---------
1 - length:1
---------
inserted project_id : 1
2 - length:2
---------
inserted project_id : 1
inserted project_id : 2
Why? Why put_assoc/4 just add new elements don't remove?

#IvanYurov are right . You need to define on_replace option but also need the preloads of user updated (in every request). Basiclly for your test to be correct you need to do a user=get_user (with the preload of the projects) before Management.upsert_user_projects(user, ...).
def get_user(id) do
Repo.get(User, id)
|> Repo.preload(:projects)
end

I think you need to define on_replace option on association:
https://hexdocs.pm/ecto/Ecto.Changeset.html#module-the-on_replace-option

Related

Python errors occurring trying to insert data into a MySQL database table

I need to create an sql login/ sign up system for my program, however I keep hitting this error no matter what I do to change it. I need to have a randomly generated UTID, the users first and surname, along with a password that is verified, then the UserID is generated by taking the first three letters of the first name and the whole surname. I cant figure out how to overcome this.
I have tried to give the values inside the sql statement when inserting some literal datatypes, like writing "
c.execute('insert INTO tbl_Teachers (str(UTID), str(FName), str(SName), str(userPass), str(userID))VALUES(?,?,?,?,?);', var_insert) " but nothing seems to work.
def signup():
name = []
surname = []
print("Please enter the following details.")
user_type = str(input("Are you a teacher or a student: "))
if user_type == ("teacher") or ("Teacher"):
var_FName = str(input("First Name: "))
var_LName = str(input("Last Name: "))
var_password1 = str(input("Choose a password: "))
var_password2 = str(input("Please confirm password: "))
UTID = str(random.randint(0,100000))
print ("Your UserID is "+UTID+"")
name.append(var_FName)
surname.append(var_LName)
userID = []
for x in range (0, 3):
userID.append(var_FName[x])
for x in range (0,len(var_LName)):
userID.append(var_LName[x])
print (userID)
if var_password1 != var_password2:
print("Please try again.")
else:
var_insert = []
var_insert.append(UTID)
var_insert.append(var_FName)
var_insert.append(var_LName)
var_insert.append(str(var_password1))
var_insert.append(userID)
conn = sqlite3.connect('Program.db')
c = conn.cursor()
c.execute('insert INTO tbl_Teachers (UTID, FName, SName, userPass, userID)VALUES(?,?,?,?,?);', var_insert)
conn.commit()
InterfaceError: Error binding parameter 4 - probably unsupported type.
userID is supposed to be a string, but you're creating a list. Use string concatenation, not the append method.
userID = var_FName[0:3] + var_LName

MySQL syntax error in Ruby on Rails scope

I'm working in a RoR application using Spreecommerce, but my application is not a simple ecommerce is a plataform with many tenants, and each tenant have a single store. So, it's a single application with many stores. I'm using rails 4.2.2 and spree 3-0-stable branch.
To create my tenancy structure i'm based in this gem:
spree_multi_tenant
And using this gem too: acts_as_tenant
My code:
module SpreeMultiTenant
def self.tenanted_models
[
Spree::Address,
Spree::User,
Spree::Promotion,
Spree::PromotionRule,
Spree::PromotionAction,
Spree::PromotionActionLineItem,
...
]
end
def self.tenanted_controllers
[
Spree::BaseController,
Spree::Api::BaseController,
...
]
end
def self.with_tenant(tenant, &block)
ActsAsTenant.with_tenant tenant do
SpreeMultiTenant.init_preferences
yield
end
end
end
To controllers:
class Spree::Preferences::StoreInstance
def set_without_persist(key, value)
#cache.write(key, value)
end
end
module SpreeMultiTenant
def self.init_preferences
Spree::Preference.all.each do |preference|
Spree::Preferences::Store.instance.set_without_persist(preference.key, preference.value)
end
end
end
SpreeMultiTenant.tenanted_controllers.each do |controller|
controller.class_eval do
set_current_tenant_through_filter
prepend_around_filter :tenant_scope
private
def tenant_scope
tenant = Spree::Tenant.find_by_domain(request.host)
if tenant
set_current_tenant(tenant)
SpreeMultiTenant.with_tenant tenant do
yield
end
end
end
def set_current_tenant_through_filter
self.class_eval do
helper_method :current_tenant
private
def set_current_tenant(current_tenant_object)
ActsAsTenant.current_tenant = current_tenant_object
end
def current_tenant
ActsAsTenant.current_tenant
end
end
end
end
end
To Models:
SpreeMultiTenant.tenanted_models.each do |model|
model.class_eval do
belongs_to :tenant
acts_as_tenant :tenant
end
end
All others areas of the store it's working properly, but when the customer click to buy a product, i've got this error:
Mysql2::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'AND (spree_promotions.starts_at IS NULL OR spree_promotions.starts_at < '2016-02' at line 1: SELECT * FROM ( SELECT `spree_promotions`.* FROM `spree_promotions` INNER JOIN `spree_orders_promotions` ON `spree_promotions`.`id` = `spree_orders_promotions`.`promotion_id` WHERE `spree_promotions`.`tenant_id` = 1 AND `spree_orders_promotions`.`order_id` = 2 AND (spree_promotions.starts_at IS NULL OR spree_promotions.starts_at < '2016-02-19 19:22:01.592902') AND (spree_promotions.expires_at IS NULL OR spree_promotions.expires_at > '2016-02-19 19:22:01.593065') UNION SELECT `spree_promotions`.* FROM `spree_promotions` WHERE `spree_promotions`.`tenant_id` = AND (spree_promotions.starts_at IS NULL OR spree_promotions.starts_at < '2016-02-19 19:22:01.593151') AND (spree_promotions.expires_at IS NULL OR spree_promotions.expires_at > '2016-02-19 19:22:01.593601') AND `spree_promotions`.`code` IS NULL AND `spree_promotions`.`path` IS NULL ) `spree_promotions`
Yes, the error is here: WHERE spree_promotions.tenant_id = AND (spree_promotions.starts_at
But why my tenant_id is empty? Why my scope is not working in this case?
I saw others spree_promotions mysql queries and it's normal:
SELECT `spree_promotions`.* FROM `spree_promotions` WHERE `spree_promotions`.`tenant_id` = 1 AND `spree_promotions`.`id` = 1 LIMIT 1
So i have no ideia to fix it :(

How to find a rails object based on a date value

I have a User object and I am attempting to do 2 different queries as part of a script that needs to run nightly. Given the schema below I would like to:
Get all the Users with a non nil end_date
Get all the Users with an end_date that is prior to today (I.E. has passed)
Users Schema:
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# name :string(100) default("")
# end_date :datetime
I've been trying to use User.where('end_date != NULL) and other things but I cannot seem to get the syntax correct.
Your methods should be as below inside the User model :
def self.users_with_end_date_not_null
self.where.not(end_date: nil)
# below Rails 4 use
# self.where("end_date != ?", nil)
end
def self.past_users n
self.where(end_date: n.day.ago)
end

Need array of self, children and children childrens in rails

I have a table like following
well,here in this table every user has a parent user,then if we select a user then its id ,children ids and children childrens ids should return as array.I need a query to get this values in rails with out using any gem.Thanx for your help:->
class User << ActiveRecord::Base
def self.build_tree(reverse=false)
g = Array.new
self.find(:all).each do |p|
if reverse
g << [p.id,p.parent]
else
g << [p.parent,p.id]
end
end
g
end
def self.subordinates(man_id)
g = self.build_tree false
g.map{|i| i[1] if i[0]==man_id.to_i}.compact
end
def self.superiors(user_id)
g = self.build_tree true
g.map{|i| i[1] if i[0]==user_id.to_i}.compact
end
end
When call either Superiors(parents) or Subordinates(childrends) it will gives required result
Ex:- [2,4,6,8]
If you want to get either children->childrends or parent->parents just do iterate call function either superior or subordinates until get the nil or [] array .
You are stranding into SQL anti-pattern. Performing operations on trees constructed like that is very inefficient. I don't say, that you should use a gem for that, but consider using some smarter method of keeping this data (searching for sql tree structure should yield some meaningful results).
Query you are looking for needs two self joins:
SELECT t1.id user_ids, t2.id children_ids, t3.id children_children_ids FROM users t1
LEFT JOIN users t2 ON t2.parent = t1.id
LEFT JOIN users t3 ON t3.parent = t2.id
On the other hand, if your rails models have defined self-relation, you could easily write:
user.children #=> (array of children)
user.children.flat_map(&:children) #=> (array of grandchildren)
Definition of this relation should look like:
class User << ActiveRecord::Base
has_many :children, class_name: User, foreign_key: 'parent'
end

Fetch all table name and row count for specific table with Rails?

How can i fetch all the table name and row count for the specific table from the specific database ?
Result
Table Name , Row Count , Table Size(MB)
---------------------------------------
table_1 , 10 , 2.45
table_2 , 20 , 4.00
ActiveRecord::Base.connection.tables.each do |table|
h = ActiveRecord::Base.connection.execute("SHOW TABLE STATUS LIKE '#{table}'").fetch_hash
puts "#{h['Name']} has #{h['Rows']} rows with size: #{h['Data_length']}"
end
The question is tagged mysql but you can do it in a DB-agnostic manner via ORM.
class DatabaseReport
def entry_counts
table_model_names.map do |model_name|
entity = model_name.constantize rescue nil
next if entity.nil?
{ entity.to_s => entity.count }
end.compact
end
private
def table_model_names
ActiveRecord::Base.connection.tables.map(&:singularize).map(&:camelize)
end
end
Note that this will skip tables for which you don't have an object mapping such as meta tables like ar_internal_metadata or schema_migrations. It also cannot infer scoped models (but could be extended to do so). E.g. with Delayed::Job I do this:
def table_model_names
ActiveRecord::Base.connection.tables.map(&:singularize).map(&:camelize) + ["Delayed::Job"]
end
I came up with my own version which is also db agnostic.
As it uses the descendants directly it also handles any tables where the table_name is different to the model name.
The rescue nil exists for cases when you have the class that inherits from ActiveRecord but for some reason don't have a table associated with it. It does give data for STI classes and the parent class.
my_models = ActiveRecord::Base.descendants
results = my_models.inject({}) do |result, model|
result[model.name] = model.count rescue nil
result
end
#temp_table = []
ActiveRecord::Base.connection.tables.each do |table|
count = ActiveRecord::Base.connection.execute("SELECT COUNT(*) as count FROM #{table}").fetch_hash['count']
size = ActiveRecord::Base.connection.execute("SHOW TABLE STATUS LIKE '#{table}'").fetch_hash
#temp_table << {:table_name => table,
:records => count.to_i,
:size_of_table => ((BigDecimal(size['Data_length']) + BigDecimal(size['Index_length']))/1024/1024).round(2)
}
end
end