I have an array of rake routes, and I am trying to extract all of the routes which have route.conditions[:request_method] as GET.
Problems:
:request_method is a regex (:request_method=>/^GET$/)
> routes.select { |route| route.conditions[:request_method] == /GET/ }
> []
I figured my select was correct. This works, and outputs all route methods:
> routes.each { |route| print route.conditions[:request_method] }
> {:request_method=>/^GET$/}{:request_method=>/^GET$/}{:request_method=>/^PUT$/}{:request_method=>/^GET$/}{:request_method=>/^PUT$/}{:request_method=>/^POST$/}{:request_method=>/^GET$/}{:request_method=>/^GET$/}
Any ideas how I might achieve this?
Found my answer. routes.each was returning the Journey object, so I first created my own custom hash:
routes = Rails.application.routes.routes.to_a
routes = routes.collect { |route| {name: route.name, method: route.verb} }
routes = routes.select { |route| route[:method] == /^GET$/ }
I'm sure this can be more elegantly reworked so I'm open to changes. It works, that's the most important part.
Related
I am having multiple nested where conditions and want to generate them without too much code duplication with typeORM.
The SQL where condition should be something like this:
WHERE "Table"."id" = $1
AND
"Table"."notAvailable" IS NULL
AND
(
"Table"."date" > $2
OR
(
"Table"."date" = $2
AND
"Table"."myId" > $3
)
)
AND
(
"Table"."created" = $2
OR
"Table"."updated" = $4
)
AND
(
"Table"."text" ilike '%search%'
OR
"Table"."name" ilike '%search%'
)
But with the FindConditions it seems not to be possible to make them nested and so I have to use all possible combinations of AND in an FindConditions array. And it isn't possible to split it to .where() and .andWhere() cause andWhere can't use an Object Literal.
Is there another possibility to achieve this query with typeORM without using Raw SQL?
When using the queryBuilder I would recommend using Brackets
as stated in the Typeorm doc: https://typeorm.io/#/select-query-builder/adding-where-expression
You could do something like:
createQueryBuilder("user")
.where("user.registered = :registered", { registered: true })
.andWhere(new Brackets(qb => {
qb.where("user.firstName = :firstName", { firstName: "Timber" })
.orWhere("user.lastName = :lastName", { lastName: "Saw" })
}))
that will result with:
SELECT ...
FROM users user
WHERE user.registered = true
AND (user.firstName = 'Timber' OR user.lastName = 'Saw')
I think you are mixing 2 ways of retrieving entities from TypeORM, find from the repository and the query builder. The FindConditions are used in the find function. The andWhere function is use by the query builder. When building more complex queries it is generally better/easier to use the query builder.
Query builder
When using the query build you got much more freedom to make sure the query is what you need it to be. With the where you are free to add any SQL as you please:
const desiredEntity = await connection
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id", { id: 1 })
.andWhere("user.date > :date OR (user.date = :date AND user.myId = :myId)",
{
date: specificCreatedAtDate,
myId: mysteryId,
})
.getOne();
Note that depending on your used database the actual SQL that you use here needs to be compatible. With that could also come a possible draw back of using this method. You will tie your project to a specific database. Make sure to read up about the aliases for tables you can set if you are using relations this would be handy.
Repository
You already saw that this is much less comfortable. This is because the find function or more specific the findOptions are using objects to build the where clause. This makes is harder to implement a proper interface to implement nested AND and OR clauses side by side. There for (I assume) they have chosen to split AND and OR clauses. This makes the interface much more declarative and means the you have to pull your OR clauses to the top:
const desiredEntity = await repository.find({
where: [{
id: id,
notAvailable: Not(IsNull()),
date: MoreThan(date)
},{
id: id,
notAvailable: Not(IsNull()),
date: date
myId: myId
}]
})
I cannot imagin looking a the size of the desired query that this code would be very performant.
Alternatively you could use the Raw find helper. This would require you to rewrite your clause per field, since you will only get access to the one alias at a time. You could guess the column names or aliases but this would be very poor practice and very unstable since you cannot directly control this easily.
if you want to nest andWhere statements if a condition is meet here is an example:
async getTasks(filterDto: GetTasksFilterDto, user: User): Promise<Task[]> {
const { status, search } = filterDto;
/* create a query using the query builder */
// task is what refer to the Task entity
const query = this.createQueryBuilder('task');
// only get the tasks that belong to the user
query.where('task.userId = :userId', { userId: user.id });
/* if status is defined then add a where clause to the query */
if (status) {
// :<variable-name> is a placeholder for the second object key value pair
query.andWhere('task.status = :status', { status });
}
/* if search is defined then add a where clause to the query */
if (search) {
query.andWhere(
/*
LIKE: find a similar match (doesn't have to be exact)
- https://www.w3schools.com/sql/sql_like.asp
Lower is a sql method
- https://www.w3schools.com/sql/func_sqlserver_lower.asp
* bug: search by pass where userId; fix: () whole addWhere statement
because andWhere stiches the where class together, add () to make andWhere with or and like into a single where statement
*/
'(LOWER(task.title) LIKE LOWER(:search) OR LOWER(task.description) LIKE LOWER(:search))',
// :search is like a param variable, and the search object is the key value pair. Both have to match
{ search: `%${search}%` },
);
}
/* execute the query
- getMany means that you are expecting an array of results
*/
let tasks;
try {
tasks = await query.getMany();
} catch (error) {
this.logger.error(
`Failed to get tasks for user "${
user.username
}", Filters: ${JSON.stringify(filterDto)}`,
error.stack,
);
throw new InternalServerErrorException();
}
return tasks;
}
I have a list of
{
date: specificCreatedAtDate,
userId: mysteryId
}
My solution is
.andWhere(
new Brackets((qb) => {
qb.where(
'userTable.date = :date0 AND userTable.type = :userId0',
{
date0: dates[0].date,
userId0: dates[0].type,
}
);
for (let i = 1; i < dates.length; i++) {
qb.orWhere(
`userTable.date = :date${i} AND userTable.userId = :userId${i}`,
{
[`date${i}`]: dates[i].date,
[`userId${i}`]: dates[i].userId,
}
);
}
})
)
That will produce something similar
const userEntity = await repository.find({
where: [{
userId: id0,
date: date0
},{
id: id1,
userId: date1
}
....
]
})
This question is about how to perform a task using RamdaJS.
First, assume I have an object with this structure:
let myObj = {
allItems: [
{
name: 'firstthing',
args: [
{
name: 'arg0'
},
{
name: 'arg1'
}
],
type: {
name: 'type_name_1'
}
},
{
name: 'otherthing',
args: [
{
name: 'arg0'
}
]
}
]
}
I am trying to create an object that looks like:
{
arg0: 'arg0', // myObj.allItems[0].args[0].name
typeName: 'type_name_1' // myObj.allItems[0].type.name
}
(I know the names are stupid, arg0, typeName. It's not important)
So if we weren't using Ramda, this is how I'd do it imperatively:
// The thing I'm searching for in the array (allItems)
let myName = 'firstthing';
// Here's how I'd find it in the array
let myMatch = myObj.allItems.find(item => item.name === myName);
// Here is the desired result, by manually using dot
// notation to access properties on the object (non-functional)
let myResult = {
arg0: myMatch.args[0].name,
typeName: myMatch.type.name
};
// Yields: {"arg0":"arg0","typeName":"type_name_1"}
console.log(myResult)
Finally, just for good measure, this is as far as I've gotten so far. Note that, I'd really like to accomplish this in a single compose/pipe.
(An object goes in, and an object with the desired data comes out)
const ramdaResult = R.compose(
R.path(['type', 'name']),
R.find(
R.propEq('name', myName)
)
)(R.prop('allItems', myObj))
Thanks
A combination of applySpec and path should work:
const transform = applySpec ({
arg0: path (['allItems', 0, 'args', 0, 'name']),
typeName: path (['allItems', 0, 'type', 'name'])
})
const myObj = {allItems: [{name: 'firstthing', args: [{name: 'arg0'}, {name: 'arg1'}], type: {name: 'type_name_1'}}, {name: 'otherthing', args: [{name: 'arg0'}]}]}
console .log (
transform (myObj)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {applySpec, path} = R </script>
But depending upon your preferences, a helper function might be useful to make a slightly simpler API:
const splitPath = useWith (path, [split('.'), identity] )
// or splitPath = curry ( (str, obj) => path (split ('.') (str), obj))
const transform = applySpec({
arg0: splitPath('allItems.0.args.0.name'),
typeName: splitPath('allItems.0.type.name'),
})
const myObj = {allItems: [{name: 'firstthing', args: [{name: 'arg0'}, {name: 'arg1'}], type: {name: 'type_name_1'}}, {name: 'otherthing', args: [{name: 'arg0'}]}]}
console .log (
transform (myObj)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {applySpec, path, useWith, split, identity} = R </script>
splitPath is not appropriate for Ramda, but it's a useful function I often include, especially if the paths are coming from a source outside my control.
Update
Yes, I did miss that requirement. Serves me right for looking only at the input and the requested output. There's always multiple incompatible algorithms that give the same result for a specific input. So here's my mea culpa, an attempt to break this into several reusable functions.
Lenses are probably your best bet for this. Ramda has a generic lens function, and specific ones for an object property (lensProp), for an array index(lensIndex), and for a deeper path(lensPath), but it does not include one to find a matching value in an array by id. It's not hard to make our own, though.
A lens is made by passing two functions to lens: a getter which takes the object and returns the corresponding value, and a setter which takes the new value and the object and returns an updated version of the object.
An important fact about lenses is that they compose, although for technical reasons the order in which you supply them feels opposite to what you might expect.
Here we write lensMatch which find or sets the value in the array where the value at a given path matches the supplied value. And we write applyLensSpec, which acts like applySpec but takes lenses in place of vanilla functions.
Using any lens, we have the view, set, and over functions which, respectively, get, set, and update the value. Here we only need view, so we could theoretically make a simpler version of lensMatch, but this could be a useful reusable function, so I keep it complete.
const lensMatch = (path) => (key) =>
lens
( find ( pathEq (path, key) )
, ( val
, arr
, idx = findIndex (pathEq (path, key), arr)
) =>
update (idx > -1 ? idx : length (arr), val, arr)
)
const applyLensSpec = (spec) => (obj) =>
map (lens => view (lens, obj), spec)
const lensName = (name) => lensMatch (['name']) (name)
const transform = (
name,
nameLens = compose(lensProp('allItems'), lensName(name))
) => applyLensSpec({
arg0: compose (nameLens, lensPath (['args', 0, 'name']) ),
typeName: compose (nameLens, lensPath (['type', 'name']) )
})
const myObj = {allItems: [{name: 'firstthing', args: [{name: 'arg0'}, {name: 'arg1'}], type: {name: 'type_name_1'}}, {name: 'otherthing', args: [{name: 'arg0'}]}]}
console .log (
transform ('firstthing') (myObj)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {lens, find, pathEq, findIndex, update, length, map, view, compose, lensProp, lensPath} = R </script>
While this may feel like more work than some other solutions, the main function, transform is pretty simple, and it's obvious how to extend it with additional behavior. And lensMatch and applyLensSpec are genuinely useful.
I've been trying to structure the relations in my database for more efficient querying and joins but after following the guides for '.belongsToMany', '.through' and '.belongsTo' I'm now getting empty results.
I've got a Sound model and a Keyword model which I want to model with a many-to-many relationship (each Sound can have multiple Keywords, and each Keyword can be related to multiple sounds). Based on the documentation '.belongsToMany' would be the relation to use here.
I've set up my models as follows, using a 'sound_keyword' relational table/SoundKeyword relational model (where each entry has it's own unique 'id', a 'soundID', and a 'keywordID'):
var Sound = bookshelf.Model.extend({
tableName: 'sounds',
keywords: function () {
return this.belongsToMany(Keyword, 'sound_keyword', 'id', 'id').through(SoundKeyword, 'id', 'soundID');
},
});
var Keyword = bookshelf.Model.extend({
tableName: 'keywords',
sounds: function () {
return this.belongsToMany(Sound, 'sound_keyword', 'id', 'id').through(SoundKeyword, 'id', 'keywordID');
}
});
where:
var SoundKeyword = bookshelf.Model.extend({
tableName: 'sound_keyword',
sound: function () {
return this.belongsTo(Sound, 'soundID');
},
keyword: function () {
return this.belongsTo(Keyword, 'keywordID');
}
});
From what I've read in the docs and the BookshelfJS GitHub page the above seems to be correct. Despite this when I run the following query I'm getting an empty result set (the Sound in question is related to 3 Keywords in the DB):
var results = await Sound
.where('id', soundID)
.fetch({
withRelated: ['keywords']
})
.then((result) => {
console.log(JSON.stringify(result.related('keywords')));
})
Where am I going wrong with this? Are the relationships not set up correctly (Possibly wrong foreign keys?)? Am I fetching related models incorrectly?
Happy to provide the Knex setup as needed.
UPDATED EDIT:
I had been using the Model-Registry Plugin from the start and had forgotten about it. As it turns out, while the below syntax is correct, it prefers syntax similar to the following (i.e. lowercase 'model', dropping the '.extends' and putting model names in quotes):
var Sound = bookshelf.model('Sound',{
tableName: 'sounds',
keywords: function () {
return this.belongsToMany('Keyword', 'sound_keyword', 'soundID', 'keywordID');
},
});
var Keyword = bookshelf.model('Keyword',{
tableName: 'keywords',
sounds: function () {
return this.belongsToMany('Sound', 'sound_keyword', 'keywordID', 'soundID');
}
});
Hope this can be of help to others.
Seems like removing the '.through' relation and changing the IDs in the '.belongsToMany' call did the trick (as below), though I'm not entirely sure why (the docs seem to imply belongsToMany and .through work well together - possibly redundant?)
var Sound = bookshelf.Model.extend({
tableName: 'sounds',
keywords: function () {
return this.belongsToMany(Keyword, 'sound_keyword', 'soundID', 'keywordID');
},
});
var Keyword = bookshelf.Model.extend({
tableName: 'keywords',
sounds: function () {
return this.belongsToMany(Sound, 'sound_keyword', 'keywordID', 'soundID');
}
});
I did try my original code with soundID and keywordId instead of 'id' (as below), but without the .through relation and that gave the same empty results.
I'm having trouble getting the correct query with sequelize.
I have an array representing ids of entries lets say its like this -
userVacationsIds = [1,2,3]
i made the first query like this
Vacation.findAll({
where: {
id: {
[Op.or]: userVacationsIds
}
}
})
.then(vacationSpec => {
Vacation.findAll({
where:{
//Here i need to get all entries that DONT have the ids from the array
}
}
})
I can't get the correct query as specified in my code "comment"
I've tried referring to sequelize documentation but i can't understand how to chain these queries specifically
Also tried an online converter but that failed too.
Specified the code i have above
So i just need some help getting this query correct please.
I eventually expect to get 2 arrays - one containing all entries with the ids from the array, the other containing everything else (as in id is NOT in the array)
I figured it out.
I feel silly.
This is the query that worked
Vacation.findAll({
where: {
id: {
[Op.or]: userVacationsIds
}
}
}).then(vacationSpec => {
Vacation.findAll({
where: {
id: {
[Op.notIn]: userVacationsIds
}
}
})
This is a Noob question regarding Backbone.JS and ActiveRecord.
I'd be grateful for a pointer on how to debug this.
I'm trying to use Backbone.Js code to create a "Trainer" object, which has a single attribute, "name" (a string).
The front end of the application is Ruby on Rails. There is a data migration for the Trainer table.
In trainers controller:
def create
document = Trainer.create! params[:trainer]
render :json => document
end
Now, in app/assets/javascripts/backbone/views/trainers/new_view.js:
Gym.Views.Trainers.NewView = Backbone.View.extend({
el : 'div.trainer_form',
template: JST['backbone/templates/trainers/new_template'],
model : new window.Gym.Models.Trainer({}),
initialize: function() {
this.document = this.options.user;
Backbone.Validation.bind(this, {
invalid: function(view, attr, error) {
$("form#new-trainer .errors ul").append("<li>" + error + "</li>")
}
});
this.render();
},
render : function() {
$(this.el).html(this.template({trainer:this.model.toJSON()}));
return this
},
events : {
"click input#submit_button" : 'create_trainer'
},
create_trainer : function(event) {
event.preventDefault()
params = $("form#new-trainer").formParams()
params['user_id'] = Gym.currentUser.userId
this.model.save(params, {success : function(model, response) {
Gym.trainers.add(model)
Objects.views.selectTrainer.render()
Gym.current_trainer = model
$("select#trainer_selector").val(Gym.current_trainer.get('id'))
Objects.views.new_trainer.model = new Gym.Models.Trainer()
Objects.views.new_trainer.render()
}
});
Now, I can see in the Rails log that I'm getting to the controller:
Started POST "/trainers" ...
Processing by TrainersController#create as JSON
Parameters: {"name"=>"Lori Stevens", "user_id"=>1, "trainer"=>{}}
However, when it gets to the SQL, I see this:
[1m^[[36mSQL (0.4ms)^[[0m ^[[1mINSERT INTO `trainers` (`created_at`, `name`, `updated_at`, `user_id`) VALUES ('2012-11-07 20:33:09', NULL, '2012-11-07 20:33:09', NULL)^[[0m
The parameter 'name' - which comes from the template, and is the attribute of the Trainer object- is not getting to the database, even though the parameter "name" is set in the JSON.
I'd appreciate a pointer on how to debug this - clearly I am not understanding how Backbone.js and ActiveRecord are connected.
The Controller takes the request from your browser and puts the data into ActiveRecord:
def create
document = Trainer.create! params[:trainer]
...
end
But then you see request in the log, params[:trainer] equals the empty hash {}
You can either change the javascript that it creates json with a hash like
{ 'trainer': {'name' : 'Lori stevens', ... }}
I don't know how easy this in backbone.
Or you can change your controller that it gets the values out of the hash and constructs an new hash for your trainer model, like it is:
gotten_name_from_json = params['name']
...
document = Trainer.create!(
{:name => gotten_name_from_json, :town => gotten_twon_from_json})
I made this verbose to show that using this you can translate what ever json comes in, even when it comes from third parties where you can not control the format.