One to Many Relationship between receipt and subject - model-associations

I am working on a school project and i'm new to ROR. I have two objects, Receipts and Subjects. The receipts have one subject and the subjects are reused among the receipts. I don't have a problem if I have equal number of subjects to receipts, but when I don't create a new subject for a receipt I get this error
The problem here is I only have two subjects in the database with 3 receipts. So an id of 3 is invalid. My models, views, controllers, and migrations are follow.
Models:
class Receipt < ActiveRecord::Base belongs_to :user has_one :subject, :foreign_key => :id validates :user_id, presence:true validates :descript, presence: true, length: { minimum: 4, maximum: 120 } end
class Subject < ActiveRecord::Base has_many :receipts validates :subject_name, presence: true, length: { minimum: 2, maximum: 30 } validates :description, presence: true, length: { minimum: 2, maximum: 200 } end
Controller:
def index #receipts = Receipt.paginate(page: params[:page], per_page: 4).order("updated_at DESC") end
Migration:
class CreateReceipts < ActiveRecord::Migration def change create_table :receipts do |t| t.datetime :date t.decimal :tax_amount, :purchase_amount, precision: 5, scale: 2 t.boolean :approved, default: false t.text :descript t.integer :user_id, :subject_id, t.timestamps end end end
class CreateSubjects < ActiveRecord::Migration def change create_table :subjects do |t| t.string :subject_name t.text :description t.timestamps end end end
View:
I think my migration and views are fine since it works when I have equal subjects to receipts,

Related

Express validator add a simple if-else structure

I have a field that can have 0 or any number. If it has 0 I want to do a validation, if it doesn't then I want to do other. How can I do that? This was my attempt.
app.post("/admin/updskill", [
body('i_sCatEdit')
.notEmpty().withMessage('The category id can not be empty.').bail()
.isNumeric({ no_symbols: true }).bail()
.trim().escape(),
body('sz_sCatEdit')
.if(body('i_sCatEdit').equals(0))
.isEmpty().withMessage('The category name must be empty.').bail().escape(),
body('sz_sCatEdit')
.if(body('i_sCatEdit').not().equals(0))
.notEmpty().withMessage('The category name can not be empty.').bail()
.exists({ checkNull: true, checkFalsy: true }).withMessage('The category name must exist (eg: not undefined, null).').bail()
.isLength({ min: 3, max: 149 }).withMessage('The category name min length is 3 characters and the max 149.').bail().escape()
In case the above code is not clear enough, this is pseudo code
if(i_sCatEdit === 0){
validation1
} else{
validation2
}

How to unwind data held in edges with a "common neighbors" style query?

I have a simple model with a single A Document collection
[{ _key: 'doc1', id: 'a/doc1', name: 'Doc 1' }, { _key: 'doc2', id: 'a/doc2', name: 'Doc 2' }]
and a single B Edge collection, joining documents A with an held weight integer on each edge.
[{ _key: 'xxx', id: 'b/xxx', _from: 'a/doc1', _to: 'a/doc2', weight: 256 }]
I'm trying to make a "common neighbors" style query, that takes 2 document as an input, and yields common neighbors of those inputs, along with respective weights (of each side).
For example with doc1 and doc26 input, here is the goal to achieve :
[
{ _key: 'doc6', weightWithDoc1: 43, weightWithDoc26: 57 },
{ _key: 'doc12', weightWithDoc1: 98, weightWithDoc26: 173 },
{ _key: 'doc21', weightWithDoc1: 3, weightWithDoc26: 98 },
]
I successfully started by targeting a single side :
FOR associated, association
IN 1..1
ANY ${d1}
${EdgeCollection}
SORT association.weight DESC
LIMIT 20
RETURN { _key: associated._key, weight: association.weight }
Then successfully went on with the INTERSECTION logic of the documentation
FOR proj IN INTERSECTION(
(FOR associated, association
IN 1..1
ANY ${d1}
${EdgeCollection}
RETURN { _key: associated._key }),
(FOR associated, association
IN 1..1
ANY ${d2}
${EdgeCollection}
RETURN { _key: associated._key })
)
LIMIT 20
RETURN proj
But I'm now struggling at extracting the weight of each side, as unwinding it on the inner RETURN clauses will make them exclusive on the intersection; thus returning nothing.
Questions :
Is there any way to make some kind of "selective INTERSECTION", grouping some fields in the process ?
Is there an alternative to INTERSECTION to achieve my goal ?
Bonus question :
Ideally, after successfully extracting weightWithDoc1 and weightWithDoc26, I'd like to SORT DESC by weightWithDoc1 + weightWithDoc26
I managed to find an acceptable answer myself
FOR associated IN INTERSECTION(
(FOR associated
IN 1..1
ANY ${doc1}
${EdgeCollection}
RETURN { _key: associated._key }),
(FOR associated
IN 1..1
ANY ${doc2}
${EdgeCollection}
RETURN { _key: associated._key })
)
LET association1 = FIRST(FOR association IN ${EdgeCollection}
FILTER association._from == CONCAT(${DocCollection.name},'/',MIN([${doc1._key},associated._key])) AND association._to == CONCAT(${DocCollection.name},'/',MAX([${doc1._key},associated._key]))
RETURN association)
LET association2 = FIRST(FOR association IN ${EdgeCollection}
FILTER association._from == CONCAT(${DocCollection.name},'/',MIN([${doc2._key},associated._key])) AND association._to == CONCAT(${DocCollection.name},'/',MAX([${doc2._key},associated._key]))
RETURN association)
SORT (association1.weight+association2.weight) DESC
LIMIT 20
RETURN { _key: associated._key, weight1: association1.weight, weight2: association2.weight }
I believe re-selecting after intersecting is not ideal and not the most performant solution, so I'm leaving it open for now to wait for a better answer.

Joining same table multiple times with Sequelize

I have the following models:
const User = Sequelize.define('user', {
login: Sequelize.DataTypes.STRING,
password: Sequelize.DataTypes.STRING,
is_manager: Sequelize.DataTypes.BOOLEAN,
notes: Sequelize.DataTypes.STRING
});
const Bike = Sequelize.define('bike', {
model: Sequelize.DataTypes.STRING,
photo: Sequelize.DataTypes.BLOB,
color: Sequelize.DataTypes.STRING,
weight: Sequelize.DataTypes.FLOAT,
location: Sequelize.DataTypes.STRING,
is_available: Sequelize.DataTypes.BOOLEAN
});
const Rate = Sequelize.define('rate', {
rate: Sequelize.DataTypes.INTEGER
});
Rate.belongsTo(User);
User.hasMany(Rate);
Rate.belongsTo(Bike);
Bike.hasMany(Rate);
And I'd like to select bikes with their average rates, plus rates of the current user for each bike:
Bike.findAll({
attributes: {include: [[Sequelize.fn('AVG', Sequelize.col('rates.rate')), 'rate_avg']],
},
include: [{
model: Rate,
attributes: []
}, {
model: Rate,
attributes: ['rate'],
include: [{
model: User,
attributes: [],
where: {
login: req.user.login
}
}]
}],
group: Object.keys(Bike.rawAttributes).map(key => 'bike.' + key) // group by all fields of Bike model
})
It constructs the following query: SELECT [bike].[id], [bike].[model], [bike].[photo], [bike].[color], [bike].[weight], [bike].[location], [bike].[is_available], AVG([rates].[rate]) AS [rate_avg], [rates].[id] AS [rates.id], [rates].[rate] AS [rates.rate] FROM [bikes] AS [bike] LEFT OUTER JOIN [rates] AS [rates] ON [bike].[id] = [rates].[bikeId] LEFT OUTER JOIN ( [rates] AS [rates] INNER JOIN [users] AS [rates->user] ON [rates].[userId] = [rates->user].[id] AND [rates->user].[login] = N'user' ) ON [bike].[id] = [rates].[bikeId] GROUP BY [bike].[id], [bike].[model], [bike].[photo], [bike].[color], [bike].[weight], [bike].[location], [bike].[is_available];
And fails: SequelizeDatabaseError: The correlation name 'rates' is specified multiple times in a FROM clause.
How do I write the query right? I need Sequelize to assign another alias to the rates table used in the 2nd join (and add its columns to the GROUP BY clause, but that's the next step).
You can do multiple inner joins with same table by adding extra same association with that model but with a different alias that is as: 'alias1' , as: 'alias2' ,... - all this existing with the same model + same type of association.
Also posted this solution at github issue: https://github.com/sequelize/sequelize/issues/7754#issuecomment-783404779
E.g. for Chats that have many Receiver
Associations (Duplicating for as many needed)
Chat.hasMany(Receiver, {
// foreignKey: ...
as: 'chatReceiver',
});
Chat.hasMany(Receiver, {
// foreignKey: ...
as: 'chatReceiver2',
});
Now you are left to include associated model multiple times all with different alias so it does not gets overridden.
So you can use them in query as below:
Chat.findAll({
attributes: ["id"],
include: [{
required: true,
model: Receiver,
as: 'chatReceiver', // Alias 1
attributes: [],
where: { userID: 1 }, // condition 1
}, {
required: true,
model: Receiver,
as: 'chatReceiver2', // Alias 2
attributes: [],
where: { userID: 2 }, // condition 2 as needed
}]
});
Solution :
Bike.findAll({
attributes: {include: [[Sequelize.fn('AVG', Sequelize.col('rates.rate')), 'rate_avg']],
},
include: [{
model: Rate,
attributes: []
}, {
model: Rate,
required : false , // 1. just to make sure not making inner join
separate : true , // 2. will run query separately , so your issue will be solved of multiple times
attributes: ['rate'],
include: [{
model: User,
attributes: [],
where: {
login: req.user.login
}
}]
group : [] // 3. <------- This needs to be managed , so please check errors and add fields as per error
}],
group: Object.keys(Bike.rawAttributes).map(key => 'bike.' + key) // group by all fields of Bike model
})
NOTE : READ THE COMMENTS
Sequelize doesn't support including through the same association twice (see here, here, and here). At the model level, you can define 2 different associations between Bike and Rate, but having to change the model, adding new foreign keys etc, is a very hacky solution.
Incidentally, it wouldn't solve your other problem, which is that you're grouping only by Bike but then want to select the user's rate. To fix that, you'd also have to change your grouping to include the user rates. (Note that if a user has more than 1 rate per bike, that might also create some inefficiency, as the rates for the bike are averaged repeatedly for each of the user's rates.)
A proper solution would be using window functions, first averaging the rates per bike and then filtering out all the rates not belonging to the logged in user. Might look something like this:
SELECT *
FROM (
SELECT bike.*,
users.login AS user_login,
AVG (rates.rate) OVER (PARTITION BY bike.id) AS rate_avg
FROM bike
INNER JOIN rates ON rates.bikeId = bike.id
INNER JOIN users ON rates.userId = users.id
)
WHERE user_login = :req_user_login
Unfortunately, as far as I'm aware sequelize doesn't currently support subqueries in the FROM clause and using window functions in this way, so you'd have to fall back to a raw query.

Sequelize move JavaScript logic to the DB?

An Organization can have multiple Grounds, and the number of grounds that are available can vary on daily basis.
The user should get a list of all the Grounds, irrespective of the fact whether they are available during the given/specified dates.
The thing which will be displayed to the user, is that the certain ground is not available in the given dates.
Denoted by rest["available"] in the Code.
So I am doing this work manually in javascript, can not I some how shift the javascript logic to the
Sequelize as well? so that it returns me the "available" status as well as "average" in the response.
Actually, I have large amount of data, and I believe looping in the JS code is not an efficient way of doing it.
Therefore, I believe the logic must be moved to the Data Base, but I am unsure about doing it using Sequelize.
The database Tables, their relationship & JS code are given below.
Organization.model.js
module.exports = function(sequelize, DataTypes) {
let Organization = sequelize.define('Organization', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
unique: true,
allowNull: false
},
name: {
type: DataTypes.STRING,
primaryKey: true,
}
}, {
timestamps: false,
freezeTableName: true,
tableName: "Organization",
underscored: false
});
Organization.associate = function(models) {
Organization.hasMany(models.Grounds, {
onDelete: 'cascade',
hooks: true,
foreignKey: 'OrganizationName',
sourceKey: 'name'
});
};
return Organization;
};
Grounds.model.js
module.exports = function(sequelize, DataTypes) {
let Grounds = sequelize.define('Grounds', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
OrganizationName: {
type: DataTypes.STRING,
references: {
model: 'Organization',
key: 'name'
}
},
NumbersAvailable: {
type: DataTypes.INTEGER
},
Date: DataTypes.DATEONLY
}, {
timestamps: false,
freezeTableName: true,
tableName: "Grounds",
underscored: false
});
Grounds.associate = function(models) {
Grounds.belongsTo(models.Organization, {
foreignKey: 'OrganizationName',
targetKey: 'name'
});
};
return Grounds;
};
JavaScript Logic:
//Get all the Grounds, in the specified Dates, e.g: '2018-05-01' & '2018-05-04'
let organizations = await Organization.findAll({
include: [
model: Grounds,
where: {
Date: {
$gte: '2018-05-01',
$lte: '2018-05-04'
}
}
]
});
//Calculate the Grounds Availability, in the specified Dates, e.g: '2018-05-01' & '2018-05-04'
let finalResult = organizations.map(function(currVal){
let organization = currVal.dataValues;
let {Grounds, ...rest} = organization;
rest["available"] = true; //Custom Key.
rest["average"] = 0; //Custom Key.
Grounds.forEach(function(ground){
let Date = ground.Date;
rest["average"] += ground.NumbersAvailable;
let number = ground.NumbersAvailable;
if(number == 0) rest["available"] = false;
});
rest["average"] = rest["average"]/Grounds.length;
});
Sample Table Data:
Organization TABLE:
id name
---------------------------
1 authority1
2 authority2
3 authority3
Grounds TABLE:
id NumbersAvailable OrganizationName Date GroundName
----------------------------------------------------------------------------
1 5 authority1 2018-05-01 someName
2 3 authority1 2018-05-02 someName
3 6 authority1 2018-05-03 someName
4 2 authority1 2018-05-04 someName
5 7 authority2 2018-05-01 someName
6 3 authority2 2018-05-02 someName
7 0 authority2 2018-05-03 someName
8 1 authority2 2018-05-04 someName
9 2 authority3 2018-05-01 someName
10 1 authority3 2018-05-02 someName
11 3 authority3 2018-05-03 someName
12 1 authority3 2018-05-04 someName
The way you'd move your logic back into the data layer is by using a view or database function (links to Postgres docs; other databases may use "procedure" instead of "function"). Since your specific case here involves running some simple calculations on all organizations and their grounds, a view on a query that joins those two tables with GROUP BY and some aggregation should suffice. You can treat the view as a read-only model as far as Sequelize is concerned; per this issue, the only catch is that you can't sync it.
If you're doing something more complex, you'll need a function, and this is where things start to get hairy. How you implement a function depends on the dialect of SQL you're using, since each database has its own linguistic quirks (SQL Server uses SELECT TOP n instead of SELECT .... LIMIT n, and so on). Here's where you run into the first potential issue -- you're now locked into that one database server, be it Postgres, SQL Server, or what have you. If you want to support another database, you'll need to port your function into its dialect.
The second problem with using a function is that, to my knowledge, Sequelize doesn't provide an easy way to execute database function which return tabular results -- the reasoning being that it, like other object-relational mappers, abstracts specificities such as dialect away and only provides an interface for things you can write the basic suite of SELECT, INSERT, etc statements against. Functions are therefore ignored. In order to execute one, you'll need to use its raw query functionality.

Rails 4, nested attributes, Couldn't find Tag with ID= for Person with ID=

Rails 4:
I want to create Person with tags
person = Person.new(:name=>Jon', :tags_attributes=>[{:id=>'15', :name=>'some_tag'}])
My Person model:
class Person < ActiveRecord::Base
validates :name, presence: true
belongs_to :user
has_many :organizations, through: :people_organizations
has_and_belongs_to_many :tags, join_table: :people_tags
has_many :phones, as: :phoneable, :dependent => :destroy
has_many :emails, as: :emaileable, :dependent => :destroy
has_many :networks, as: :networkable, :dependent => :destroy
has_many :messengers, as: :messengerable, :dependent => :destroy
accepts_nested_attributes_for :phones, :emails, :networks, :messengers, allow_destroy: true
accepts_nested_attributes_for :tags, reject_if: :all_blank
end
My PeopleController:
def create
#person = Person.new(person_params)
respond_to do |format|
if #person.save(validate: false)
format.json { render action: 'show', status: :created }
else
format.json { render json: #person.errors, status: :unprocessable_entity }
end
end
end
def person_params
params.require(:person).permit(:id, :name, :born, :description,
phones_attributes:[:id, :number, :_destroy],
emails_attributes:[:id, :email, :_destroy]
networks_attributes:[:id, :name, :_destroy],
messengers_attributes:[:id, :identifier, :_destroy],
tags_attributes:[:id, :name, :_destroy]
)
end
When i create new person, i have error
p = Person.new(:name=>'Jon', :tags_attributes=>[{:id=>'15', :name=>'tag'}])
Couldn't find Tag with ID=15 for Person with ID=
Please tell me what to do to keep the model
I got the same problem. I think that rails just don't support creating a new record with an existing nested record. I found no solution for this situation at all.
So Try to filter tags_attributes from person_params and then use tag_ids like this:
tag_ids = params[:person][:tags_attributes].map { |tag| tag[:id] }
#person = Person.new(person_params.merge({ tag_ids: tag_ids })
You should create the nested fields through of people_tags relationship, for example something like that
= form_for(#person) do |f|
= f.text_field :name
= f.simple_fields_for :people_tags do |people_tag_builder|
people_tag_builder.hidden_field :tag_id 15
= people_tag_builder.simple_fields_for :tags, user do |tag_builder|
= tag_builder.id 15
= tag_builder.text_field :name, value: 'tag'
the people_tag_builder.hidden_field :tag_id 15 is mandatory because it the middle table needs either person_id or tag_id how you are creating a person, you must send a tag object or create it or both

Resources